From a93fd54d09419d1fd1f1b3c166798d21d7b625a2 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Fri, 8 Sep 2023 20:02:37 +0800 Subject: [PATCH 1/8] Save foreground_service and cached consumed power in battery usage slot datebase. Bug: 284893240 Test: manual Change-Id: Ic09efabb0d93fb4bc7400b9410f32ec2f438dc73 --- .../settings/fuelgauge/batteryusage/ConvertUtils.java | 7 +++++-- .../settings/fuelgauge/protos/battery_usage_slot.proto | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java index ec0d01a4a85..a1987c9b8ad 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java @@ -473,6 +473,9 @@ public final class ConvertUtils { .setConsumePower(batteryDiffEntry.mConsumePower) .setForegroundUsageConsumePower(batteryDiffEntry.mForegroundUsageConsumePower) .setBackgroundUsageConsumePower(batteryDiffEntry.mBackgroundUsageConsumePower) + .setForegroundServiceUsageConsumePower( + batteryDiffEntry.mForegroundServiceUsageConsumePower) + .setCachedUsageConsumePower(batteryDiffEntry.mCachedUsageConsumePower) .setForegroundUsageTime(batteryDiffEntry.mForegroundUsageTimeInMs) .setBackgroundUsageTime(batteryDiffEntry.mBackgroundUsageTimeInMs) .setScreenOnTime(batteryDiffEntry.mScreenOnTimeInMs); @@ -525,9 +528,9 @@ public final class ConvertUtils { batteryUsageDiff.getScreenOnTime(), batteryUsageDiff.getConsumePower(), batteryUsageDiff.getForegroundUsageConsumePower(), - /*foregroundServiceUsageConsumePower=*/ 0, + batteryUsageDiff.getForegroundServiceUsageConsumePower(), batteryUsageDiff.getBackgroundUsageConsumePower(), - /*cachedUsageConsumePower=*/ 0); + batteryUsageDiff.getCachedUsageConsumePower()); } static BatteryDiffData convertToBatteryDiffData( diff --git a/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto b/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto index e3b604ba0a7..5bc1a3e330e 100644 --- a/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto +++ b/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto @@ -26,7 +26,9 @@ message BatteryUsageDiff { optional double consume_power = 9; optional double foreground_usage_consume_power = 10; optional double background_usage_consume_power = 11; - optional int64 foreground_usage_time = 12; - optional int64 background_usage_time = 13; - optional int64 screen_on_time = 14; + optional double foreground_service_usage_consume_power = 12; + optional double cached_usage_consume_power = 13; + optional int64 foreground_usage_time = 14; + optional int64 background_usage_time = 15; + optional int64 screen_on_time = 16; } From aeb3bd3df5fd38ac05ce91c5f2c341cf8e4e52ab Mon Sep 17 00:00:00 2001 From: randypfohl Date: Mon, 11 Sep 2023 12:24:33 -0700 Subject: [PATCH 2/8] update navigation mode settings string Test: built locally, verified with screenshot Bug:293110731 Change-Id: I4c57eca43c6f4ce8ede515ee52096e4449d88b2b --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 07d05f9cf5d..545e7849eb4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9934,7 +9934,7 @@ Gesture navigation - To go Home, swipe up from the bottom of the screen. To switch apps, swipe up from the bottom, hold, then release. To go back, swipe from either the left or right edge. + To go home, swipe up from the bottom of the screen. To switch apps, swipe up from the bottom, hold, then release. To go back, swipe from either the left or right edge. 3-button navigation From 13695adf07a7f2544801b23fab66f4008bef796e Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Wed, 6 Sep 2023 12:24:33 +0800 Subject: [PATCH 3/8] Recheck the scedule job when the device is full charged and add more logs Bug: 298539147 Fix: 298539147 Test: manual Change-Id: Id9fdba17b24cbc6558bc29576f8cd93782854ee4 --- .../batteryusage/BatteryUsageBroadcastReceiver.java | 1 + .../fuelgauge/batteryusage/PeriodicJobManager.java | 6 ++++-- .../fuelgauge/batteryusage/PeriodicJobReceiver.java | 11 +++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java index ed5f182fd90..952b83f440f 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java @@ -120,6 +120,7 @@ public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver { mFetchBatteryUsageData = true; BatteryUsageDataLoader.enqueueWork(context, /*isFullChargeStart=*/ true); + BootBroadcastReceiver.invokeJobRecheck(context); } private void sendBatteryEventData(Context context, BatteryEventType batteryEventType) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java index 8c0e66c78d8..43cd69dc942 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java @@ -68,6 +68,8 @@ public final class PeriodicJobManager { /** Schedules the next alarm job if it is available. */ public void refreshJob(final boolean fromBoot) { if (mAlarmManager == null) { + BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB, + "cannot schedule next alarm job due to AlarmManager is null"); Log.e(TAG, "cannot schedule next alarm job"); return; } @@ -80,8 +82,8 @@ public final class PeriodicJobManager { AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); final String utcToLocalTime = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis); - BatteryUsageLogUtils.writeLog( - mContext, Action.SCHEDULE_JOB, "triggerTime=" + utcToLocalTime); + BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB, + String.format("triggerTime=%s, fromBoot=%b", utcToLocalTime, fromBoot)); Log.d(TAG, "schedule next alarm job at " + utcToLocalTime); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java index 2371a19f85c..dccca43e680 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java @@ -33,12 +33,23 @@ public final class PeriodicJobReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + try { + loadDataAndRefreshJob(context, intent); + } catch (Exception e) { + BatteryUsageLogUtils.writeLog(context, Action.SCHEDULE_JOB, + String.format("loadDataAndRefreshJob() failed: %s", e)); + } + } + + private static void loadDataAndRefreshJob(Context context, Intent intent) { final String action = intent == null ? "" : intent.getAction(); if (!ACTION_PERIODIC_JOB_UPDATE.equals(action)) { Log.w(TAG, "receive unexpected action=" + action); return; } if (DatabaseUtils.isWorkProfile(context)) { + BatteryUsageLogUtils.writeLog(context, Action.SCHEDULE_JOB, + "do not refresh job for work profile"); Log.w(TAG, "do not refresh job for work profile action=" + action); return; } From 19046965da6c306640e2deaa617cf90bdc9e5f12 Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Tue, 5 Sep 2023 18:25:04 +0800 Subject: [PATCH 4/8] Refactor flags of Power Anomaly Tips in Settings Intelligence.(1/2) - Remove unused flag is_battery_tips_feedback_enabled - Update logic about is_battery_tips_enabled Bug: 284893240 Bug: 291689623 Test: Manual Change-Id: I826f517aa61e0c970048164706c4512ac3e0472d Merged-In: I826f517aa61e0c970048164706c4512ac3e0472d --- .../fuelgauge/PowerUsageFeatureProvider.java | 5 ----- .../PowerUsageFeatureProviderImpl.java | 5 ----- .../BatteryChartPreferenceController.java | 3 --- .../BatteryTipsCardPreference.java | 16 ---------------- .../batteryusage/BatteryTipsController.java | 12 ------------ .../batteryusage/PowerUsageAdvanced.java | 19 ++++++------------- .../PowerUsageFeatureProviderImplTest.java | 4 ---- 7 files changed, 6 insertions(+), 58 deletions(-) diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java index 30eabfabb3f..4253ca6db55 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java @@ -43,11 +43,6 @@ public interface PowerUsageFeatureProvider { */ boolean isBatteryTipsEnabled(); - /** - * Check whether the feedback card is enabled in the battery tips card - */ - boolean isBatteryTipsFeedbackEnabled(); - /** * Returns a threshold (in milliseconds) for the minimal screen on time in battery usage list */ diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java index 127178a4993..5931e206b1d 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java @@ -80,11 +80,6 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider return false; } - @Override - public boolean isBatteryTipsFeedbackEnabled() { - return false; - } - @Override public double getBatteryUsageListScreenOnTimeThresholdInMs() { return 0; diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 1720f0d62b1..5294dd58700 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -374,9 +374,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } mOnBatteryUsageUpdatedListener.onBatteryUsageUpdated( slotUsageData, getSlotInformation(), isBatteryUsageMapNullOrEmpty()); - - Log.d(TAG, "isBatteryTipsEnabled = " - + mPowerUsageFeatureProvider.isBatteryTipsEnabled()); if (mOnBatteryTipsUpdatedListener != null) { mExecutor.execute(() -> { final PowerAnomalyEventList anomalyEventList = mPowerUsageFeatureProvider diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java index ea5534d6a60..763a0f85e2a 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java @@ -22,7 +22,6 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -34,7 +33,6 @@ import androidx.preference.PreferenceViewHolder; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.core.SubSettingLauncher; -import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -47,7 +45,6 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic private static final String TAG = "BatteryTipsCardPreference"; - private final PowerUsageFeatureProvider mPowerUsageFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider; private String mAnomalyEventId; @@ -71,7 +68,6 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic setLayoutResource(R.layout.battery_tips_card); setSelectable(false); final FeatureFactory featureFactory = FeatureFactory.getFactory(context); - mPowerUsageFeatureProvider = featureFactory.getPowerUsageFeatureProvider(context); mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider(); mPowerAnomalyKey = null; } @@ -191,17 +187,5 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic if (mIconResourceId != 0) { ((ImageView) view.findViewById(R.id.icon)).setImageResource(mIconResourceId); } - - if (!mPowerUsageFeatureProvider.isBatteryTipsFeedbackEnabled()) { - return; - } - view.findViewById(R.id.tips_card) - .setBackgroundResource(R.drawable.battery_tips_half_rounded_top_bg); - view.findViewById(R.id.feedback_card).setVisibility(View.VISIBLE); - - ImageButton thumbUpButton = (ImageButton) view.findViewById(R.id.thumb_up); - thumbUpButton.setOnClickListener(this); - ImageButton thumbDownButton = (ImageButton) view.findViewById(R.id.thumb_down); - thumbDownButton.setOnClickListener(this); } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java index 80b26950383..fd81e9bcd0f 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java @@ -25,7 +25,6 @@ import androidx.preference.PreferenceScreen; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; -import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -38,7 +37,6 @@ public class BatteryTipsController extends BasePreferenceController { private static final String ROOT_PREFERENCE_KEY = "battery_tips_category"; private static final String CARD_PREFERENCE_KEY = "battery_tips_card"; - private final PowerUsageFeatureProvider mPowerUsageFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider; @VisibleForTesting @@ -47,14 +45,9 @@ public class BatteryTipsController extends BasePreferenceController { public BatteryTipsController(Context context) { super(context, ROOT_PREFERENCE_KEY); final FeatureFactory featureFactory = FeatureFactory.getFactory(context); - mPowerUsageFeatureProvider = featureFactory.getPowerUsageFeatureProvider(context); mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider(); } - private boolean isTipsCardVisible() { - return mPowerUsageFeatureProvider.isBatteryTipsEnabled(); - } - @Override public int getAvailabilityStatus() { return AVAILABLE; @@ -102,12 +95,7 @@ public class BatteryTipsController extends BasePreferenceController { : getStringFromResource(resourceId, resourceIndex); } - @VisibleForTesting void handleBatteryTipsCardUpdated(PowerAnomalyEvent powerAnomalyEvent) { - if (!isTipsCardVisible()) { - mCardPreference.setVisible(false); - return; - } if (powerAnomalyEvent == null) { mCardPreference.setVisible(false); return; diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java index e4f8b39540d..ae74689eef7 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java @@ -35,8 +35,6 @@ import androidx.loader.content.Loader; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.fuelgauge.BatteryBroadcastReceiver; -import com.android.settings.fuelgauge.PowerUsageFeatureProvider; -import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; @@ -138,30 +136,25 @@ public class PowerUsageAdvanced extends PowerUsageBase { mBatteryChartPreferenceController = new BatteryChartPreferenceController( context, getSettingsLifecycle(), (SettingsActivity) getActivity()); - ScreenOnTimeController screenOnTimeController = new ScreenOnTimeController(context); - BatteryUsageBreakdownController batteryUsageBreakdownController = + final ScreenOnTimeController screenOnTimeController = new ScreenOnTimeController(context); + final BatteryUsageBreakdownController batteryUsageBreakdownController = new BatteryUsageBreakdownController( context, getSettingsLifecycle(), (SettingsActivity) getActivity(), this); + final BatteryTipsController batteryTipsController = new BatteryTipsController(context); mBatteryChartPreferenceController.setOnScreenOnTimeUpdatedListener( screenOnTimeController::handleSceenOnTimeUpdated); mBatteryChartPreferenceController.setOnBatteryUsageUpdatedListener( batteryUsageBreakdownController::handleBatteryUsageUpdated); + mBatteryChartPreferenceController.setOnBatteryTipsUpdatedListener( + batteryTipsController::handleBatteryTipsCardUpdated); controllers.add(mBatteryChartPreferenceController); controllers.add(screenOnTimeController); controllers.add(batteryUsageBreakdownController); + controllers.add(batteryTipsController); setBatteryChartPreferenceController(); - final PowerUsageFeatureProvider powerUsageFeatureProvider = - FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context); - if (powerUsageFeatureProvider.isBatteryTipsEnabled()) { - BatteryTipsController batteryTipsController = new BatteryTipsController(context); - mBatteryChartPreferenceController.setOnBatteryTipsUpdatedListener( - batteryTipsController::handleBatteryTipsCardUpdated); - controllers.add(batteryTipsController); - } - return controllers; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java index bf4e8938f88..a0b449a3ba4 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java @@ -72,10 +72,6 @@ public class PowerUsageFeatureProviderImplTest { assertThat(mPowerFeatureProvider.isBatteryTipsEnabled()).isFalse(); } - @Test - public void testIsBatteryTipsFeedbackEnabled_returnFalse() { - assertThat(mPowerFeatureProvider.isBatteryTipsFeedbackEnabled()).isFalse(); - } @Test public void testGetBatteryUsageListConsumePowerThreshold_return0() { assertThat(mPowerFeatureProvider.getBatteryUsageListConsumePowerThreshold()).isEqualTo(0.0); From c37c8b87898261420616da8e3442cebb824730ae Mon Sep 17 00:00:00 2001 From: Graciela Wissen Putri Date: Thu, 7 Sep 2023 11:53:59 +0000 Subject: [PATCH 5/8] Change aspect ratio summary and footer text Add more education for device user that setting aspect ratio has not been tested by the app developer. Move Aspect ratio under Apps > Advanced instead of General. Fix: 296160724 Test: atest UserAspectRatioAppsPageProviderTest Change-Id: I2078f285d7bfb8de3a61c69f9d0ff3872d683ed4 --- res/values/strings.xml | 6 ++-- res/xml/apps.xml | 31 ++++++++++++------- .../UserAspectRatioAppsPageProvider.kt | 2 +- .../UserAspectRatioAppsPageProviderTest.kt | 3 +- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 07d05f9cf5d..55a13169d07 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -12144,7 +12144,9 @@ Aspect ratio - Choose an aspect ratio to view this app if it hasn\'t been designed to fit your %1$s + Try a new aspect ratio to view this app if it hasn\'t been designed to fit your %1$s + + Try a new aspect ratio to view this app if it hasn\'t been designed to fit your %1$s. Some apps may not be optimized for certain aspect ratios. Suggested apps @@ -12164,7 +12166,7 @@ 4:3 - The app will restart when you change aspect ratio. You may lose unsaved changes. + The app will restart when you change aspect ratio. You may lose unsaved changes. Some apps may not be optimized for certain aspect ratios. diff --git a/res/xml/apps.xml b/res/xml/apps.xml index 651ed9bc98d..db46a1a7265 100644 --- a/res/xml/apps.xml +++ b/res/xml/apps.xml @@ -79,18 +79,6 @@ android:key="dashboard_tile_placeholder" android:order="10"/> - - - - - + + + + + + + diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt index 35e99a77988..93da489b184 100644 --- a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt +++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt @@ -110,7 +110,7 @@ fun UserAspectRatioAppList( appList = appList, header = { Box(Modifier.padding(SettingsDimension.itemPadding)) { - SettingsBody(UserAspectRatioAppsPageProvider.getSummary()) + SettingsBody(stringResource(R.string.aspect_ratio_main_summary, Build.MODEL)) } Illustration(object : IllustrationModel { override val resId = R.raw.user_aspect_ratio_education diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt index c314655e0d2..044968d75e3 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt @@ -67,7 +67,8 @@ class UserAspectRatioAppsPageProviderTest { @Test fun injectEntry_summary() { setInjectEntry() - composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_summary, Build.MODEL)) + composeTestRule + .onNodeWithText(context.getString(R.string.aspect_ratio_summary, Build.MODEL)) .assertIsDisplayed() } From ea0f5b3d45f37166a8c6d14a91fd791b31494eba Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Mon, 28 Aug 2023 16:30:21 +0800 Subject: [PATCH 6/8] Refactor battery usage page contollers interaction logic (1) Move controllers interaction logic from BatteryChartPreferenceController to main page PowerUsageAdvanced. (2) Move query power anomaly logic to DataProcessManager async job. Bug: 284893240 Test: manual Change-Id: Ib23b338fe3946e68ff73a372342ec5d86494c566 Merged-In: Ib23b338fe3946e68ff73a372342ec5d86494c566 --- .../BatteryChartPreferenceController.java | 252 +++--------------- .../batteryusage/PowerUsageAdvanced.java | 163 +++++++++-- .../BatteryChartPreferenceControllerTest.java | 128 +++++---- .../batteryusage/PowerUsageAdvancedTest.java | 92 +++++++ 4 files changed, 329 insertions(+), 306 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 5294dd58700..d04ab0baf8b 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -36,7 +36,6 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -44,7 +43,6 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnCreate; import com.android.settingslib.core.lifecycle.events.OnDestroy; -import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; @@ -52,17 +50,12 @@ import com.google.common.base.Objects; import java.util.ArrayList; import java.util.Calendar; -import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; /** Controls the update for chart graph and the list items. */ public class BatteryChartPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy, OnPause, + implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy, OnSaveInstanceState, OnResume { private static final String TAG = "BatteryChartPreferenceController"; private static final String PREFERENCE_KEY = "battery_chart"; @@ -74,53 +67,17 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private static final String KEY_DAILY_CHART_INDEX = "daily_chart_index"; private static final String KEY_HOURLY_CHART_INDEX = "hourly_chart_index"; - /** - * A callback listener for battery usage is updated. - * This happens when battery usage data is ready or the selected index is changed. - */ - public interface OnBatteryUsageUpdatedListener { - /** - * The callback function for battery usage is updated. - * @param slotUsageData The battery usage diff data for the selected slot. This is used in - * the app list. - * @param slotTimestamp The selected slot timestamp information. This is used in the battery - * usage breakdown category. - * @param isAllUsageDataEmpty Whether all the battery usage data is null or empty. This is - * used when showing the footer. - */ - void onBatteryUsageUpdated( - BatteryDiffData slotUsageData, String slotTimestamp, boolean isAllUsageDataEmpty); + /** A callback listener for the selected index is updated. */ + interface OnSelectedIndexUpdatedListener { + /** The callback function for the selected index is updated. */ + void onSelectedIndexUpdated(); } - /** - * A callback listener for the device screen on time is updated. - * This happens when screen on time data is ready or the selected index is changed. - */ - public interface OnScreenOnTimeUpdatedListener { - /** - * The callback function for the device screen on time is updated. - * @param screenOnTime The selected slot device screen on time. - * @param slotTimestamp The selected slot timestamp information. - */ - void onScreenOnTimeUpdated(Long screenOnTime, String slotTimestamp); - } - - /** - * A callback listener for the battery tips card is updated. - * This happens when battery tips card is ready. - */ - public interface OnBatteryTipsUpdatedListener { - /** - * The callback function for the battery tips card is updated. - * @param powerAnomalyEvent the power anomaly event with highest score - */ - void onBatteryTipsUpdated(PowerAnomalyEvent powerAnomalyEvent); - } - - @VisibleForTesting Context mPrefContext; @VisibleForTesting + TextView mChartSummaryTextView; + @VisibleForTesting BatteryChartView mDailyChartView; @VisibleForTesting BatteryChartView mHourlyChartView; @@ -128,28 +85,20 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; @VisibleForTesting int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; - @VisibleForTesting - Map> mBatteryUsageMap; private boolean mIs24HourFormat; private View mBatteryChartViewGroup; - private TextView mChartSummaryTextView; private BatteryChartViewModel mDailyViewModel; private List mHourlyViewModels; - private OnBatteryUsageUpdatedListener mOnBatteryUsageUpdatedListener; - private OnScreenOnTimeUpdatedListener mOnScreenOnTimeUpdatedListener; - private OnBatteryTipsUpdatedListener mOnBatteryTipsUpdatedListener; - private AtomicBoolean mIsAppResume = new AtomicBoolean(false); + private OnSelectedIndexUpdatedListener mOnSelectedIndexUpdatedListener; private final SettingsActivity mActivity; private final MetricsFeatureProvider mMetricsFeatureProvider; - private final PowerUsageFeatureProvider mPowerUsageFeatureProvider; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final AnimatorListenerAdapter mHourlyChartFadeInAdapter = createHourlyChartAnimatorListenerAdapter(/*visible=*/ true); private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter = createHourlyChartAnimatorListenerAdapter(/*visible=*/ false); - private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); @VisibleForTesting final DailyChartLabelTextGenerator mDailyChartLabelTextGenerator = @@ -165,8 +114,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mIs24HourFormat = DateFormat.is24HourFormat(context); mMetricsFeatureProvider = FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(); - mPowerUsageFeatureProvider = - FeatureFactory.getFactory(mContext).getPowerUsageFeatureProvider(context); if (lifecycle != null) { lifecycle.addObserver(this); } @@ -184,15 +131,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d", mDailyChartIndex, mHourlyChartIndex)); } - @Override - public void onPause() { - mIsAppResume.compareAndSet(/* expect= */ true, /* update= */ false); - } - @Override public void onResume() { - mIsAppResume.compareAndSet(/* expect= */ false, /* update= */ true); mIs24HourFormat = DateFormat.is24HourFormat(mContext); mMetricsFeatureProvider.action(mPrefContext, SettingsEnums.OPEN_BATTERY_USAGE); } @@ -232,16 +173,16 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll return PREFERENCE_KEY; } - void setOnBatteryUsageUpdatedListener(OnBatteryUsageUpdatedListener listener) { - mOnBatteryUsageUpdatedListener = listener; + int getDailyChartIndex() { + return mDailyChartIndex; } - void setOnScreenOnTimeUpdatedListener(OnScreenOnTimeUpdatedListener listener) { - mOnScreenOnTimeUpdatedListener = listener; + int getHourlyChartIndex() { + return mHourlyChartIndex; } - void setOnBatteryTipsUpdatedListener(OnBatteryTipsUpdatedListener listener) { - mOnBatteryTipsUpdatedListener = listener; + void setOnSelectedIndexUpdatedListener(OnSelectedIndexUpdatedListener listener) { + mOnSelectedIndexUpdatedListener = listener; } void onBatteryLevelDataUpdate(final BatteryLevelData batteryLevelData) { @@ -276,13 +217,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll refreshUi(); } - void onBatteryUsageMapUpdate(Map> batteryUsageMap) { - Log.d(TAG, "onBatteryUsageMapUpdate: " + batteryUsageMap); - mBatteryUsageMap = batteryUsageMap; - logScreenUsageTime(); - refreshUi(); - } - void setBatteryChartView(@NonNull final BatteryChartView dailyChartView, @NonNull final BatteryChartView hourlyChartView) { final View parentView = (View) dailyChartView.getParent(); @@ -319,6 +253,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll ? SettingsEnums.ACTION_BATTERY_USAGE_DAILY_SHOW_ALL : SettingsEnums.ACTION_BATTERY_USAGE_DAILY_TIME_SLOT, mDailyChartIndex); + if (mOnSelectedIndexUpdatedListener != null) { + mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated(); + } }); mHourlyChartView = hourlyChartView; mHourlyChartView.setOnSelectListener(trapezoidIndex -> { @@ -340,102 +277,37 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT, mHourlyChartIndex); + if (mOnSelectedIndexUpdatedListener != null) { + mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated(); + } }); refreshUi(); } + // Show empty hourly chart view only if there is no valid battery usage data. + void showEmptyChart() { + setChartSummaryVisible(true); + mDailyChartView.setVisibility(View.GONE); + mHourlyChartView.setVisibility(View.VISIBLE); + mHourlyChartView.setViewModel(null); + } + @VisibleForTesting - boolean refreshUi() { + void refreshUi() { if (mDailyChartView == null || mHourlyChartView == null) { // Chart views are not initialized. - return false; + return; } - // When mDailyViewModel or mHourlyViewModels is null, there is no battery level data. - // This is mainly in 2 cases: - // 1) battery data is within 2 hours - // 2) no battery data in the latest 7 days (power off >= 7 days) - final boolean refreshUiResult = mDailyViewModel == null || mHourlyViewModels == null - ? refreshUiWithNoLevelDataCase() - : refreshUiWithLevelDataCase(); - - if (!refreshUiResult) { - return false; - } - - if (mOnBatteryUsageUpdatedListener != null && mBatteryUsageMap != null - && mBatteryUsageMap.get(mDailyChartIndex) != null) { - final BatteryDiffData slotUsageData = - mBatteryUsageMap.get(mDailyChartIndex).get(mHourlyChartIndex); - if (slotUsageData != null) { - mOnScreenOnTimeUpdatedListener.onScreenOnTimeUpdated( - slotUsageData.getScreenOnTime(), - getSlotInformation()); - } - mOnBatteryUsageUpdatedListener.onBatteryUsageUpdated( - slotUsageData, getSlotInformation(), isBatteryUsageMapNullOrEmpty()); - if (mOnBatteryTipsUpdatedListener != null) { - mExecutor.execute(() -> { - final PowerAnomalyEventList anomalyEventList = mPowerUsageFeatureProvider - .detectSettingsAnomaly(mContext, /* displayDrain= */ 0); - Log.d(TAG, "anomalyEventList = " + anomalyEventList); - final PowerAnomalyEvent displayEvent = - getHighestScoreAnomalyEvent(anomalyEventList); - mHandler.post(() -> { - if (mIsAppResume.get()) { - mOnBatteryTipsUpdatedListener - .onBatteryTipsUpdated(displayEvent); - } - } - ); - }); - } - } - return true; - } - - @VisibleForTesting - PowerAnomalyEvent getHighestScoreAnomalyEvent(PowerAnomalyEventList anomalyEventList) { - if (anomalyEventList == null || anomalyEventList.getPowerAnomalyEventsCount() == 0) { - return null; - } - final Set dismissedPowerAnomalyKeys = - DatabaseUtils.getDismissedPowerAnomalyKeys(mContext); - Log.d(TAG, "dismissedPowerAnomalyKeys = " + dismissedPowerAnomalyKeys); - - final PowerAnomalyEvent highestScoreEvent = anomalyEventList.getPowerAnomalyEventsList() - .stream() - .filter(event -> event.hasKey() - && !dismissedPowerAnomalyKeys.contains(event.getKey().name())) - .max(Comparator.comparing(PowerAnomalyEvent::getScore)) - .orElse(null); - Log.d(TAG, "highestScoreAnomalyEvent = " + highestScoreEvent); - return highestScoreEvent; - } - - private boolean refreshUiWithNoLevelDataCase() { - setChartSummaryVisible(false); - if (mBatteryUsageMap == null) { - // There is no battery level data and battery usage data is not ready, wait for data - // ready to refresh UI. Show nothing temporarily. + if (mDailyViewModel == null || mHourlyViewModels == null) { + setChartSummaryVisible(false); mDailyChartView.setVisibility(View.GONE); mHourlyChartView.setVisibility(View.GONE); mDailyChartView.setViewModel(null); mHourlyChartView.setViewModel(null); - return false; - } else if (mBatteryUsageMap - .get(BatteryChartViewModel.SELECTED_INDEX_ALL) - .get(BatteryChartViewModel.SELECTED_INDEX_ALL) == null) { - // There is no battery level data and battery usage data, show an empty hourly chart - // view. - mDailyChartView.setVisibility(View.GONE); - mHourlyChartView.setVisibility(View.VISIBLE); - mHourlyChartView.setViewModel(null); + return; } - return true; - } - private boolean refreshUiWithLevelDataCase() { setChartSummaryVisible(true); // Gets valid battery level data. if (isBatteryLevelDataInOneDay()) { @@ -464,15 +336,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll hourlyViewModel.setSelectedIndex(mHourlyChartIndex); mHourlyChartView.setViewModel(hourlyViewModel); } - - if (mBatteryUsageMap == null) { - // Battery usage data is not ready, wait for data ready to refresh UI. - return false; - } - return true; } - @VisibleForTesting String getSlotInformation() { if (mDailyViewModel == null || mHourlyViewModels == null) { // No data @@ -563,44 +428,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll }; } - private void logScreenUsageTime() { - if (mBatteryUsageMap == null) { - return; - } - final BatteryDiffData allBatteryDiffData = mBatteryUsageMap.get( - BatteryChartViewModel.SELECTED_INDEX_ALL).get( - BatteryChartViewModel.SELECTED_INDEX_ALL); - if (allBatteryDiffData == null) { - return; - } - mMetricsFeatureProvider.action( - mPrefContext, - SettingsEnums.ACTION_BATTERY_USAGE_SCREEN_ON_TIME, - (int) allBatteryDiffData.getScreenOnTime()); - mMetricsFeatureProvider.action( - mPrefContext, - SettingsEnums.ACTION_BATTERY_USAGE_FOREGROUND_USAGE_TIME, - (int) getTotalForegroundUsageTime()); - } - - private long getTotalForegroundUsageTime() { - if (mBatteryUsageMap == null) { - return 0; - } - final BatteryDiffData totalBatteryUsageDiffData = - mBatteryUsageMap - .get(BatteryChartViewModel.SELECTED_INDEX_ALL) - .get(BatteryChartViewModel.SELECTED_INDEX_ALL); - if (totalBatteryUsageDiffData == null) { - return 0; - } - long totalValue = 0; - for (final BatteryDiffEntry entry : totalBatteryUsageDiffData.getAppDiffEntryList()) { - totalValue += entry.mForegroundUsageTimeInMs; - } - return totalValue; - } - private boolean isBatteryLevelDataInOneDay() { return mHourlyViewModels != null && mHourlyViewModels.size() == 1; } @@ -611,19 +438,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll && mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL; } - private boolean isBatteryUsageMapNullOrEmpty() { - if (mBatteryUsageMap == null) { - return true; - } - BatteryDiffData allBatteryDiffData = mBatteryUsageMap - .get(BatteryChartViewModel.SELECTED_INDEX_ALL) - .get(BatteryChartViewModel.SELECTED_INDEX_ALL); - // If all data is null or empty, each slot must be null or empty. - return allBatteryDiffData == null - || (allBatteryDiffData.getAppDiffEntryList().isEmpty() - && allBatteryDiffData.getSystemDiffEntryList().isEmpty()); - } - @VisibleForTesting static int getTotalHours(final BatteryLevelData batteryLevelData) { if (batteryLevelData == null) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java index ae74689eef7..ccfc1e2e080 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java @@ -35,6 +35,8 @@ import androidx.loader.content.Loader; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.fuelgauge.BatteryBroadcastReceiver; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; @@ -42,9 +44,13 @@ import com.android.settingslib.utils.AsyncLoaderCompat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** Advanced power usage. */ @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) @@ -61,9 +67,14 @@ public class PowerUsageAdvanced extends PowerUsageBase { private boolean mIsChartDataLoaded = false; private long mResumeTimestamp; + private BatteryTipsController mBatteryTipsController; private BatteryChartPreferenceController mBatteryChartPreferenceController; + private ScreenOnTimeController mScreenOnTimeController; + private BatteryUsageBreakdownController mBatteryUsageBreakdownController; private Optional mBatteryLevelData; + private Map> mBatteryUsageMap; + private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); private final Handler mHandler = new Handler(Looper.getMainLooper()); private final ContentObserver mBatteryObserver = new ContentObserver(mHandler) { @@ -90,6 +101,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { if (getActivity().isChangingConfigurations()) { BatteryEntry.clearUidCache(); } + mExecutor.shutdown(); } @Override @@ -112,7 +124,6 @@ public class PowerUsageAdvanced extends PowerUsageBase { super.onPause(); // Resets the flag to reload usage data in onResume() callback. mIsChartDataLoaded = false; - mBatteryLevelData = null; final Uri uri = DatabaseUtils.BATTERY_CONTENT_URI; if (uri != null) { getContext().getContentResolver().unregisterContentObserver(mBatteryObserver); @@ -133,28 +144,25 @@ public class PowerUsageAdvanced extends PowerUsageBase { @Override protected List createPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); + mBatteryTipsController = new BatteryTipsController(context); mBatteryChartPreferenceController = new BatteryChartPreferenceController( context, getSettingsLifecycle(), (SettingsActivity) getActivity()); - final ScreenOnTimeController screenOnTimeController = new ScreenOnTimeController(context); - final BatteryUsageBreakdownController batteryUsageBreakdownController = + mScreenOnTimeController = new ScreenOnTimeController(context); + mBatteryUsageBreakdownController = new BatteryUsageBreakdownController( context, getSettingsLifecycle(), (SettingsActivity) getActivity(), this); - final BatteryTipsController batteryTipsController = new BatteryTipsController(context); - - mBatteryChartPreferenceController.setOnScreenOnTimeUpdatedListener( - screenOnTimeController::handleSceenOnTimeUpdated); - mBatteryChartPreferenceController.setOnBatteryUsageUpdatedListener( - batteryUsageBreakdownController::handleBatteryUsageUpdated); - mBatteryChartPreferenceController.setOnBatteryTipsUpdatedListener( - batteryTipsController::handleBatteryTipsCardUpdated); + controllers.add(mBatteryTipsController); controllers.add(mBatteryChartPreferenceController); - controllers.add(screenOnTimeController); - controllers.add(batteryUsageBreakdownController); - controllers.add(batteryTipsController); + controllers.add(mScreenOnTimeController); + controllers.add(mBatteryUsageBreakdownController); setBatteryChartPreferenceController(); + mBatteryChartPreferenceController.setOnSelectedIndexUpdatedListener( + this::onSelectedSlotDataUpdated); + // Force UI refresh if battery usage data was loaded before UI initialization. + onSelectedSlotDataUpdated(); return controllers; } @@ -169,12 +177,17 @@ public class PowerUsageAdvanced extends PowerUsageBase { bundle.putInt(KEY_REFRESH_TYPE, refreshType); if (!mIsChartDataLoaded) { mIsChartDataLoaded = true; + mBatteryLevelData = null; + mBatteryUsageMap = null; restartLoader(LoaderIndex.BATTERY_LEVEL_DATA_LOADER, bundle, mBatteryLevelDataLoaderCallbacks); } } private void onBatteryLevelDataUpdate(BatteryLevelData batteryLevelData) { + if (!isResumed()) { + return; + } mBatteryLevelData = Optional.ofNullable(batteryLevelData); if (mBatteryChartPreferenceController != null) { mBatteryChartPreferenceController.onBatteryLevelDataUpdate(batteryLevelData); @@ -184,23 +197,131 @@ public class PowerUsageAdvanced extends PowerUsageBase { } private void onBatteryDiffDataMapUpdate(Map batteryDiffDataMap) { - if (mBatteryLevelData != null && mBatteryChartPreferenceController != null) { - Map> batteryUsageMap = - DataProcessor.generateBatteryUsageMap( - getContext(), batteryDiffDataMap, mBatteryLevelData.orElse(null)); - DataProcessor.loadLabelAndIcon(batteryUsageMap); - mBatteryChartPreferenceController.onBatteryUsageMapUpdate(batteryUsageMap); + if (!isResumed() || mBatteryLevelData == null) { + return; } + mBatteryUsageMap = DataProcessor.generateBatteryUsageMap( + getContext(), batteryDiffDataMap, mBatteryLevelData.orElse(null)); + Log.d(TAG, "onBatteryDiffDataMapUpdate: " + mBatteryUsageMap); + DataProcessor.loadLabelAndIcon(mBatteryUsageMap); + onSelectedSlotDataUpdated(); + detectAnomaly(); + logScreenUsageTime(); + if (mBatteryChartPreferenceController != null + && mBatteryLevelData.isEmpty() && isBatteryUsageMapNullOrEmpty()) { + // No available battery usage and battery level data. + mBatteryChartPreferenceController.showEmptyChart(); + } + } + + private void onSelectedSlotDataUpdated() { + if (mBatteryChartPreferenceController == null + || mScreenOnTimeController == null + || mBatteryUsageBreakdownController == null + || mBatteryUsageMap == null) { + return; + } + final int dailyIndex = mBatteryChartPreferenceController.getDailyChartIndex(); + final int hourlyIndex = mBatteryChartPreferenceController.getHourlyChartIndex(); + final String slotInformation = mBatteryChartPreferenceController.getSlotInformation(); + final BatteryDiffData slotUsageData = mBatteryUsageMap.get(dailyIndex).get(hourlyIndex); + if (slotUsageData != null) { + mScreenOnTimeController.handleSceenOnTimeUpdated( + slotUsageData.getScreenOnTime(), slotInformation); + } + mBatteryUsageBreakdownController.handleBatteryUsageUpdated( + slotUsageData, slotInformation, isBatteryUsageMapNullOrEmpty()); Log.d(TAG, String.format("Battery usage list shows in %d millis", System.currentTimeMillis() - mResumeTimestamp)); } + private void detectAnomaly() { + mExecutor.execute(() -> { + final PowerUsageFeatureProvider powerUsageFeatureProvider = + FeatureFactory.getFactory(getContext()) + .getPowerUsageFeatureProvider(getContext()); + final PowerAnomalyEventList anomalyEventList = + powerUsageFeatureProvider.detectSettingsAnomaly( + getContext(), /* displayDrain= */ 0); + mHandler.post(() -> onAnomalyDetected(anomalyEventList)); + }); + } + + private void onAnomalyDetected(PowerAnomalyEventList anomalyEventList) { + if (!isResumed() || anomalyEventList == null) { + return; + } + Log.d(TAG, "anomalyEventList = " + anomalyEventList); + final PowerAnomalyEvent displayEvent = + getHighestScoreAnomalyEvent(getContext(), anomalyEventList); + if (displayEvent == null) { + return; + } + if (mBatteryTipsController != null) { + mBatteryTipsController.handleBatteryTipsCardUpdated(displayEvent); + } + } + private void setBatteryChartPreferenceController() { if (mHistPref != null && mBatteryChartPreferenceController != null) { mHistPref.setChartPreferenceController(mBatteryChartPreferenceController); } } + private boolean isBatteryUsageMapNullOrEmpty() { + final BatteryDiffData allBatteryDiffData = getAllBatteryDiffData(mBatteryUsageMap); + // If all data is null or empty, each slot must be null or empty. + return allBatteryDiffData == null + || (allBatteryDiffData.getAppDiffEntryList().isEmpty() + && allBatteryDiffData.getSystemDiffEntryList().isEmpty()); + } + + private void logScreenUsageTime() { + final BatteryDiffData allBatteryDiffData = getAllBatteryDiffData(mBatteryUsageMap); + if (allBatteryDiffData == null) { + return; + } + long totalForegroundUsageTime = 0; + for (final BatteryDiffEntry entry : allBatteryDiffData.getAppDiffEntryList()) { + totalForegroundUsageTime += entry.mForegroundUsageTimeInMs; + } + mMetricsFeatureProvider.action( + getContext(), + SettingsEnums.ACTION_BATTERY_USAGE_SCREEN_ON_TIME, + (int) allBatteryDiffData.getScreenOnTime()); + mMetricsFeatureProvider.action( + getContext(), + SettingsEnums.ACTION_BATTERY_USAGE_FOREGROUND_USAGE_TIME, + (int) totalForegroundUsageTime); + } + + @VisibleForTesting + static PowerAnomalyEvent getHighestScoreAnomalyEvent( + Context context, PowerAnomalyEventList anomalyEventList) { + if (anomalyEventList == null || anomalyEventList.getPowerAnomalyEventsCount() == 0) { + return null; + } + final Set dismissedPowerAnomalyKeys = + DatabaseUtils.getDismissedPowerAnomalyKeys(context); + Log.d(TAG, "dismissedPowerAnomalyKeys = " + dismissedPowerAnomalyKeys); + + final PowerAnomalyEvent highestScoreEvent = anomalyEventList.getPowerAnomalyEventsList() + .stream() + .filter(event -> event.hasKey() + && !dismissedPowerAnomalyKeys.contains(event.getKey().name())) + .max(Comparator.comparing(PowerAnomalyEvent::getScore)) + .orElse(null); + Log.d(TAG, "highestScoreAnomalyEvent = " + highestScoreEvent); + return highestScoreEvent; + } + + private static BatteryDiffData getAllBatteryDiffData( + Map> batteryUsageMap) { + return batteryUsageMap == null ? null : batteryUsageMap + .get(BatteryChartViewModel.SELECTED_INDEX_ALL) + .get(BatteryChartViewModel.SELECTED_INDEX_ALL); + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override @@ -237,7 +358,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { public BatteryLevelData loadInBackground() { return DataProcessManager.getBatteryLevelData( getContext(), mHandler, /*isFromPeriodJob=*/ false, - map -> PowerUsageAdvanced.this.onBatteryDiffDataMapUpdate(map)); + PowerUsageAdvanced.this::onBatteryDiffDataMapUpdate); } }; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java index 786a529814d..cd4e599546d 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -44,9 +45,9 @@ import android.util.ArrayMap; import android.view.View; import android.view.ViewPropertyAnimator; import android.widget.LinearLayout; +import android.widget.TextView; import com.android.settings.SettingsActivity; -import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; @@ -72,6 +73,8 @@ public final class BatteryChartPreferenceControllerTest { @Mock private SettingsActivity mSettingsActivity; @Mock + private TextView mChartSummaryTextView; + @Mock private BatteryChartView mDailyChartView; @Mock private BatteryChartView mHourlyChartView; @@ -112,6 +115,7 @@ public final class BatteryChartPreferenceControllerTest { setupHourlyChartViewAnimationMock(); mBatteryChartPreferenceController = createController(); mBatteryChartPreferenceController.mPrefContext = mContext; + mBatteryChartPreferenceController.mChartSummaryTextView = mChartSummaryTextView; mBatteryChartPreferenceController.mDailyChartView = mDailyChartView; mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView; BatteryDiffEntry.clearCache(); @@ -180,7 +184,6 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mDailyChartLabelTextGenerator); mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); - mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE); verify(mViewPropertyAnimator, atLeastOnce()).alpha(0f); @@ -275,29 +278,78 @@ public final class BatteryChartPreferenceControllerTest { } @Test - public void refreshUi_normalCase_returnTrue() { + public void onBatteryLevelDataUpdate_oneDay_showHourlyChartOnly() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); - mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); - assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue(); + + verify(mChartSummaryTextView).setVisibility(View.VISIBLE); + verify(mDailyChartView).setVisibility(View.GONE); + verify(mHourlyChartView).setVisibility(View.VISIBLE); } @Test - public void refreshUi_batteryIndexedMapIsNull_returnTrue() { + public void onBatteryLevelDataUpdate_selectAllForMultipleDays_showDailyChartOnly() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + + mBatteryChartPreferenceController.mDailyChartIndex = SELECTED_INDEX_ALL; + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + + verify(mChartSummaryTextView).setVisibility(View.VISIBLE); + verify(mDailyChartView).setVisibility(View.VISIBLE); + verify(mHourlyChartView, never()).setVisibility(View.VISIBLE); + } + + @Test + public void onBatteryLevelDataUpdate_selectOneDayForMultipleDays_showBothCharts() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + + mBatteryChartPreferenceController.mDailyChartIndex = 0; + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + + verify(mChartSummaryTextView).setVisibility(View.VISIBLE); + verify(mDailyChartView).setVisibility(View.VISIBLE); + verify(mHourlyChartView).setVisibility(View.VISIBLE); + } + + @Test + public void onBatteryLevelDataUpdate_batteryLevelDataIsNull_showNoChart() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(null); - mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); - assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue(); + + verify(mChartSummaryTextView).setVisibility(View.GONE); + verify(mDailyChartView).setVisibility(View.GONE); + verify(mHourlyChartView).setVisibility(View.GONE); + } + + @Test + public void showEmptyChart_normalCase_showEmptyChart() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + + mBatteryChartPreferenceController.showEmptyChart(); + + verify(mChartSummaryTextView).setVisibility(View.VISIBLE); + verify(mDailyChartView).setVisibility(View.GONE); + verify(mHourlyChartView).setVisibility(View.VISIBLE); } @Test public void refreshUi_dailyChartViewIsNull_ignoreRefresh() { mBatteryChartPreferenceController.mDailyChartView = null; - assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse(); + + mBatteryChartPreferenceController.refreshUi(); + + verify(mChartSummaryTextView, never()).setVisibility(anyInt()); } @Test public void refreshUi_hourlyChartViewIsNull_ignoreRefresh() { mBatteryChartPreferenceController.mHourlyChartView = null; - assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse(); + + mBatteryChartPreferenceController.refreshUi(); + + verify(mChartSummaryTextView, never()).setVisibility(anyInt()); } @Test @@ -408,57 +460,6 @@ public final class BatteryChartPreferenceControllerTest { assertThat(totalHour).isEqualTo(59); } - @Test - public void getHighestScoreAnomalyEvent_withEmptyOrNullList_getNull() { - assertThat(mBatteryChartPreferenceController.getHighestScoreAnomalyEvent(null)) - .isEqualTo(null); - assertThat(mBatteryChartPreferenceController.getHighestScoreAnomalyEvent( - BatteryTestUtils.createEmptyPowerAnomalyEventList())) - .isEqualTo(null); - } - - @Test - public void getHighestScoreAnomalyEvent_withoutDismissed_getHighestScoreEvent() { - final PowerAnomalyEventList eventList = - BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); - - final PowerAnomalyEvent highestScoreEvent = - mBatteryChartPreferenceController.getHighestScoreAnomalyEvent(eventList); - - assertThat(highestScoreEvent) - .isEqualTo(BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent()); - } - - @Test - public void getHighestScoreAnomalyEvent_withBrightnessDismissed_getScreenTimeout() { - final PowerAnomalyEventList eventList = - BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); - DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); - DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, PowerAnomalyKey.KEY_BRIGHTNESS.name()); - - final PowerAnomalyEvent highestScoreEvent = - mBatteryChartPreferenceController.getHighestScoreAnomalyEvent(eventList); - - assertThat(highestScoreEvent) - .isEqualTo(BatteryTestUtils.createScreenTimeoutAnomalyEvent()); - } - - @Test - public void getHighestScoreAnomalyEvent_withAllDismissed_getNull() { - final PowerAnomalyEventList eventList = - BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); - DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); - for (PowerAnomalyKey key : PowerAnomalyKey.values()) { - DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, key.name()); - } - - final PowerAnomalyEvent highestScoreEvent = - mBatteryChartPreferenceController.getHighestScoreAnomalyEvent(eventList); - - assertThat(highestScoreEvent).isEqualTo(null); - } - - private static Long generateTimestamp(int index) { // "2021-04-23 07:00:00 UTC" + index hours return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS; @@ -481,11 +482,6 @@ public final class BatteryChartPreferenceControllerTest { return new BatteryLevelData(batteryLevelMap); } - private static Map> getEmptyBatteryUsageMap() { - return Map.of(SELECTED_INDEX_ALL, Map.of(SELECTED_INDEX_ALL, new BatteryDiffData( - null, 0, 0, 0, 0, 0, List.of(), List.of(), Set.of(), Set.of(), false))); - } - private BatteryChartPreferenceController createController() { final BatteryChartPreferenceController controller = new BatteryChartPreferenceController( diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java new file mode 100644 index 00000000000..ee2a8b2c9a3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.fuelgauge.batteryusage; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import com.android.settings.testutils.BatteryTestUtils; +import com.android.settings.testutils.shadow.ShadowDashboardFragment; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowDashboardFragment.class) +public final class PowerUsageAdvancedTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + } + + @Test + public void getHighestScoreAnomalyEvent_withEmptyOrNullList_getNull() { + assertThat(PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, null)).isNull(); + assertThat(PowerUsageAdvanced.getHighestScoreAnomalyEvent( + mContext, BatteryTestUtils.createEmptyPowerAnomalyEventList())).isNull(); + } + + @Test + public void getHighestScoreAnomalyEvent_withoutDismissed_getHighestScoreEvent() { + final PowerAnomalyEventList powerAnomalyEventList = + BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); + + final PowerAnomalyEvent highestScoreEvent = + PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList); + + assertThat(highestScoreEvent) + .isEqualTo(BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent()); + } + + @Test + public void getHighestScoreAnomalyEvent_withBrightnessDismissed_getScreenTimeout() { + final PowerAnomalyEventList powerAnomalyEventList = + BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); + DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); + DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, PowerAnomalyKey.KEY_BRIGHTNESS.name()); + + final PowerAnomalyEvent highestScoreEvent = + PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList); + + assertThat(highestScoreEvent) + .isEqualTo(BatteryTestUtils.createScreenTimeoutAnomalyEvent()); + } + + @Test + public void getHighestScoreAnomalyEvent_withAllDismissed_getNull() { + final PowerAnomalyEventList powerAnomalyEventList = + BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); + DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); + for (PowerAnomalyKey key : PowerAnomalyKey.values()) { + DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, key.name()); + } + + final PowerAnomalyEvent highestScoreEvent = + PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList); + + assertThat(highestScoreEvent).isEqualTo(null); + } +} From 67506342596189cf6258c1427da9e6a403062339 Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Tue, 22 Aug 2023 18:09:42 +0800 Subject: [PATCH 7/8] Impl highlight effect on BatteryChartView slots. Screenshot: https://screenshot.googleplex.com/F5VrGjj5kdNHMV6 Bug: 284893240 Bug: 291689623 Test: manual Change-Id: I846d95d31e8bb839481b86a94d5191ff205f8328 --- res/layout/battery_chart_graph.xml | 5 +- res/values/dimens.xml | 6 ++ .../BatteryChartPreferenceController.java | 39 ++++++++ .../batteryusage/BatteryChartView.java | 96 +++++++++++++++++- .../batteryusage/BatteryChartViewModel.java | 9 ++ .../batteryusage/BatteryLevelData.java | 23 +++++ .../BatteryTipsCardPreference.java | 81 +++++---------- .../batteryusage/BatteryTipsController.java | 99 ++++++++++++++++--- .../batteryusage/PowerUsageAdvanced.java | 64 ++++++++++-- .../protos/power_anomaly_event.proto | 2 + .../batteryusage/BatteryLevelDataTest.java | 29 ++++++ .../BatteryTipsCardPreferenceTest.java | 67 +++++++++++-- .../BatteryTipsControllerTest.java | 30 +++--- .../batteryusage/PowerUsageAdvancedTest.java | 95 +++++++++++++++++- .../settings/testutils/BatteryTestUtils.java | 3 + 15 files changed, 538 insertions(+), 110 deletions(-) diff --git a/res/layout/battery_chart_graph.xml b/res/layout/battery_chart_graph.xml index f116c8ee313..9e816ed9ba1 100644 --- a/res/layout/battery_chart_graph.xml +++ b/res/layout/battery_chart_graph.xml @@ -27,6 +27,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginVertical="16dp" + android:textAlignment="viewStart" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary" android:text="@string/battery_usage_chart_graph_hint_last_full_charge" /> @@ -40,7 +41,7 @@ 6dp 1dp 4dp + 4dp + 4dp + 12dp + 2dp + 12dp + 182dp 5dp 1dp 2dp diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index d04ab0baf8b..1893096dbf4 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -85,6 +85,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; @VisibleForTesting int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; + @VisibleForTesting + int mDailyHighlightSlotIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; + @VisibleForTesting + int mHourlyHighlightSlotIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; private boolean mIs24HourFormat; private View mBatteryChartViewGroup; @@ -217,6 +221,37 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll refreshUi(); } + void onHighlightSlotIndexUpdate(int dailyHighlightSlotIndex, int hourlyHighlightSlotIndex) { + if (mDailyHighlightSlotIndex == dailyHighlightSlotIndex + && mHourlyHighlightSlotIndex == hourlyHighlightSlotIndex) { + return; + } + mDailyHighlightSlotIndex = dailyHighlightSlotIndex; + mHourlyHighlightSlotIndex = hourlyHighlightSlotIndex; + refreshUi(); + } + + void selectHighlightSlotIndex() { + if (mDailyHighlightSlotIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID + || mHourlyHighlightSlotIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID) { + return; + } + if (mDailyHighlightSlotIndex == mDailyChartIndex + && mHourlyHighlightSlotIndex == mHourlyChartIndex) { + return; + } + mDailyChartIndex = mDailyHighlightSlotIndex; + mHourlyChartIndex = mHourlyHighlightSlotIndex; + Log.d(TAG, String.format("onDailyChartSelect:%d, onHourlyChartSelect:%d", + mDailyChartIndex, mHourlyChartIndex)); + refreshUi(); + mHandler.post(() -> mDailyChartView.announceForAccessibility( + getAccessibilityAnnounceMessage())); + if (mOnSelectedIndexUpdatedListener != null) { + mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated(); + } + } + void setBatteryChartView(@NonNull final BatteryChartView dailyChartView, @NonNull final BatteryChartView hourlyChartView) { final View parentView = (View) dailyChartView.getParent(); @@ -320,6 +355,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; } mDailyViewModel.setSelectedIndex(mDailyChartIndex); + mDailyViewModel.setHighlightSlotIndex(mDailyHighlightSlotIndex); mDailyChartView.setViewModel(mDailyViewModel); } @@ -334,6 +370,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; } hourlyViewModel.setSelectedIndex(mHourlyChartIndex); + hourlyViewModel.setHighlightSlotIndex((mDailyChartIndex == mDailyHighlightSlotIndex) + ? mHourlyHighlightSlotIndex + : BatteryChartViewModel.SELECTED_INDEX_INVALID); mHourlyChartView.setViewModel(hourlyViewModel); } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java index 086f56c03fe..bb468fe35d6 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java @@ -31,6 +31,7 @@ import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.ArraySet; import android.util.AttributeSet; @@ -90,6 +91,15 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick private int mTrapezoidHoverColor; private int mDefaultTextColor; private int mTextPadding; + private int mTransomIconSize; + private int mTransomTop; + private int mTransomViewHeight; + private int mTransomLineDefaultColor; + private int mTransomLineSelectedColor; + private float mTransomPadding; + private Drawable mTransomIcon; + private Paint mTransomLinePaint; + private Paint mTransomSelectedSlotPaint; private Paint mDividerPaint; private Paint mTrapezoidPaint; private Paint mTextPaint; @@ -123,8 +133,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick return; } - Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.", - viewModel.size(), viewModel.selectedIndex())); + Log.d(TAG, String.format( + "setViewModel(): size: %d, selectedIndex: %d, getHighlightSlotIndex: %d", + viewModel.size(), viewModel.selectedIndex(), viewModel.getHighlightSlotIndex())); mViewModel = viewModel; initializeAxisLabelsBounds(); initializeTrapezoidSlots(viewModel.size() - 1); @@ -162,7 +173,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick mPercentageBounds[index]); } // Updates the indent configurations. - mIndent.top = mPercentageBounds[0].height(); + mIndent.top = mPercentageBounds[0].height() + mTransomViewHeight; final int textWidth = mPercentageBounds[0].width() + mTextPadding; if (isRTL()) { mIndent.left = textWidth; @@ -196,6 +207,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick } drawVerticalDividers(canvas); drawTrapezoids(canvas); + drawTransomLine(canvas); } @Override @@ -340,6 +352,40 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick resources.getDimensionPixelSize(R.dimen.chartview_trapezoid_radius))); // Initializes for drawing text information. mTextPadding = resources.getDimensionPixelSize(R.dimen.chartview_text_padding); + // Initializes the padding top for drawing text information. + mTransomViewHeight = resources.getDimensionPixelSize( + R.dimen.chartview_transom_layout_height); + } + + private void initializeTransomPaint() { + if (mTransomLinePaint != null && mTransomSelectedSlotPaint != null + && mTransomIcon != null) { + return; + } + // Initializes the transom line paint. + final Resources resources = getContext().getResources(); + final int transomLineWidth = resources.getDimensionPixelSize( + R.dimen.chartview_transom_width); + final int transomRadius = resources.getDimensionPixelSize(R.dimen.chartview_transom_radius); + mTransomPadding = transomRadius * .5f; + mTransomTop = resources.getDimensionPixelSize(R.dimen.chartview_transom_padding_top); + mTransomLineDefaultColor = Utils.getDisabled(mContext, DIVIDER_COLOR); + mTransomLineSelectedColor = resources.getColor( + R.color.color_battery_anomaly_yellow_selector); + final int slotHighlightColor = Utils.getDisabled(mContext, mTransomLineSelectedColor); + mTransomIconSize = resources.getDimensionPixelSize(R.dimen.chartview_transom_icon_size); + mTransomLinePaint = new Paint(); + mTransomLinePaint.setAntiAlias(true); + mTransomLinePaint.setStyle(Paint.Style.STROKE); + mTransomLinePaint.setStrokeWidth(transomLineWidth); + mTransomLinePaint.setStrokeCap(Paint.Cap.ROUND); + mTransomLinePaint.setPathEffect(new CornerPathEffect(transomRadius)); + mTransomSelectedSlotPaint = new Paint(); + mTransomSelectedSlotPaint.setAntiAlias(true); + mTransomSelectedSlotPaint.setColor(slotHighlightColor); + mTransomSelectedSlotPaint.setStyle(Paint.Style.FILL); + // Get the companion icon beside transom line + mTransomIcon = getResources().getDrawable(R.drawable.ic_battery_tips_warning_icon); } private void drawHorizontalDividers(Canvas canvas) { @@ -592,6 +638,50 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick } } + private boolean isHighlightSlotValid() { + return mViewModel != null && mViewModel.getHighlightSlotIndex() + != BatteryChartViewModel.SELECTED_INDEX_INVALID; + } + + private void drawTransomLine(Canvas canvas) { + if (!isHighlightSlotValid()) { + return; + } + initializeTransomPaint(); + // Draw the whole transom line and a warning icon + mTransomLinePaint.setColor(mTransomLineDefaultColor); + final int width = getWidth() - abs(mIndent.width()); + final float transomOffset = mTrapezoidHOffset + mDividerWidth * .5f + mTransomPadding; + final float trapezoidBottom = getHeight() - mIndent.bottom - mDividerHeight - mDividerWidth + - mTrapezoidVOffset; + canvas.drawLine(mIndent.left + transomOffset, mTransomTop, + mIndent.left + width - transomOffset, mTransomTop, + mTransomLinePaint); + drawTransomIcon(canvas); + // Draw selected segment of transom line and a highlight slot + mTransomLinePaint.setColor(mTransomLineSelectedColor); + final int index = mViewModel.getHighlightSlotIndex(); + final float startX = mTrapezoidSlots[index].mLeft; + final float endX = mTrapezoidSlots[index].mRight; + canvas.drawLine(startX + mTransomPadding, mTransomTop, + endX - mTransomPadding, mTransomTop, + mTransomLinePaint); + canvas.drawRect(startX, mTransomTop, endX, trapezoidBottom, + mTransomSelectedSlotPaint); + } + + private void drawTransomIcon(Canvas canvas) { + if (mTransomIcon == null) { + return; + } + final int left = isRTL() + ? mIndent.left - mTextPadding - mTransomIconSize + : getWidth() - abs(mIndent.width()) + mTextPadding; + mTransomIcon.setBounds(left, mTransomTop - mTransomIconSize / 2, + left + mTransomIconSize, mTransomTop + mTransomIconSize / 2); + mTransomIcon.draw(canvas); + } + // Searches the corresponding trapezoid index from x location. private int getTrapezoidIndex(float x) { if (mTrapezoidSlots == null) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java index f58d2415e19..bf8a771a117 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java @@ -55,6 +55,7 @@ class BatteryChartViewModel { private final String[] mFullTexts; private int mSelectedIndex = SELECTED_INDEX_ALL; + private int mHighlightSlotIndex = SELECTED_INDEX_INVALID; BatteryChartViewModel(@NonNull List levels, @NonNull List timestamps, @NonNull AxisLabelPosition axisLabelPosition, @@ -106,6 +107,14 @@ class BatteryChartViewModel { mSelectedIndex = index; } + public int getHighlightSlotIndex() { + return mHighlightSlotIndex; + } + + public void setHighlightSlotIndex(int index) { + mHighlightSlotIndex = index; + } + @Override public int hashCode() { return Objects.hash(mLevels, mTimestamps, mSelectedIndex, mAxisLabelPosition); diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java index 53ebbd90595..09d66c704dc 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java @@ -20,6 +20,7 @@ import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKN import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -69,6 +70,16 @@ public final class BatteryLevelData { return String.format(Locale.ENGLISH, "timestamps: %s; levels: %s", Objects.toString(mTimestamps), Objects.toString(mLevels)); } + + private int getIndexByTimestamps(long startTimestamp, long endTimestamp) { + for (int index = 0; index < mTimestamps.size() - 1; index++) { + if (mTimestamps.get(index) <= startTimestamp + && endTimestamp <= mTimestamps.get(index + 1)) { + return index; + } + } + return BatteryChartViewModel.SELECTED_INDEX_INVALID; + } } /** @@ -100,6 +111,18 @@ public final class BatteryLevelData { } } + /** Gets daily and hourly index between start and end timestamps. */ + public Pair getIndexByTimestamps(long startTimestamp, long endTimestamp) { + final int dailyHighlightIndex = + mDailyBatteryLevels.getIndexByTimestamps(startTimestamp, endTimestamp); + final int hourlyHighlightIndex = + (dailyHighlightIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID) + ? BatteryChartViewModel.SELECTED_INDEX_INVALID + : mHourlyBatteryLevelsPerDay.get(dailyHighlightIndex) + .getIndexByTimestamps(startTimestamp, endTimestamp); + return Pair.create(dailyHighlightIndex, hourlyHighlightIndex); + } + public PeriodBatteryLevelData getDailyBatteryLevels() { return mDailyBatteryLevels; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java index 763a0f85e2a..e98077c699c 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java @@ -16,9 +16,7 @@ package com.android.settings.fuelgauge.batteryusage; -import android.app.settings.SettingsEnums; import android.content.Context; -import android.os.Bundle; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; @@ -31,8 +29,6 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; -import com.android.settings.SettingsActivity; -import com.android.settings.core.SubSettingLauncher; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -45,10 +41,17 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic private static final String TAG = "BatteryTipsCardPreference"; - private final MetricsFeatureProvider mMetricsFeatureProvider; + interface OnConfirmListener { + void onConfirm(); + } - private String mAnomalyEventId; - private PowerAnomalyKey mPowerAnomalyKey; + interface OnRejectListener { + void onReject(); + } + + private final MetricsFeatureProvider mMetricsFeatureProvider; + private OnConfirmListener mOnConfirmListener; + private OnRejectListener mOnRejectListener; private int mIconResourceId = 0; private int mMainButtonStrokeColorResourceId = 0; @@ -56,12 +59,6 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic CharSequence mMainButtonLabel; @VisibleForTesting CharSequence mDismissButtonLabel; - @VisibleForTesting - String mDestinationComponentName; - @VisibleForTesting - String mPreferenceHighlightKey; - @VisibleForTesting - Integer mSourceMetricsCategory; public BatteryTipsCardPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -69,7 +66,14 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic setSelectable(false); final FeatureFactory featureFactory = FeatureFactory.getFactory(context); mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider(); - mPowerAnomalyKey = null; + } + + public void setOnConfirmListener(OnConfirmListener listener) { + mOnConfirmListener = listener; + } + + public void setOnRejectListener(OnRejectListener listener) { + mOnRejectListener = listener; } /** @@ -92,13 +96,6 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic } } - /** - * Sets the anomaly event id which is used in metrics. - */ - public void setAnomalyEventId(final String anomalyEventId) { - mAnomalyEventId = anomalyEventId; - } - /** * Sets the label of main button in tips card. */ @@ -119,50 +116,18 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic } } - /** - * Sets the power anomaly key of battery tips card. - */ - public void setPowerAnomalyKey(final PowerAnomalyKey powerAnomalyKey) { - mPowerAnomalyKey = powerAnomalyKey; - } - - /** - * Sets the info of target fragment launched by main button. - */ - public void setMainButtonLauncherInfo(final String destinationClassName, - final Integer sourceMetricsCategory, final String highlightKey) { - mDestinationComponentName = destinationClassName; - mSourceMetricsCategory = sourceMetricsCategory; - mPreferenceHighlightKey = highlightKey; - } - @Override public void onClick(View view) { final int viewId = view.getId(); if (viewId == R.id.main_button || viewId == R.id.tips_card) { - if (TextUtils.isEmpty(mDestinationComponentName)) { - return; - } - Bundle arguments = Bundle.EMPTY; - if (!TextUtils.isEmpty(mPreferenceHighlightKey)) { - arguments = new Bundle(1); - arguments.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, - mPreferenceHighlightKey); - } - new SubSettingLauncher(getContext()) - .setDestination(mDestinationComponentName) - .setSourceMetricsCategory(mSourceMetricsCategory) - .setArguments(arguments) - .launch(); setVisible(false); - mMetricsFeatureProvider.action( - getContext(), SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, mAnomalyEventId); + if (mOnConfirmListener != null) { + mOnConfirmListener.onConfirm(); + } } else if (viewId == R.id.dismiss_button) { setVisible(false); - mMetricsFeatureProvider.action( - getContext(), SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, mAnomalyEventId); - if (mPowerAnomalyKey != null) { - DatabaseUtils.setDismissedPowerAnomalyKeys(getContext(), mPowerAnomalyKey.name()); + if (mOnRejectListener != null) { + mOnRejectListener.onReject(); } } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java index fd81e9bcd0f..400e70ad5dd 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java @@ -18,13 +18,16 @@ package com.android.settings.fuelgauge.batteryusage; import android.app.settings.SettingsEnums; import android.content.Context; +import android.os.Bundle; import android.text.TextUtils; import androidx.preference.PreferenceScreen; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; +import com.android.settings.SettingsActivity; import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.SubSettingLauncher; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -39,6 +42,21 @@ public class BatteryTipsController extends BasePreferenceController { private final MetricsFeatureProvider mMetricsFeatureProvider; + /** A callback listener for the battery tips is confirmed. */ + interface OnAnomalyConfirmListener { + /** The callback function for the battery tips is confirmed. */ + void onAnomalyConfirm(); + } + + /** A callback listener for the battery tips is rejected. */ + interface OnAnomalyRejectListener { + /** The callback function for the battery tips is rejected. */ + void onAnomalyReject(); + } + + private OnAnomalyConfirmListener mOnAnomalyConfirmListener; + private OnAnomalyRejectListener mOnAnomalyRejectListener; + @VisibleForTesting BatteryTipsCardPreference mCardPreference; @@ -59,6 +77,14 @@ public class BatteryTipsController extends BasePreferenceController { mCardPreference = screen.findPreference(CARD_PREFERENCE_KEY); } + void setOnAnomalyConfirmListener(OnAnomalyConfirmListener listener) { + mOnAnomalyConfirmListener = listener; + } + + void setOnAnomalyRejectListener(OnAnomalyRejectListener listener) { + mOnAnomalyRejectListener = listener; + } + private T getInfo(PowerAnomalyEvent powerAnomalyEvent, Function warningBannerInfoSupplier, Function warningItemInfoSupplier) { @@ -95,6 +121,21 @@ public class BatteryTipsController extends BasePreferenceController { : getStringFromResource(resourceId, resourceIndex); } + /** Generate a key string of current anomaly to record as dismissed in sharedPreferences. */ + public static String getDismissRecordKey(PowerAnomalyEvent event) { + if (!event.hasKey()) { + return null; + } + switch (event.getKey()){ + case KEY_APP: + return event.hasWarningItemInfo() + && event.getWarningItemInfo().hasDismissRecordKey() + ? event.getWarningItemInfo().getDismissRecordKey() : null; + default: + return event.getKey().name(); + } + } + void handleBatteryTipsCardUpdated(PowerAnomalyEvent powerAnomalyEvent) { if (powerAnomalyEvent == null) { mCardPreference.setVisible(false); @@ -109,44 +150,76 @@ public class BatteryTipsController extends BasePreferenceController { R.array.battery_tips_card_colors, cardStyleId, "color"); // Get card preference strings and navigate fragment info + final String eventId = powerAnomalyEvent.hasEventId() + ? powerAnomalyEvent.getEventId() : null; final PowerAnomalyKey powerAnomalyKey = powerAnomalyEvent.hasKey() ? powerAnomalyEvent.getKey() : null; final int resourceIndex = powerAnomalyKey != null ? powerAnomalyKey.getNumber() : -1; - String titleString = getString(powerAnomalyEvent, WarningBannerInfo::getTitleString, + final String titleString = getString(powerAnomalyEvent, WarningBannerInfo::getTitleString, WarningItemInfo::getTitleString, R.array.power_anomaly_titles, resourceIndex); if (titleString.isEmpty()) { mCardPreference.setVisible(false); return; } - String mainBtnString = getString(powerAnomalyEvent, + final String mainBtnString = getString(powerAnomalyEvent, WarningBannerInfo::getMainButtonString, WarningItemInfo::getMainButtonString, R.array.power_anomaly_main_btn_strings, resourceIndex); - String dismissBtnString = getString(powerAnomalyEvent, + final String dismissBtnString = getString(powerAnomalyEvent, WarningBannerInfo::getCancelButtonString, WarningItemInfo::getCancelButtonString, R.array.power_anomaly_dismiss_btn_strings, resourceIndex); - String destinationClassName = getInfo(powerAnomalyEvent, + final String destinationClassName = getInfo(powerAnomalyEvent, WarningBannerInfo::getMainButtonDestination, null); - Integer sourceMetricsCategory = getInfo(powerAnomalyEvent, + final Integer sourceMetricsCategory = getInfo(powerAnomalyEvent, WarningBannerInfo::getMainButtonSourceMetricsCategory, null); - String preferenceHighlightKey = getInfo(powerAnomalyEvent, + final String preferenceHighlightKey = getInfo(powerAnomalyEvent, WarningBannerInfo::getMainButtonSourceHighlightKey, null); // Update card preference and main button fragment launcher - mCardPreference.setAnomalyEventId(powerAnomalyEvent.getEventId()); - mCardPreference.setPowerAnomalyKey(powerAnomalyKey); mCardPreference.setTitle(titleString); mCardPreference.setIconResourceId(iconResId); mCardPreference.setMainButtonStrokeColorResourceId(colorResId); mCardPreference.setMainButtonLabel(mainBtnString); mCardPreference.setDismissButtonLabel(dismissBtnString); - mCardPreference.setMainButtonLauncherInfo( - destinationClassName, sourceMetricsCategory, preferenceHighlightKey); - mCardPreference.setVisible(true); - mMetricsFeatureProvider.action(mContext, - SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, powerAnomalyEvent.getEventId()); + // Set battery tips card listener + mCardPreference.setOnConfirmListener(() -> { + if (mOnAnomalyConfirmListener != null) { + mOnAnomalyConfirmListener.onAnomalyConfirm(); + } else if (!TextUtils.isEmpty(destinationClassName)) { + // Navigate to sub setting page + Bundle arguments = Bundle.EMPTY; + if (!TextUtils.isEmpty(preferenceHighlightKey)) { + arguments = new Bundle(1); + arguments.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, + preferenceHighlightKey); + } + new SubSettingLauncher(mContext) + .setDestination(destinationClassName) + .setSourceMetricsCategory(sourceMetricsCategory) + .setArguments(arguments) + .launch(); + } + mMetricsFeatureProvider.action( + mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, eventId); + }); + mCardPreference.setOnRejectListener(() -> { + if (mOnAnomalyRejectListener != null) { + mOnAnomalyRejectListener.onAnomalyReject(); + } + // For anomaly events with same record key, dismissed until next time full charged. + final String dismissRecordKey = getDismissRecordKey(powerAnomalyEvent); + if (!TextUtils.isEmpty(dismissRecordKey)) { + DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, dismissRecordKey); + } + mMetricsFeatureProvider.action( + mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, eventId); + }); + + mCardPreference.setVisible(true); + mMetricsFeatureProvider.action( + mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, eventId); } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java index ccfc1e2e080..4e8e396528e 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.Looper; import android.provider.SearchIndexableResource; import android.util.Log; +import android.util.Pair; import androidx.annotation.VisibleForTesting; import androidx.loader.app.LoaderManager; @@ -67,11 +68,6 @@ public class PowerUsageAdvanced extends PowerUsageBase { private boolean mIsChartDataLoaded = false; private long mResumeTimestamp; - private BatteryTipsController mBatteryTipsController; - private BatteryChartPreferenceController mBatteryChartPreferenceController; - private ScreenOnTimeController mScreenOnTimeController; - private BatteryUsageBreakdownController mBatteryUsageBreakdownController; - private Optional mBatteryLevelData; private Map> mBatteryUsageMap; private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); @@ -87,6 +83,19 @@ public class PowerUsageAdvanced extends PowerUsageBase { } }; + @VisibleForTesting + BatteryTipsController mBatteryTipsController; + @VisibleForTesting + BatteryChartPreferenceController mBatteryChartPreferenceController; + @VisibleForTesting + ScreenOnTimeController mScreenOnTimeController; + @VisibleForTesting + BatteryUsageBreakdownController mBatteryUsageBreakdownController; + @VisibleForTesting + PowerAnomalyEvent mPowerAnomalyEvent; + @VisibleForTesting + Optional mBatteryLevelData; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -179,6 +188,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { mIsChartDataLoaded = true; mBatteryLevelData = null; mBatteryUsageMap = null; + mPowerAnomalyEvent = null; restartLoader(LoaderIndex.BATTERY_LEVEL_DATA_LOADER, bundle, mBatteryLevelDataLoaderCallbacks); } @@ -254,12 +264,45 @@ public class PowerUsageAdvanced extends PowerUsageBase { Log.d(TAG, "anomalyEventList = " + anomalyEventList); final PowerAnomalyEvent displayEvent = getHighestScoreAnomalyEvent(getContext(), anomalyEventList); - if (displayEvent == null) { + onDisplayAnomalyEventUpdated(displayEvent); + } + + @VisibleForTesting + void onDisplayAnomalyEventUpdated(PowerAnomalyEvent event) { + mPowerAnomalyEvent = event; + if (mBatteryTipsController == null + || mBatteryChartPreferenceController == null + || mBatteryUsageBreakdownController == null) { return; } - if (mBatteryTipsController != null) { - mBatteryTipsController.handleBatteryTipsCardUpdated(displayEvent); + + // Update battery tips card preference & behaviour + mBatteryTipsController.setOnAnomalyConfirmListener(null); + mBatteryTipsController.setOnAnomalyRejectListener(null); + mBatteryTipsController.handleBatteryTipsCardUpdated(mPowerAnomalyEvent); + + // Update highlight slot effect in battery chart view + Pair highlightSlotIndexPair = Pair.create( + BatteryChartViewModel.SELECTED_INDEX_INVALID, + BatteryChartViewModel.SELECTED_INDEX_INVALID); + if (mPowerAnomalyEvent != null && mPowerAnomalyEvent.hasWarningItemInfo()) { + final WarningItemInfo warningItemInfo = mPowerAnomalyEvent.getWarningItemInfo(); + final Long startTimestamp = warningItemInfo.hasStartTimestamp() + ? warningItemInfo.getStartTimestamp() : null; + final Long endTimestamp = warningItemInfo.hasEndTimestamp() + ? warningItemInfo.getEndTimestamp() : null; + if (startTimestamp != null && endTimestamp != null) { + highlightSlotIndexPair = mBatteryLevelData.map(levelData -> + levelData.getIndexByTimestamps(startTimestamp, endTimestamp)) + .orElse(highlightSlotIndexPair); + mBatteryTipsController.setOnAnomalyConfirmListener( + mBatteryChartPreferenceController::selectHighlightSlotIndex); + mBatteryTipsController.setOnAnomalyRejectListener( + () -> onDisplayAnomalyEventUpdated(null)); + } } + mBatteryChartPreferenceController.onHighlightSlotIndexUpdate( + highlightSlotIndexPair.first, highlightSlotIndexPair.second); } private void setBatteryChartPreferenceController() { @@ -307,8 +350,8 @@ public class PowerUsageAdvanced extends PowerUsageBase { final PowerAnomalyEvent highestScoreEvent = anomalyEventList.getPowerAnomalyEventsList() .stream() - .filter(event -> event.hasKey() - && !dismissedPowerAnomalyKeys.contains(event.getKey().name())) + .filter(event -> !dismissedPowerAnomalyKeys.contains( + BatteryTipsController.getDismissRecordKey(event))) .max(Comparator.comparing(PowerAnomalyEvent::getScore)) .orElse(null); Log.d(TAG, "highestScoreAnomalyEvent = " + highestScoreEvent); @@ -342,6 +385,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { controllers.add(new BatteryUsageBreakdownController( context, null /* lifecycle */, null /* activity */, null /* fragment */)); + controllers.add(new BatteryTipsController(context)); return controllers; } }; diff --git a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto index 644ab9eba54..99df215f5aa 100644 --- a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto +++ b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto @@ -60,4 +60,6 @@ message WarningItemInfo { optional string description_string = 5; optional string main_button_string = 6; optional string cancel_button_string = 7; + optional string dismiss_record_key = 8; + optional string item_key = 9; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java index 13d60bb6c12..7dc4eabf6f4 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java @@ -18,6 +18,10 @@ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; +import android.util.Pair; + +import com.android.settings.testutils.BatteryTestUtils; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -211,4 +215,29 @@ public class BatteryLevelDataTest { assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getLevels()) .isEqualTo(List.of(100, 98)); } + + @Test + public void getIndexByTimestamps_returnExpectedResult() { + final BatteryLevelData batteryLevelData = + new BatteryLevelData(Map.of( + 1694354400000L, 1, // 2023-09-10 22:00:00 + 1694361600000L, 2, // 2023-09-11 00:00:00 + 1694368800000L, 3)); // 2023-09-11 02:00:00 + final PowerAnomalyEvent event = BatteryTestUtils.createAppAnomalyEvent(); + + assertThat(batteryLevelData.getIndexByTimestamps(0L, 0L)) + .isEqualTo(Pair.create(BatteryChartViewModel.SELECTED_INDEX_INVALID, + BatteryChartViewModel.SELECTED_INDEX_INVALID)); + assertThat(batteryLevelData.getIndexByTimestamps(1694361600000L + 1L, 1694368800000L + 1L)) + .isEqualTo(Pair.create(BatteryChartViewModel.SELECTED_INDEX_INVALID, + BatteryChartViewModel.SELECTED_INDEX_INVALID)); + assertThat(batteryLevelData.getIndexByTimestamps(1694361600000L, 1694368800000L)) + .isEqualTo(Pair.create(1, 0)); + assertThat(batteryLevelData.getIndexByTimestamps(1694361600000L + 1L, 1694368800000L - 1L)) + .isEqualTo(Pair.create(1, 0)); + assertThat(batteryLevelData.getIndexByTimestamps( + event.getWarningItemInfo().getStartTimestamp(), + event.getWarningItemInfo().getEndTimestamp())) + .isEqualTo(Pair.create(1, 0)); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java index ac67dfdf86b..630ff45bf2f 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java @@ -19,7 +19,9 @@ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,16 +47,24 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.Map; +import java.util.Optional; + @RunWith(RobolectricTestRunner.class) public final class BatteryTipsCardPreferenceTest { private Context mContext; private FakeFeatureFactory mFeatureFactory; private BatteryTipsCardPreference mBatteryTipsCardPreference; + private PowerUsageAdvanced mPowerUsageAdvanced; private BatteryTipsController mBatteryTipsController; @Mock private View mFakeView; + @Mock + private BatteryChartPreferenceController mBatteryChartPreferenceController; + @Mock + private BatteryUsageBreakdownController mBatteryUsageBreakdownController; @Before public void setUp() { @@ -64,6 +74,14 @@ public final class BatteryTipsCardPreferenceTest { mBatteryTipsCardPreference = new BatteryTipsCardPreference(mContext, /*attrs=*/ null); mBatteryTipsController = new BatteryTipsController(mContext); mBatteryTipsController.mCardPreference = mBatteryTipsCardPreference; + mPowerUsageAdvanced = new PowerUsageAdvanced(); + mPowerUsageAdvanced.mBatteryTipsController = mBatteryTipsController; + mPowerUsageAdvanced.mBatteryChartPreferenceController = mBatteryChartPreferenceController; + mPowerUsageAdvanced.mBatteryUsageBreakdownController = mBatteryUsageBreakdownController; + mPowerUsageAdvanced.mBatteryLevelData = Optional.of(new BatteryLevelData(Map.of( + 1694354400000L, 1, // 2023-09-10 22:00:00 + 1694361600000L, 2, // 2023-09-11 00:00:00 + 1694368800000L, 3))); // 2023-09-11 02:00:00 } @Test @@ -71,8 +89,9 @@ public final class BatteryTipsCardPreferenceTest { assertThat(mBatteryTipsCardPreference.getLayoutResource()).isEqualTo( R.layout.battery_tips_card); } + @Test - public void onClick_mainBtn_getAdaptiveBrightnessLauncher() { + public void onClick_mainBtnOfSettingsAnomaly_getAdaptiveBrightnessLauncher() { final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); PowerAnomalyEvent adaptiveBrightnessAnomaly = BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent(); @@ -80,10 +99,10 @@ public final class BatteryTipsCardPreferenceTest { when(mFakeView.getId()).thenReturn(R.id.main_button); doNothing().when(mContext).startActivity(captor.capture()); - mBatteryTipsController.handleBatteryTipsCardUpdated(adaptiveBrightnessAnomaly); + mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(adaptiveBrightnessAnomaly); mBatteryTipsCardPreference.onClick(mFakeView); - assertThat(mBatteryTipsCardPreference.isVisible()).isEqualTo(false); + assertThat(mBatteryTipsCardPreference.isVisible()).isFalse(); verify(mContext).startActivity(any(Intent.class)); final Intent intent = captor.getValue(); assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) @@ -96,21 +115,53 @@ public final class BatteryTipsCardPreferenceTest { @Test public void onClick_dismissBtn_cardDismissAndLogged() { - PowerAnomalyEvent screenTimeoutAnomaly = + final PowerAnomalyEvent screenTimeoutAnomaly = BatteryTestUtils.createScreenTimeoutAnomalyEvent(); DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true); when(mFakeView.getId()).thenReturn(R.id.dismiss_button); - mBatteryTipsController.handleBatteryTipsCardUpdated(screenTimeoutAnomaly); + mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(screenTimeoutAnomaly); mBatteryTipsCardPreference.onClick(mFakeView); - assertThat(mBatteryTipsCardPreference.isVisible()).isEqualTo(false); - assertThat(DatabaseUtils.getDismissedPowerAnomalyKeys(mContext).size()) - .isEqualTo(1); + assertThat(mBatteryTipsCardPreference.isVisible()).isFalse(); + assertThat(DatabaseUtils.getDismissedPowerAnomalyKeys(mContext)).hasSize(1); assertThat(DatabaseUtils.getDismissedPowerAnomalyKeys(mContext)) .contains(PowerAnomalyKey.KEY_SCREEN_TIMEOUT.name()); verify(mFeatureFactory.metricsFeatureProvider).action( mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, "ScreenTimeoutAnomaly"); } + + @Test + public void onClick_mainBtnOfAppsAnomaly_selectHighlightSlot() { + final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent(); + when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true); + when(mFakeView.getId()).thenReturn(R.id.main_button); + + mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly); + mBatteryTipsCardPreference.onClick(mFakeView); + + assertThat(mBatteryTipsCardPreference.isVisible()).isFalse(); + verify(mContext, never()).startActivity(any(Intent.class)); + verify(mBatteryChartPreferenceController).selectHighlightSlotIndex(); + verify(mFeatureFactory.metricsFeatureProvider).action( + mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, "AppAnomaly"); + } + + @Test + public void onClick_dismissBtnOfAppsAnomaly_removeHighlightSlotIndex() { + final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent(); + when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true); + when(mFakeView.getId()).thenReturn(R.id.dismiss_button); + + mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly); + mBatteryTipsCardPreference.onClick(mFakeView); + + assertThat(mBatteryTipsCardPreference.isVisible()).isFalse(); + verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate( + eq(BatteryChartViewModel.SELECTED_INDEX_INVALID), + eq(BatteryChartViewModel.SELECTED_INDEX_INVALID)); + verify(mFeatureFactory.metricsFeatureProvider).action( + mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, "AppAnomaly"); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java index ac9de1fa336..913c00a1fb6 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java @@ -16,6 +16,8 @@ package com.android.settings.fuelgauge.batteryusage; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -73,6 +75,19 @@ public final class BatteryTipsControllerTest { verify(mBatteryTipsCardPreference).setVisible(false); } + @Test + public void getDismissRecordKey_returnExpectedResult() { + assertThat(BatteryTipsController.getDismissRecordKey( + BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent())) + .isEqualTo("KEY_BRIGHTNESS"); + assertThat(BatteryTipsController.getDismissRecordKey( + BatteryTestUtils.createScreenTimeoutAnomalyEvent())) + .isEqualTo("KEY_SCREEN_TIMEOUT"); + assertThat(BatteryTipsController.getDismissRecordKey( + BatteryTestUtils.createAppAnomalyEvent())) + .isEqualTo("KEY_APP_1"); + } + @Test public void handleBatteryTipsCardUpdated_adaptiveBrightnessAnomaly_showAnomaly() { PowerAnomalyEvent event = BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent(); @@ -80,7 +95,6 @@ public final class BatteryTipsControllerTest { mBatteryTipsController.handleBatteryTipsCardUpdated(event); - verify(mBatteryTipsCardPreference).setAnomalyEventId("BrightnessAnomaly"); // Check pre-defined string verify(mBatteryTipsCardPreference).setTitle( "Turn on adaptive brightness to extend battery life"); @@ -90,9 +104,6 @@ public final class BatteryTipsControllerTest { verify(mBatteryTipsCardPreference).setMainButtonLabel("View Settings"); verify(mBatteryTipsCardPreference).setDismissButtonLabel("Got it"); // Check proto info - verify(mBatteryTipsCardPreference).setMainButtonLauncherInfo( - "com.android.settings.DisplaySettings", - 46, "auto_brightness_entry"); verify(mBatteryTipsCardPreference).setVisible(true); verify(mFeatureFactory.metricsFeatureProvider).action( mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "BrightnessAnomaly"); @@ -105,16 +116,12 @@ public final class BatteryTipsControllerTest { mBatteryTipsController.handleBatteryTipsCardUpdated(event); - verify(mBatteryTipsCardPreference).setAnomalyEventId("ScreenTimeoutAnomaly"); verify(mBatteryTipsCardPreference).setTitle("Reduce screen timeout to extend battery life"); verify(mBatteryTipsCardPreference).setIconResourceId(R.drawable.ic_battery_tips_lightbulb); verify(mBatteryTipsCardPreference).setMainButtonStrokeColorResourceId( R.color.color_accent_selector); verify(mBatteryTipsCardPreference).setMainButtonLabel("View Settings"); verify(mBatteryTipsCardPreference).setDismissButtonLabel("Got it"); - verify(mBatteryTipsCardPreference).setMainButtonLauncherInfo( - "com.android.settings.display.ScreenTimeoutSettings", - 1852, "60000"); verify(mBatteryTipsCardPreference).setVisible(true); verify(mFeatureFactory.metricsFeatureProvider).action( mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "ScreenTimeoutAnomaly"); @@ -134,16 +141,12 @@ public final class BatteryTipsControllerTest { mBatteryTipsController.handleBatteryTipsCardUpdated(event); - verify(mBatteryTipsCardPreference).setAnomalyEventId("ScreenTimeoutAnomaly"); verify(mBatteryTipsCardPreference).setTitle(testTitle); verify(mBatteryTipsCardPreference).setIconResourceId(R.drawable.ic_battery_tips_lightbulb); verify(mBatteryTipsCardPreference).setMainButtonStrokeColorResourceId( R.color.color_accent_selector); verify(mBatteryTipsCardPreference).setMainButtonLabel("View Settings"); verify(mBatteryTipsCardPreference).setDismissButtonLabel("Got it"); - verify(mBatteryTipsCardPreference).setMainButtonLauncherInfo( - "com.android.settings.display.ScreenTimeoutSettings", - 1852, "60000"); verify(mBatteryTipsCardPreference).setVisible(true); verify(mFeatureFactory.metricsFeatureProvider).action( mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "ScreenTimeoutAnomaly"); @@ -156,7 +159,6 @@ public final class BatteryTipsControllerTest { mBatteryTipsController.handleBatteryTipsCardUpdated(event); - verify(mBatteryTipsCardPreference).setAnomalyEventId("AppAnomaly"); verify(mBatteryTipsCardPreference).setTitle( "Chrome used more battery than usual in foreground"); verify(mBatteryTipsCardPreference).setIconResourceId( @@ -165,8 +167,6 @@ public final class BatteryTipsControllerTest { R.color.color_battery_anomaly_yellow_selector); verify(mBatteryTipsCardPreference).setMainButtonLabel("Check"); verify(mBatteryTipsCardPreference).setDismissButtonLabel("Got it"); - verify(mBatteryTipsCardPreference).setMainButtonLauncherInfo( - null, null, null); verify(mBatteryTipsCardPreference).setVisible(true); verify(mFeatureFactory.metricsFeatureProvider).action( mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "AppAnomaly"); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java index ee2a8b2c9a3..953c2d4ef39 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java @@ -17,9 +17,15 @@ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.content.Context; +import android.util.Pair; import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.shadow.ShadowDashboardFragment; @@ -27,19 +33,47 @@ import com.android.settings.testutils.shadow.ShadowDashboardFragment; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.Map; +import java.util.Optional; +import java.util.TimeZone; + @RunWith(RobolectricTestRunner.class) @Config(shadows = ShadowDashboardFragment.class) public final class PowerUsageAdvancedTest { private Context mContext; + private PowerUsageAdvanced mPowerUsageAdvanced; + + @Mock + private BatteryTipsController mBatteryTipsController; + @Mock + private BatteryChartPreferenceController mBatteryChartPreferenceController; + @Mock + private ScreenOnTimeController mScreenOnTimeController; + @Mock + private BatteryUsageBreakdownController mBatteryUsageBreakdownController; @Before public void setUp() { + MockitoAnnotations.initMocks(this); + TimeZone.setDefault(TimeZone.getTimeZone("GMT+8")); mContext = spy(RuntimeEnvironment.application); + + mPowerUsageAdvanced = new PowerUsageAdvanced(); + mPowerUsageAdvanced.mBatteryTipsController = mBatteryTipsController; + mPowerUsageAdvanced.mBatteryChartPreferenceController = mBatteryChartPreferenceController; + mPowerUsageAdvanced.mScreenOnTimeController = mScreenOnTimeController; + mPowerUsageAdvanced.mBatteryUsageBreakdownController = mBatteryUsageBreakdownController; + mPowerUsageAdvanced.mBatteryLevelData = Optional.of(new BatteryLevelData(Map.of( + 1694354400000L, 1, // 2023-09-10 22:00:00 + 1694361600000L, 2, // 2023-09-11 00:00:00 + 1694368800000L, 3))); // 2023-09-11 02:00:00 } @Test @@ -87,6 +121,65 @@ public final class PowerUsageAdvancedTest { final PowerAnomalyEvent highestScoreEvent = PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList); - assertThat(highestScoreEvent).isEqualTo(null); + assertThat(highestScoreEvent).isNull(); + } + + @Test + public void onDisplayAnomalyEventUpdated_withSettingsAnomalyEvent_skipHighlightSlotEffect() { + final PowerAnomalyEvent event = BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent(); + + mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event); + + assertThat(mPowerUsageAdvanced.mPowerAnomalyEvent).isEqualTo(event); + verify(mBatteryTipsController).handleBatteryTipsCardUpdated(eq(event)); + verify(mPowerUsageAdvanced.mBatteryTipsController).setOnAnomalyConfirmListener(isNull()); + verify(mPowerUsageAdvanced.mBatteryTipsController).setOnAnomalyRejectListener(isNull()); + verify(mPowerUsageAdvanced.mBatteryChartPreferenceController).onHighlightSlotIndexUpdate( + eq(BatteryChartViewModel.SELECTED_INDEX_INVALID), + eq(BatteryChartViewModel.SELECTED_INDEX_INVALID)); + } + + @Test + public void onDisplayAnomalyEventUpdated_withAppAnomalyEvent_setHighlightSlotEffect() { + final PowerAnomalyEvent event = BatteryTestUtils.createAppAnomalyEvent(); + + mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event); + + assertThat(mPowerUsageAdvanced.mPowerAnomalyEvent).isEqualTo(event); + verify(mBatteryTipsController).handleBatteryTipsCardUpdated(eq(event)); + verify(mBatteryTipsController).setOnAnomalyConfirmListener(isNull()); + verify(mBatteryTipsController).setOnAnomalyRejectListener(isNull()); + + assertThat(event.getWarningItemInfo().hasStartTimestamp()).isTrue(); + assertThat(event.getWarningItemInfo().hasEndTimestamp()).isTrue(); + assertThat(mPowerUsageAdvanced.mBatteryLevelData.get().getIndexByTimestamps( + event.getWarningItemInfo().getStartTimestamp(), + event.getWarningItemInfo().getEndTimestamp() + )).isEqualTo(Pair.create(1, 0)); + verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(eq(1), eq(0)); + verify(mBatteryTipsController).setOnAnomalyConfirmListener(notNull()); + verify(mBatteryTipsController).setOnAnomalyRejectListener(notNull()); + } + + @Test + public void onDisplayAnomalyEventUpdated_withNull_removeHighlightSlotEffect() { + final PowerAnomalyEvent event = BatteryTestUtils.createAppAnomalyEvent(); + + mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event); + mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(null); + + assertThat(mPowerUsageAdvanced.mPowerAnomalyEvent).isNull(); + verify(mBatteryTipsController, times(2)) + .setOnAnomalyConfirmListener(isNull()); + verify(mBatteryTipsController, times(2)) + .setOnAnomalyRejectListener(isNull()); + verify(mBatteryTipsController).setOnAnomalyConfirmListener(notNull()); + verify(mBatteryTipsController).setOnAnomalyRejectListener(notNull()); + + verify(mBatteryChartPreferenceController) + .onHighlightSlotIndexUpdate(eq(1), eq(0)); + verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate( + eq(BatteryChartViewModel.SELECTED_INDEX_INVALID), + eq(BatteryChartViewModel.SELECTED_INDEX_INVALID)); } } diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java index 3297d1ef4de..10355605747 100644 --- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java @@ -283,6 +283,9 @@ public class BatteryTestUtils { .setKey(PowerAnomalyKey.KEY_APP) .setScore(2.0f) .setWarningItemInfo(WarningItemInfo.newBuilder() + .setDismissRecordKey("KEY_APP_1") + .setStartTimestamp(1694361600000L) // 2023-09-11 00:00:00 + .setEndTimestamp(1694368800000L) // 2023-09-11 02:00:00 .setTitleString("Chrome used more battery than usual in foreground") .setMainButtonString("Check") .setCancelButtonString("Got it") From a44e75d6ed31e65fe9d8d1fe371d9395f04b7353 Mon Sep 17 00:00:00 2001 From: Wentao Wang Date: Thu, 7 Sep 2023 02:57:28 +0000 Subject: [PATCH 8/8] Separate the demo user factory reset option with admin user factory reset option. A security vulnerability was discovered by Android security. b/292548775 Within a short period of time after the device reboot, the user could enter the settings page and factory reset the device. Android Enterprise suggests to add DISALLOW_FACTORY_RESET user restriction to the device. However, DISALLOW_FACTORY_RESET will be enabled on all Android users, including both the admin user and the demo user. The existing behavior in Android settings is that once the user restriction is set, factory reset button will be greyed out, which makes the factory reset functionality in demo user go away. In demo user, the factory reset command will be forwarded to the current active device owner. So in this change, we separate the button for admin user and the button for demo user. In demo user, the button is still visible when the restriction is set. And in admin user, the button will be greyed out as expected. Once this change is in, then Pixel Retail Demo could set the user restriction properly and rely on its internal logic to do factory reset. If other applications are trying to do the factory reset, it will be denied by OS. BUG: 292548775 Change-Id: I9d2d47bb29bc2c1e05058b246908768cd2f95990 --- res/xml/reset_dashboard_fragment.xml | 8 ++ src/com/android/settings/MainClear.java | 2 +- ...toryResetDemoUserPreferenceController.java | 32 +++++++ .../FactoryResetPreferenceController.java | 25 ++--- .../system/ResetDashboardFragment.java | 1 - .../system/ResetPreferenceController.java | 2 - ...ResetDemoUserPreferenceControllerTest.java | 96 +++++++++++++++++++ .../FactoryResetPreferenceControllerTest.java | 4 +- 8 files changed, 147 insertions(+), 23 deletions(-) create mode 100644 src/com/android/settings/system/FactoryResetDemoUserPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/system/FactoryResetDemoUserPreferenceControllerTest.java diff --git a/res/xml/reset_dashboard_fragment.xml b/res/xml/reset_dashboard_fragment.xml index 3bd7a136760..08852c92c86 100644 --- a/res/xml/reset_dashboard_fragment.xml +++ b/res/xml/reset_dashboard_fragment.xml @@ -57,5 +57,13 @@ settings:keywords="@string/keywords_factory_data_reset" settings:userRestriction="no_factory_reset" settings:useAdminDisabledSummary="true" + settings:controller="com.android.settings.system.FactoryResetPreferenceController" + android:fragment="com.android.settings.MainClear" /> + + diff --git a/src/com/android/settings/MainClear.java b/src/com/android/settings/MainClear.java index f706c785401..8a441e2c2ce 100644 --- a/src/com/android/settings/MainClear.java +++ b/src/com/android/settings/MainClear.java @@ -569,7 +569,7 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis UserHandle.myUserId()); if (disallow && !Utils.isDemoUser(context)) { return inflater.inflate(R.layout.main_clear_disallowed_screen, null); - } else if (admin != null) { + } else if (admin != null && !Utils.isDemoUser(context)) { new ActionDisabledByAdminDialogHelper(getActivity()) .prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin) .setOnDismissListener(__ -> getActivity().finish()) diff --git a/src/com/android/settings/system/FactoryResetDemoUserPreferenceController.java b/src/com/android/settings/system/FactoryResetDemoUserPreferenceController.java new file mode 100644 index 00000000000..f6a9b3198a3 --- /dev/null +++ b/src/com/android/settings/system/FactoryResetDemoUserPreferenceController.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.settings.system; + +import android.content.Context; +import com.android.settings.Utils; + +public class FactoryResetDemoUserPreferenceController extends FactoryResetPreferenceController { + + public FactoryResetDemoUserPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** Hide demo user specific "Factory reset" settings for non demo users. */ + @Override + public int getAvailabilityStatus() { + return Utils.isDemoUser(mContext) ? AVAILABLE : DISABLED_FOR_USER; + } +} diff --git a/src/com/android/settings/system/FactoryResetPreferenceController.java b/src/com/android/settings/system/FactoryResetPreferenceController.java index a307171d122..6e010c1fbc2 100644 --- a/src/com/android/settings/system/FactoryResetPreferenceController.java +++ b/src/com/android/settings/system/FactoryResetPreferenceController.java @@ -24,35 +24,26 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.Utils; -import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settings.core.BasePreferenceController; -public class FactoryResetPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin { - /** Key of the "Factory reset" preference in {@link R.xml.reset_dashboard_fragment}. */ - private static final String KEY_FACTORY_RESET = "factory_reset"; +public class FactoryResetPreferenceController extends BasePreferenceController { private final UserManager mUm; - public FactoryResetPreferenceController(Context context) { - super(context); + public FactoryResetPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); } - /** Hide "Factory reset" settings for secondary users, except demo users. */ + /** Hide "Factory reset" settings for secondary users. */ @Override - public boolean isAvailable() { - return mUm.isAdminUser() || Utils.isDemoUser(mContext); - } - - @Override - public String getPreferenceKey() { - return KEY_FACTORY_RESET; + public int getAvailabilityStatus() { + return mUm.isAdminUser() ? AVAILABLE : DISABLED_FOR_USER; } @Override public boolean handlePreferenceTreeClick(Preference preference) { - if (KEY_FACTORY_RESET.equals(preference.getKey())) { + if (mPreferenceKey.equals(preference.getKey())) { final Intent intent = new Intent(mContext, Settings.FactoryResetActivity.class); mContext.startActivity(intent); return true; diff --git a/src/com/android/settings/system/ResetDashboardFragment.java b/src/com/android/settings/system/ResetDashboardFragment.java index aea92aafc90..662edc53b20 100644 --- a/src/com/android/settings/system/ResetDashboardFragment.java +++ b/src/com/android/settings/system/ResetDashboardFragment.java @@ -78,7 +78,6 @@ public class ResetDashboardFragment extends DashboardFragment { if (SubscriptionUtil.isSimHardwareVisible(context)) { controllers.add(new NetworkResetPreferenceController(context)); } - controllers.add(new FactoryResetPreferenceController(context)); controllers.add(new ResetAppPrefPreferenceController(context, lifecycle)); return controllers; } diff --git a/src/com/android/settings/system/ResetPreferenceController.java b/src/com/android/settings/system/ResetPreferenceController.java index 0740ac9dae0..35f1ff7a9f2 100644 --- a/src/com/android/settings/system/ResetPreferenceController.java +++ b/src/com/android/settings/system/ResetPreferenceController.java @@ -26,13 +26,11 @@ public class ResetPreferenceController extends BasePreferenceController { private final UserManager mUm; private final NetworkResetPreferenceController mNetworkReset; - private final FactoryResetPreferenceController mFactpruReset; public ResetPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); mNetworkReset = new NetworkResetPreferenceController(context); - mFactpruReset = new FactoryResetPreferenceController(context); } @Override diff --git a/tests/robotests/src/com/android/settings/system/FactoryResetDemoUserPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/system/FactoryResetDemoUserPreferenceControllerTest.java new file mode 100644 index 00000000000..0c92b057f92 --- /dev/null +++ b/tests/robotests/src/com/android/settings/system/FactoryResetDemoUserPreferenceControllerTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.settings.system; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settings.testutils.shadow.ShadowUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowUserManager.class) +public class FactoryResetDemoUserPreferenceControllerTest { + + private static final String FACTORY_RESET_DEMO_USER_KEY = "factory_reset_demo_user"; + + private ShadowUserManager mShadowUserManager; + + private Context mContext; + private FactoryResetDemoUserPreferenceController mController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mShadowUserManager = ShadowUserManager.getShadow(); + + mController = new FactoryResetDemoUserPreferenceController( + mContext, FACTORY_RESET_DEMO_USER_KEY); + } + + @After + public void tearDown() { + ShadowUtils.reset(); + mShadowUserManager.setIsAdminUser(false); + mShadowUserManager.setIsDemoUser(false); + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0); + } + + @Test + public void isAvailable_systemUser() { + mShadowUserManager.setIsAdminUser(true); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_nonSystemUser() { + mShadowUserManager.setIsAdminUser(false); + mShadowUserManager.setIsDemoUser(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_demoUser() { + mShadowUserManager.setIsAdminUser(false); + + // Place the device in demo mode. + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1); + + // Indicate the user is a demo user. + mShadowUserManager.addUser(UserHandle.myUserId(), "test", UserInfo.FLAG_DEMO); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void getPreferenceKey() { + assertThat(mController.getPreferenceKey()).isEqualTo(FACTORY_RESET_DEMO_USER_KEY); + } +} diff --git a/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java index f2a932ee0fe..6e6fad83a27 100644 --- a/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java @@ -49,7 +49,7 @@ public class FactoryResetPreferenceControllerTest { mContext = RuntimeEnvironment.application; mShadowUserManager = ShadowUserManager.getShadow(); - mController = new FactoryResetPreferenceController(mContext); + mController = new FactoryResetPreferenceController(mContext, FACTORY_RESET_KEY); } @After @@ -85,7 +85,7 @@ public class FactoryResetPreferenceControllerTest { // Indicate the user is a demo user. mShadowUserManager.addUser(UserHandle.myUserId(), "test", UserInfo.FLAG_DEMO); - assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.isAvailable()).isFalse(); } @Test