From a5505ba81c43b7e4ec5a05ed3c17e4f3f1cb9442 Mon Sep 17 00:00:00 2001
From: Oliver Zimmerman <oezimmerman@gmail.com>
Date: Thu, 30 Jul 2020 10:30:15 +0100
Subject: [PATCH] Energy optimized and manual check warning dialogs
 (EXPOSUREAPP-1853) (#944)

* Check for battery saver mode

added a check for battery saver mode in connectivity helper
created sample dialog to appear if battery saver is enabled - string values to be confirmed.

* Implemented the energy saving warning dialog in main screen

- Created a Dialog in MainFragment that shows once (shared pref boolean check), and if energy saver mode is enabled
- Created a shared pref in local data to store if the energy saving warning has been shown
- Added strings required for dialog (Only DE confirmed)
- Added a method in ExternalActionHelper that will open energy saver settings
- Adjusted existing dialog in onboarding process

* Change from battery saver check to battery optimized check

- Change from battery saver check to battery optimized check
- Added second dialog explaining manual checks

* Update strings.xml

Text changes as provided on JIRA ticket

* Dialog will now only appear once in tracing settings.

* removed unused imports

* Update SettingsTracingFragment.kt

* Update SettingsTracingFragment.kt

* code formatting

* formatting

* Update ConnectivityHelper.kt

* Update SettingsTracingFragment.kt

* Lint and string file changes

1. Small corrections of brackets and lines
2. Keeping order of the elements in string files uniform across languages

* Update strings.xml

* Update strings.xml

Co-authored-by: Rituraj Sambherao <git.rituraj.sambherao@gmail.com>
---
 .../ui/main/MainFragment.kt                   | 32 +++++++++++
 .../de/rki/coronawarnapp/storage/LocalData.kt | 26 +++++++++
 .../OnboardingNotificationsFragment.kt        | 54 +++++++++++++++++--
 .../ui/settings/SettingsTracingFragment.kt    | 24 +++++++--
 .../util/ExternalActionHelper.kt              | 13 +++++
 .../src/main/res/values-de/strings.xml        | 14 +++++
 .../src/main/res/values/strings.xml           | 16 ++++++
 7 files changed, 171 insertions(+), 8 deletions(-)

diff --git a/Corona-Warn-App/src/device/java/de.rki.coronawarnapp/ui/main/MainFragment.kt b/Corona-Warn-App/src/device/java/de.rki.coronawarnapp/ui/main/MainFragment.kt
index ec73b7aa8..f0707b0ae 100644
--- a/Corona-Warn-App/src/device/java/de.rki.coronawarnapp/ui/main/MainFragment.kt
+++ b/Corona-Warn-App/src/device/java/de.rki.coronawarnapp/ui/main/MainFragment.kt
@@ -21,6 +21,7 @@ import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.ExternalActionHelper
+import de.rki.coronawarnapp.util.PowerManagementHelper
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -71,6 +72,7 @@ class MainFragment : Fragment() {
         setContentDescription()
 
         showOneTimeTracingExplanationDialog()
+        showEnergyOptimizedExplanationDialog()
     }
 
     override fun onResume() {
@@ -218,4 +220,34 @@ class MainFragment : Fragment() {
             }
         }
     }
+
+    private fun showEnergyOptimizedExplanationDialog() {
+
+        // check if the dialog explaining the effects of energy saver mode were already shown and if energy saver is enabled
+        if (!LocalData.energyOptimizedExplanationDialogWasShown() && !PowerManagementHelper.isIgnoringBatteryOptimizations(requireActivity())) {
+            lifecycleScope.launch {
+
+                withContext(Dispatchers.Main) {
+
+                    val dialog = DialogHelper.DialogInstance(
+                        requireActivity(),
+                        R.string.onboarding_energy_optimized_dialog_headline,
+                        R.string.onboarding_energy_optimized_dialog_body,
+                        R.string.onboarding_energy_optimized_dialog_button_positive,
+                        R.string.onboarding_energy_optimized_dialog_button_negative,
+                        false,
+                        {
+                            // go to battery optimization
+                            ExternalActionHelper.toBatteryOptimizationSettings(requireContext())
+                            LocalData.energyOptimizedExplanationDialogWasShown(true)
+                        },
+                        {
+                            // keep battery optimization enabled
+                            LocalData.energyOptimizedExplanationDialogWasShown(true)
+                        })
+                    DialogHelper.showDialog(dialog)
+                }
+            }
+        }
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt
index 68de331ba..fe14acd1d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt
@@ -328,6 +328,32 @@ object LocalData {
             )
         }
 
+    /**
+     * Gets the boolean if the user has seen the energy saving explanation dialog
+     * from the EncryptedSharedPrefs
+     *
+     * @return boolean if user has seen the energy saving explanation dialog
+     */
+    fun energyOptimizedExplanationDialogWasShown(): Boolean = getSharedPreferenceInstance().getBoolean(
+        CoronaWarnApplication.getAppContext()
+            .getString(R.string.preference_energy_optimized_explanation_shown),
+        false
+    )
+
+    /**
+     * Sets the boolean if the user has seen the energy saving explanation dialog
+     * from the EncryptedSharedPrefs
+     *
+     * @param value boolean if onboarding in relation to energy saving was completed
+     */
+    fun energyOptimizedExplanationDialogWasShown(value: Boolean) =
+        getSharedPreferenceInstance().edit(true) {
+            putBoolean(
+                CoronaWarnApplication.getAppContext()
+                    .getString(R.string.preference_energy_optimized_explanation_shown), value
+            )
+        }
+
     /****************************************************
      * SERVER FETCH DATA
      ****************************************************/
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt
index f8f62bcc6..78b41cb72 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt
@@ -13,8 +13,11 @@ import androidx.core.app.NotificationManagerCompat
 import androidx.fragment.app.Fragment
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentOnboardingNotificationsBinding
+import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.util.ConnectivityHelper
 import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.ExternalActionHelper
+import de.rki.coronawarnapp.util.PowerManagementHelper
 
 /**
  * This fragment ask the user if he wants to get notifications and finishes the onboarding afterwards.
@@ -66,6 +69,14 @@ class OnboardingNotificationsFragment : Fragment() {
     private fun checkForBackgroundJobDisabled() {
         if (!ConnectivityHelper.isBackgroundJobEnabled(requireActivity())) {
             showBackgroundJobDisabledNotification()
+        } else {
+            checkForEnergyOptimizedEnabled()
+        }
+    }
+
+    private fun checkForEnergyOptimizedEnabled() {
+        if (!PowerManagementHelper.isIgnoringBatteryOptimizations(requireActivity())) {
+            showEnergyOptimizedEnabledForBackground()
         } else {
             navigateToMain()
         }
@@ -78,21 +89,56 @@ class OnboardingNotificationsFragment : Fragment() {
             R.string.onboarding_background_fetch_dialog_body,
             R.string.onboarding_background_fetch_dialog_button_positive,
             R.string.onboarding_background_fetch_dialog_button_negative,
-            false,
-            {
+            false, {
                 val intent = Intent(
                     ACTION_APPLICATION_DETAILS_SETTINGS,
                     Uri.fromParts("package", requireContext().packageName, null)
                 )
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                 startActivity(intent)
-            },
-            {
+                // show battery optimization system dialog after background processing dialog
+                checkForEnergyOptimizedEnabled()
+            }, {
+                // declined, show additional dialog explaining manual risk calculation
+                showManualCheckingRequiredDialog()
+            })
+        DialogHelper.showDialog(dialog)
+    }
+
+    private fun showEnergyOptimizedEnabledForBackground() {
+        val dialog = DialogHelper.DialogInstance(
+            requireActivity(),
+            R.string.onboarding_energy_optimized_dialog_headline,
+            R.string.onboarding_energy_optimized_dialog_body,
+            R.string.onboarding_energy_optimized_dialog_button_positive,
+            R.string.onboarding_energy_optimized_dialog_button_negative,
+            false, {
+                // go to battery optimization
+                ExternalActionHelper.toBatteryOptimizationSettings(requireContext())
+                LocalData.energyOptimizedExplanationDialogWasShown(true)
                 navigateToMain()
+            }, {
+                // keep battery optimization enabled
+                LocalData.energyOptimizedExplanationDialogWasShown(true)
+                showManualCheckingRequiredDialog()
             })
         DialogHelper.showDialog(dialog)
     }
 
+    private fun showManualCheckingRequiredDialog() {
+        val dialog = DialogHelper.DialogInstance(
+            requireActivity(),
+            R.string.onboarding_manual_required_dialog_headline,
+            R.string.onboarding_manual_required_dialog_body,
+            R.string.onboarding_manual_required_dialog_button,
+            null,
+            false, {
+                navigateToMain()
+            }
+        )
+        DialogHelper.showDialog(dialog)
+    }
+
     private fun navigateToMain() {
         (requireActivity() as OnboardingActivity).completeOnboarding()
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
index 435bc96ea..b954f326c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt
@@ -16,16 +16,15 @@ import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper
 import de.rki.coronawarnapp.storage.LocalData
-import de.rki.coronawarnapp.storage.SettingsRepository.isLocationEnabled
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.ExternalActionHelper
 import de.rki.coronawarnapp.util.IGNORE_CHANGE_TAG
+import de.rki.coronawarnapp.util.PowerManagementHelper
 import de.rki.coronawarnapp.util.formatter.formatTracingSwitchEnabled
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
-import kotlinx.android.synthetic.main.fragment_settings_tracing.view.*
 import kotlinx.coroutines.launch
 
 /**
@@ -162,6 +161,10 @@ class SettingsTracingFragment : Fragment(),
                         // ask for consent via dialog for initial tracing activation when tracing was not
                         // activated during onboarding
                         showConsentDialog()
+                        // check if background processing is switched off, if it is, show the manual calculation dialog explanation before turning on.
+                        if (!PowerManagementHelper.isIgnoringBatteryOptimizations(requireActivity())) {
+                            showManualCheckingRequiredDialog()
+                        }
                     }
                 }
             } catch (exception: Exception) {
@@ -175,6 +178,20 @@ class SettingsTracingFragment : Fragment(),
         }
     }
 
+    private fun showManualCheckingRequiredDialog() {
+        val dialog = DialogHelper.DialogInstance(
+            requireActivity(),
+            R.string.onboarding_manual_required_dialog_headline,
+            R.string.onboarding_manual_required_dialog_body,
+            R.string.onboarding_manual_required_dialog_button,
+            null,
+            false, {
+                // close dialog
+            }
+        )
+        DialogHelper.showDialog(dialog)
+    }
+
     private fun showConsentDialog() {
         val dialog = DialogHelper.DialogInstance(
             requireActivity(),
@@ -182,8 +199,7 @@ class SettingsTracingFragment : Fragment(),
             R.string.onboarding_tracing_body_consent,
             R.string.onboarding_button_enable,
             R.string.onboarding_button_cancel,
-            true,
-            {
+            true, {
                 internalExposureNotificationPermissionHelper.requestPermissionToStartTracing()
             }, {
                 tracingViewModel.refreshIsTracingEnabled()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ExternalActionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ExternalActionHelper.kt
index 7afb041b8..088326503 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ExternalActionHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ExternalActionHelper.kt
@@ -184,4 +184,17 @@ object ExternalActionHelper {
             )
         }
     }
+
+    fun toBatterySaverSettings(context: Context) {
+        try {
+            val intent = Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+            context.startActivity(intent)
+        } catch (exception: Exception) {
+            // catch generic exception on settings navigation
+            // most likely due to device / rom specific intent issue
+            ExternalActionException(exception).report(
+                ExceptionCategory.UI
+            )
+        }
+    }
 }
diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml
index 8fef8f887..175a37c52 100644
--- a/Corona-Warn-App/src/main/res/values-de/strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/strings.xml
@@ -415,6 +415,20 @@
     <string name="onboarding_background_fetch_dialog_button_positive">"Geräteeinstellungen öffnen"</string>
     <!-- XBUT: onboarding(tracing) - dialog about background jobs, continue in app -->
     <string name="onboarding_background_fetch_dialog_button_negative">"Risiko-Ermittlung manuell starten"</string>
+    <!-- XACT: onboarding(tracing) - dialog about energy optimized header text -->
+    <string name="onboarding_energy_optimized_dialog_headline">"Priorisierte Hintergrundaktivität erlauben"</string>
+    <!-- YMSI: onboarding(tracing) - dialog about energy optimized -->
+    <string name="onboarding_energy_optimized_dialog_body">"Erlauben Sie die priorisierte Hintergrundaktivität, damit die App jederzeit Ihren Risikostatus im Hintergrund ermitteln kann (empfohlen). Damit wird die Optimierung des Akku-Verbrauchs ausschließlich für die Corona-Warn-App deaktiviert. Ein stark erhöhter Akku-Verbrauch ist hierbei nicht zu erwarten. \n\nWenn Sie diese Einstellung nicht erlauben, müssen Sie Ihren Risikostatus manuell in der App aktualisieren."</string>
+    <!-- XBUT: onboarding(tracing) - dialog about energy optimized, open device settings -->
+    <string name="onboarding_energy_optimized_dialog_button_positive">"Erlauben"</string>
+    <!-- XBUT: onboarding(tracing) - dialog about energy optimized, continue in app -->
+    <string name="onboarding_energy_optimized_dialog_button_negative">"Nicht erlauben"</string>
+    <!-- XACT: onboarding(tracing) - dialog about manual checking header text -->
+    <string name="onboarding_manual_required_dialog_headline">"Priorisierte Hintergrundaktivität deaktiviert"</string>
+    <!-- YMSI: onboarding(tracing) - dialog about manual checking -->
+    <string name="onboarding_manual_required_dialog_body">"Beachten Sie, dass Sie ohne Aktivierung der priorisierten Hintergrundaktivität die App einmal am Tag aufrufen müssen, um Ihren Risikostatus manuell aktualisieren zu können. \n\nSie können die priorisierte Hintergrundaktivität jederzeit in Ihren Einstellungen aktivieren. "</string>
+    <!-- XBUT: onboarding(tracing) - dialog about manual checking button -->
+    <string name="onboarding_manual_required_dialog_button">"Ok"</string>
     <!-- XACT: onboarding(tracing) - illustraction description, header image -->
     <string name="onboarding_tracing_illustration_description">"Drei Personen haben die Risiko-Ermittlung auf ihren Smartphones aktiviert, ihre Begegnung wird daher aufgezeichnet."</string>
     <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline -->
diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml
index 27d3cc658..4cd91ca73 100644
--- a/Corona-Warn-App/src/main/res/values/strings.xml
+++ b/Corona-Warn-App/src/main/res/values/strings.xml
@@ -71,6 +71,8 @@
     <string name="preference_test_result_notification"><xliff:g id="preference">"preference_test_result_notification"</xliff:g></string>
     <!-- NOTR -->
     <string name="preference_risk_days_explanation_shown"><xliff:g id="preference">"preference_risk_days_explanation_shown"</xliff:g></string>
+    <!-- NOTR -->
+    <string name="preference_energy_optimized_explanation_shown"><xliff:g id="preference">"preference_energy_optimized_explanation_shown"</xliff:g></string>
 
     <!-- ####################################
                      Generics
@@ -415,6 +417,20 @@
     <string name="onboarding_background_fetch_dialog_button_positive">"Open Device Settings"</string>
     <!-- XBUT: onboarding(tracing) - dialog about background jobs, continue in app -->
     <string name="onboarding_background_fetch_dialog_button_negative">"Start Exposure Logging Manually"</string>
+    <!-- XACT: onboarding(tracing) - dialog about energy optimized header text -->
+    <string name="onboarding_energy_optimized_dialog_headline">""</string>
+    <!-- YMSI: onboarding(tracing) - dialog about energy optimized -->
+    <string name="onboarding_energy_optimized_dialog_body">""</string>
+    <!-- XBUT: onboarding(tracing) - dialog about energy optimized, open device settings -->
+    <string name="onboarding_energy_optimized_dialog_button_positive">""</string>
+    <!-- XBUT: onboarding(tracing) - dialog about energy optimized, continue in app -->
+    <string name="onboarding_energy_optimized_dialog_button_negative">""</string>
+    <!-- XACT: onboarding(tracing) - dialog about manual checking header text -->
+    <string name="onboarding_manual_required_dialog_headline">""</string>
+    <!-- YMSI: onboarding(tracing) - dialog about manual checking -->
+    <string name="onboarding_manual_required_dialog_body">""</string>
+    <!-- XBUT: onboarding(tracing) - dialog about manual checking button -->
+    <string name="onboarding_manual_required_dialog_button">"Ok"</string>
     <!-- XACT: onboarding(tracing) - illustraction description, header image -->
     <string name="onboarding_tracing_illustration_description">"Three people have activated exposure logging on their devices, which will log their encounters with each other."</string>
     <!-- XHED: onboarding(tracing) - location explanation for bluetooth headline -->
-- 
GitLab