App anomaly tips on PowerUsage App list
Screenshots:
[in bg - banner] https://screenshot.googleplex.com/MzLC6LfX93TkkYf
[in bg - hints] https://screenshot.googleplex.com/9JLXNsRiVG8arAU
[in fg - banner] https://screenshot.googleplex.com/9oYbwUkeeLbQX2t
[in fg - hints] https://screenshot.googleplex.com/53DTTUCUnf8rsoE
[apps anomaly highlight hint + settings anomaly banner]
https://screenshot.googleplex.com/8NdS2VMrSzwv2DM
Bug: 291689643
Bug: 291689623
Bug: 284893240
Test: manual
Change-Id: Ic02db49cb3794ef134759d9dcec5f5ef32454a95
Merged-In: Ic02db49cb3794ef134759d9dcec5f5ef32454a95
Merged-In: I7015cdf5a96d518febb160934d780ae84fe14427
diff --git a/res/drawable/battery_hints_chip_bg.xml b/res/drawable/battery_hints_chip_bg.xml
new file mode 100644
index 0000000..e7d1d0f
--- /dev/null
+++ b/res/drawable/battery_hints_chip_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/settingslib_dialog_background" />
+ <corners android:radius="@dimen/battery_hints_chip_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/battery_hints_chip_bg_ripple.xml b/res/drawable/battery_hints_chip_bg_ripple.xml
new file mode 100644
index 0000000..a8bd0b37
--- /dev/null
+++ b/res/drawable/battery_hints_chip_bg_ripple.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:drawable="@drawable/battery_hints_chip_bg"/>
+</ripple>
\ No newline at end of file
diff --git a/res/layout/anomaly_app_item_preference.xml b/res/layout/anomaly_app_item_preference.xml
new file mode 100644
index 0000000..0a19849
--- /dev/null
+++ b/res/layout/anomaly_app_item_preference.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <include layout="@layout/preference_app"/>
+
+ <LinearLayout
+ android:id="@+id/warning_chip"
+ android:visibility="gone"
+ android:clickable="false"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <Space
+ android:layout_width="@dimen/secondary_app_icon_size"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="8dp"
+ android:layout_marginStart="16dp"
+ android:background="@drawable/battery_hints_chip_bg_ripple">
+
+ <ImageView
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_gravity="center_vertical|start"
+ android:contentDescription="@string/battery_hints_warning_icon_a11y"
+ android:src="@drawable/ic_battery_tips_warning_icon" />
+
+ <TextView
+ android:id="@+id/warning_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="8dp"
+ android:layout_gravity="center_vertical|start"
+ android:textAlignment="viewStart"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"/>
+ </LinearLayout>
+
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 78e7ca4..1ab9876 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1420,20 +1420,49 @@
<item>color_battery_anomaly_yellow_selector</item>
</string-array>
- <!-- The following 3 arrays are for power anomaly tips card. Please keep them the same size. -->
- <string-array name="power_anomaly_titles">
- <item>Turn on adaptive brightness to extend battery life</item>
- <item>Reduce screen timeout to extend battery life</item>
+ <!-- The following 4 arrays are for power anomaly tips card. Please keep them the same size. -->
+ <string-array name="power_anomaly_title_ids" translatable="false">
+ <item>battery_tips_settings_summary_brightness</item>
+ <item>battery_tips_settings_summary_screen_timeout</item>
+ <item>battery_tips_apps_summary_always_high</item>
+ <item>battery_tips_apps_summary_higher_than_usual</item>
+ <item>battery_tips_apps_summary_always_high_in_background</item>
+ <item>battery_tips_apps_summary_higher_than_usual_in_background</item>
+ <item>battery_tips_apps_summary_always_high_in_foreground</item>
+ <item>battery_tips_apps_summary_higher_than_usual_in_foreground</item>
</string-array>
<string-array name="power_anomaly_main_btn_strings" translatable="false">
<item>@string/battery_tips_card_action_button</item>
<item>@string/battery_tips_card_action_button</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
</string-array>
<string-array name="power_anomaly_dismiss_btn_strings" translatable="false">
<item>@string/battery_tips_card_dismiss_button</item>
<item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ </string-array>
+
+ <string-array name="power_anomaly_hint_messages" translatable="false">
+ <item></item>
+ <item></item>
+ <item>@string/battery_app_item_hint</item>
+ <item>@string/battery_app_item_hint</item>
+ <item>@string/battery_app_item_hint_in_bg</item>
+ <item>@string/battery_app_item_hint_in_bg</item>
+ <item>@string/battery_app_item_hint_in_fg</item>
+ <item>@string/battery_app_item_hint_in_fg</item>
</string-array>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4ae140e..9703124 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -382,6 +382,7 @@
<!-- Battery tips card view component -->
<dimen name="battery_tips_card_corner_radius_small">4dp</dimen>
<dimen name="battery_tips_card_corner_radius_normal">24dp</dimen>
+ <dimen name="battery_hints_chip_corner_radius">8dp</dimen>
<!-- Dimensions for Dream settings cards -->
<dimen name="dream_item_min_column_width">174dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 81c6792..1961776 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9688,12 +9688,51 @@
<!-- Label of action button in battery tips card [CHAR LIMIT=50] -->
<string name="battery_tips_card_action_button">View Settings</string>
+ <!-- Label of action button in battery tips card [CHAR LIMIT=50] -->
+ <string name="battery_tips_card_action_button_check">Check</string>
+
<!-- Label of dismiss button in battery tips card [CHAR LIMIT=50] -->
<string name="battery_tips_card_dismiss_button">Got it</string>
<!-- Feedback card message in battery tips card [CHAR LIMIT=NONE] -->
<string name="battery_tips_card_feedback_info">Is this message helpful?</string>
+ <!-- Content description for battery hints warning icon of app anomaly [CHAR LIMIT=NONE] -->
+ <string name="battery_hints_warning_icon_a11y">Battery tips warning icon</string>
+
+ <!-- Summary of settings anomaly for adaptive brightness [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_settings_summary_brightness">Turn on adaptive brightness to extend battery life</string>
+
+ <!-- Summary of settings anomaly for screen timeout [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_settings_summary_screen_timeout">Reduce screen timeout to extend battery life</string>
+
+ <!-- Summary of apps anomaly for always high [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_always_high"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery</string>
+
+ <!-- Summary of apps anomaly for higher than usual [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_higher_than_usual"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery than usual</string>
+
+ <!-- Summary of apps anomaly for always high in background [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_always_high_in_background"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery while in the background</string>
+
+ <!-- Summary of apps anomaly for higher than usual in background [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_higher_than_usual_in_background"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery than usual while in the background</string>
+
+ <!-- Summary of apps anomaly for always high in foreground [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_always_high_in_foreground"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery while in the foreground</string>
+
+ <!-- Summary of apps anomaly for higher than usual in foreground [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_higher_than_usual_in_foreground"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery than usual while in the foreground</string>
+
+ <!-- Label of hint for apps anomaly in battery usage [CHAR LIMIT=NONE] -->
+ <string name="battery_app_item_hint">High battery usage</string>
+
+ <!-- Label of hint for apps background anomaly in battery usage [CHAR LIMIT=NONE] -->
+ <string name="battery_app_item_hint_in_bg">High battery usage in the background</string>
+
+ <!-- Label of hint for apps foreground anomaly in battery usage [CHAR LIMIT=NONE] -->
+ <string name="battery_app_item_hint_in_fg">High battery usage in the foreground</string>
+
<!-- Filter title for battery unrestricted[CHAR_LIMIT=50]-->
<string name="filter_battery_unrestricted_title">Unrestricted</string>
diff --git a/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java b/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java
new file mode 100644
index 0000000..2f139ec
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 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 android.content.Context;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+class AnomalyAppItemPreference extends PowerGaugePreference {
+
+ private static final String TAG = "AnomalyAppItemPreference";
+
+ private CharSequence mAnomalyHintText;
+
+ AnomalyAppItemPreference(Context context) {
+ super(context, /* attrs */ null);
+ setLayoutResource(R.layout.anomaly_app_item_preference);
+ }
+
+ void setAnomalyHint(CharSequence anomalyHintText) {
+ if (!TextUtils.equals(mAnomalyHintText, anomalyHintText)) {
+ mAnomalyHintText = anomalyHintText;
+ notifyChanged();
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder viewHolder) {
+ super.onBindViewHolder(viewHolder);
+ final LinearLayout warningChipView =
+ (LinearLayout) viewHolder.findViewById(R.id.warning_chip);
+
+ if (!TextUtils.isEmpty(mAnomalyHintText)) {
+ ((TextView) warningChipView.findViewById(R.id.warning_info)).setText(mAnomalyHintText);
+ warningChipView.setVisibility(View.VISIBLE);
+ } else {
+ warningChipView.setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java b/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java
new file mode 100644
index 0000000..d535490
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 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 android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.core.SubSettingLauncher;
+
+import java.util.function.Function;
+
+final class AnomalyEventWrapper {
+ private static final String TAG = "AnomalyEventWrapper";
+
+ private final Context mContext;
+ private final PowerAnomalyEvent mPowerAnomalyEvent;
+
+ private final int mCardStyleId;
+ private final int mResourceIndex;
+
+ private SubSettingLauncher mSubSettingLauncher = null;
+ private Pair<Integer, Integer> mHighlightSlotPair = null;
+ private BatteryDiffEntry mRelatedBatteryDiffEntry = null;
+
+ AnomalyEventWrapper(Context context, PowerAnomalyEvent powerAnomalyEvent) {
+ mContext = context;
+ mPowerAnomalyEvent = powerAnomalyEvent;
+ // Set basic battery tips card info
+ mCardStyleId = mPowerAnomalyEvent.getType().getNumber();
+ mResourceIndex = mPowerAnomalyEvent.getKey().getNumber();
+ }
+
+ private <T> T getInfo(Function<WarningBannerInfo, T> warningBannerInfoSupplier,
+ Function<WarningItemInfo, T> warningItemInfoSupplier) {
+ if (warningBannerInfoSupplier != null && mPowerAnomalyEvent.hasWarningBannerInfo()) {
+ return warningBannerInfoSupplier.apply(mPowerAnomalyEvent.getWarningBannerInfo());
+ } else if (warningItemInfoSupplier != null && mPowerAnomalyEvent.hasWarningItemInfo()) {
+ return warningItemInfoSupplier.apply(mPowerAnomalyEvent.getWarningItemInfo());
+ }
+ return null;
+ }
+
+ private int getResourceId(int resourceId, int resourceIndex, String defType) {
+ final String key = getStringFromArrayResource(resourceId, resourceIndex);
+ return TextUtils.isEmpty(key) ? 0
+ : mContext.getResources().getIdentifier(key, defType, mContext.getPackageName());
+ }
+
+ private String getString(Function<WarningBannerInfo, String> warningBannerInfoSupplier,
+ Function<WarningItemInfo, String> warningItemInfoSupplier,
+ int resourceId, int resourceIndex) {
+ final String string = getInfo(warningBannerInfoSupplier, warningItemInfoSupplier);
+ return (!TextUtils.isEmpty(string) || resourceId <= 0) ? string
+ : getStringFromArrayResource(resourceId, resourceIndex);
+ }
+
+ private String getStringFromArrayResource(int resourceId, int resourceIndex) {
+ if (resourceId <= 0 || resourceIndex < 0) {
+ return null;
+ }
+ final String[] stringArray = mContext.getResources().getStringArray(resourceId);
+ return (resourceIndex >= 0 && resourceIndex < stringArray.length)
+ ? stringArray[resourceIndex] : null;
+ }
+
+ void setRelatedBatteryDiffEntry(BatteryDiffEntry batteryDiffEntry) {
+ mRelatedBatteryDiffEntry = batteryDiffEntry;
+ }
+
+ String getEventId() {
+ return mPowerAnomalyEvent.hasEventId() ? mPowerAnomalyEvent.getEventId() : null;
+ }
+
+ int getIconResId() {
+ return getResourceId(R.array.battery_tips_card_icons, mCardStyleId, "drawable");
+ }
+
+ int getColorResId() {
+ return getResourceId(R.array.battery_tips_card_colors, mCardStyleId, "color");
+ }
+
+ String getTitleString() {
+ final String protoTitleString = getInfo(WarningBannerInfo::getTitleString,
+ WarningItemInfo::getTitleString);
+ if (!TextUtils.isEmpty(protoTitleString)) {
+ return protoTitleString;
+ }
+ final int titleFormatResId = getResourceId(R.array.power_anomaly_title_ids,
+ mResourceIndex, "string");
+ if (mPowerAnomalyEvent.hasWarningBannerInfo()) {
+ return mContext.getString(titleFormatResId);
+ } else if (mPowerAnomalyEvent.hasWarningItemInfo() && mRelatedBatteryDiffEntry != null) {
+ final String appLabel = mRelatedBatteryDiffEntry.getAppLabel();
+ return mContext.getString(titleFormatResId, appLabel);
+ }
+ return null;
+ }
+
+ String getMainBtnString() {
+ return getString(WarningBannerInfo::getMainButtonString,
+ WarningItemInfo::getMainButtonString,
+ R.array.power_anomaly_main_btn_strings, mResourceIndex);
+ }
+
+ String getDismissBtnString() {
+ return getString(WarningBannerInfo::getCancelButtonString,
+ WarningItemInfo::getCancelButtonString,
+ R.array.power_anomaly_dismiss_btn_strings, mResourceIndex);
+ }
+
+ String getAnomalyHintString() {
+ return getStringFromArrayResource(R.array.power_anomaly_hint_messages, mResourceIndex);
+ }
+
+ String getDismissRecordKey() {
+ return mPowerAnomalyEvent.getDismissRecordKey();
+ }
+
+ boolean hasAnomalyEntryKey() {
+ return getAnomalyEntryKey() != null;
+ }
+
+ String getAnomalyEntryKey() {
+ return mPowerAnomalyEvent.hasWarningItemInfo()
+ && mPowerAnomalyEvent.getWarningItemInfo().hasItemKey()
+ ? mPowerAnomalyEvent.getWarningItemInfo().getItemKey() : null;
+ }
+
+ boolean hasSubSettingLauncher() {
+ if (mSubSettingLauncher == null) {
+ mSubSettingLauncher = getSubSettingLauncher();
+ }
+ return mSubSettingLauncher != null;
+ }
+
+ SubSettingLauncher getSubSettingLauncher() {
+ if (mSubSettingLauncher != null) {
+ return mSubSettingLauncher;
+ }
+ final String destinationClassName = getInfo(
+ WarningBannerInfo::getMainButtonDestination, null);
+ if (!TextUtils.isEmpty(destinationClassName)) {
+ final Integer sourceMetricsCategory = getInfo(
+ WarningBannerInfo::getMainButtonSourceMetricsCategory, null);
+ final String preferenceHighlightKey = getInfo(
+ WarningBannerInfo::getMainButtonSourceHighlightKey, null);
+ Bundle arguments = Bundle.EMPTY;
+ if (!TextUtils.isEmpty(preferenceHighlightKey)) {
+ arguments = new Bundle(1);
+ arguments.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
+ preferenceHighlightKey);
+ }
+ mSubSettingLauncher = new SubSettingLauncher(mContext)
+ .setDestination(destinationClassName)
+ .setSourceMetricsCategory(sourceMetricsCategory)
+ .setArguments(arguments);
+ }
+ return mSubSettingLauncher;
+ }
+
+ boolean hasHighlightSlotPair(BatteryLevelData batteryLevelData) {
+ if (mHighlightSlotPair == null) {
+ mHighlightSlotPair = getHighlightSlotPair(batteryLevelData);
+ }
+ return mHighlightSlotPair != null;
+ }
+
+ Pair<Integer, Integer> getHighlightSlotPair(BatteryLevelData batteryLevelData) {
+ if (mHighlightSlotPair != null) {
+ return mHighlightSlotPair;
+ }
+ if (!mPowerAnomalyEvent.hasWarningItemInfo()) {
+ return null;
+ }
+ 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) {
+ mHighlightSlotPair = batteryLevelData
+ .getIndexByTimestamps(startTimestamp, endTimestamp);
+ if (mHighlightSlotPair.first == BatteryChartViewModel.SELECTED_INDEX_INVALID
+ || mHighlightSlotPair.second == BatteryChartViewModel.SELECTED_INDEX_INVALID) {
+ // Drop invalid mHighlightSlotPair index
+ mHighlightSlotPair = null;
+ }
+ }
+ return mHighlightSlotPair;
+ }
+
+ boolean updateTipsCardPreference(BatteryTipsCardPreference preference) {
+ final String titleString = getTitleString();
+ if (TextUtils.isEmpty(titleString)) {
+ return false;
+ }
+ preference.setTitle(titleString);
+ preference.setIconResourceId(getIconResId());
+ preference.setMainButtonStrokeColorResourceId(getColorResId());
+ preference.setMainButtonLabel(getMainBtnString());
+ preference.setDismissButtonLabel(getDismissBtnString());
+ return true;
+ }
+
+ boolean launchSubSetting() {
+ if (!hasSubSettingLauncher()) {
+ return false;
+ }
+ // Navigate to sub setting page
+ mSubSettingLauncher.launch();
+ return true;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 1893096..844241e 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -221,14 +221,20 @@
refreshUi();
}
+ boolean isHighlightSlotFocused() {
+ return (mDailyHighlightSlotIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID
+ && mDailyHighlightSlotIndex == mDailyChartIndex
+ && mHourlyHighlightSlotIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID
+ && mHourlyHighlightSlotIndex == mHourlyChartIndex);
+ }
+
void onHighlightSlotIndexUpdate(int dailyHighlightSlotIndex, int hourlyHighlightSlotIndex) {
- if (mDailyHighlightSlotIndex == dailyHighlightSlotIndex
- && mHourlyHighlightSlotIndex == hourlyHighlightSlotIndex) {
- return;
- }
mDailyHighlightSlotIndex = dailyHighlightSlotIndex;
mHourlyHighlightSlotIndex = hourlyHighlightSlotIndex;
refreshUi();
+ if (mOnSelectedIndexUpdatedListener != null) {
+ mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated();
+ }
}
void selectHighlightSlotIndex() {
@@ -405,7 +411,7 @@
final String slotInformation = getSlotInformation();
return slotInformation == null
? mPrefContext.getString(
- R.string.battery_usage_breakdown_title_since_last_full_charge)
+ R.string.battery_usage_breakdown_title_since_last_full_charge)
: mPrefContext.getString(
R.string.battery_usage_breakdown_title_for_slot, slotInformation);
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
index e98077c..47d2ac3 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
@@ -120,12 +120,10 @@
public void onClick(View view) {
final int viewId = view.getId();
if (viewId == R.id.main_button || viewId == R.id.tips_card) {
- setVisible(false);
if (mOnConfirmListener != null) {
mOnConfirmListener.onConfirm();
}
} else if (viewId == R.id.dismiss_button) {
- setVisible(false);
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 400e70a..39ed0dc 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
@@ -18,21 +18,15 @@
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;
-import java.util.function.Function;
-
/** Controls the update for battery tips card */
public class BatteryTipsController extends BasePreferenceController {
@@ -59,6 +53,10 @@
@VisibleForTesting
BatteryTipsCardPreference mCardPreference;
+ @VisibleForTesting
+ AnomalyEventWrapper mAnomalyEventWrapper = null;
+ @VisibleForTesting
+ Boolean mIsAcceptable = false;
public BatteryTipsController(Context context) {
super(context, ROOT_PREFERENCE_KEY);
@@ -85,132 +83,56 @@
mOnAnomalyRejectListener = listener;
}
- private <T> T getInfo(PowerAnomalyEvent powerAnomalyEvent,
- Function<WarningBannerInfo, T> warningBannerInfoSupplier,
- Function<WarningItemInfo, T> warningItemInfoSupplier) {
- if (warningBannerInfoSupplier != null && powerAnomalyEvent.hasWarningBannerInfo()) {
- return warningBannerInfoSupplier.apply(powerAnomalyEvent.getWarningBannerInfo());
- } else if (warningItemInfoSupplier != null && powerAnomalyEvent.hasWarningItemInfo()) {
- return warningItemInfoSupplier.apply(powerAnomalyEvent.getWarningItemInfo());
+ void acceptTipsCard() {
+ if (mAnomalyEventWrapper == null || !mIsAcceptable) {
+ return;
}
- return null;
- }
-
- private String getStringFromResource(int resourceId, int resourceIndex) {
- if (resourceId < 0) {
- return null;
+ // For anomaly events with same record key, dismissed until next time full charged.
+ final String dismissRecordKey = mAnomalyEventWrapper.getDismissRecordKey();
+ if (!TextUtils.isEmpty(dismissRecordKey)) {
+ DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, dismissRecordKey);
}
- final String[] stringArray = mContext.getResources().getStringArray(resourceId);
- return (resourceIndex >= 0 && resourceIndex < stringArray.length)
- ? stringArray[resourceIndex] : null;
+ mCardPreference.setVisible(false);
+ mMetricsFeatureProvider.action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT,
+ mAnomalyEventWrapper.getEventId());
}
- private int getResourceId(int resourceId, int resourceIndex, String defType) {
- final String key = getStringFromResource(resourceId, resourceIndex);
- return TextUtils.isEmpty(key) ? 0
- : mContext.getResources().getIdentifier(key, defType, mContext.getPackageName());
- }
-
- private String getString(PowerAnomalyEvent powerAnomalyEvent,
- Function<WarningBannerInfo, String> warningBannerInfoSupplier,
- Function<WarningItemInfo, String> warningItemInfoSupplier,
- int resourceId, int resourceIndex) {
- String string =
- getInfo(powerAnomalyEvent, warningBannerInfoSupplier, warningItemInfoSupplier);
- return (!TextUtils.isEmpty(string) || resourceId < 0) ? string
- : 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) {
+ void handleBatteryTipsCardUpdated(
+ AnomalyEventWrapper anomalyEventWrapper, boolean isAcceptable) {
+ mAnomalyEventWrapper = anomalyEventWrapper;
+ mIsAcceptable = isAcceptable;
+ if (mAnomalyEventWrapper == null) {
mCardPreference.setVisible(false);
return;
}
- // Get card icon and color styles
- final int cardStyleId = powerAnomalyEvent.getType().getNumber();
- final int iconResId = getResourceId(
- R.array.battery_tips_card_icons, cardStyleId, "drawable");
- final int colorResId = getResourceId(
- 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;
+ final String eventId = mAnomalyEventWrapper.getEventId();
- final String titleString = getString(powerAnomalyEvent, WarningBannerInfo::getTitleString,
- WarningItemInfo::getTitleString, R.array.power_anomaly_titles, resourceIndex);
- if (titleString.isEmpty()) {
+ // Update card & buttons preference
+ if (!mAnomalyEventWrapper.updateTipsCardPreference(mCardPreference)) {
mCardPreference.setVisible(false);
return;
}
- final String mainBtnString = getString(powerAnomalyEvent,
- WarningBannerInfo::getMainButtonString, WarningItemInfo::getMainButtonString,
- R.array.power_anomaly_main_btn_strings, resourceIndex);
- final String dismissBtnString = getString(powerAnomalyEvent,
- WarningBannerInfo::getCancelButtonString, WarningItemInfo::getCancelButtonString,
- R.array.power_anomaly_dismiss_btn_strings, resourceIndex);
-
- final String destinationClassName = getInfo(powerAnomalyEvent,
- WarningBannerInfo::getMainButtonDestination, null);
- final Integer sourceMetricsCategory = getInfo(powerAnomalyEvent,
- WarningBannerInfo::getMainButtonSourceMetricsCategory, null);
- final String preferenceHighlightKey = getInfo(powerAnomalyEvent,
- WarningBannerInfo::getMainButtonSourceHighlightKey, null);
-
- // Update card preference and main button fragment launcher
- mCardPreference.setTitle(titleString);
- mCardPreference.setIconResourceId(iconResId);
- mCardPreference.setMainButtonStrokeColorResourceId(colorResId);
- mCardPreference.setMainButtonLabel(mainBtnString);
- mCardPreference.setDismissButtonLabel(dismissBtnString);
-
// Set battery tips card listener
mCardPreference.setOnConfirmListener(() -> {
+ mCardPreference.setVisible(false);
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();
+ } else if (mAnomalyEventWrapper.launchSubSetting()) {
+ mMetricsFeatureProvider.action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, eventId);
}
- mMetricsFeatureProvider.action(
- mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, eventId);
});
mCardPreference.setOnRejectListener(() -> {
+ mCardPreference.setVisible(false);
if (mOnAnomalyRejectListener != null) {
mOnAnomalyRejectListener.onAnomalyReject();
}
// For anomaly events with same record key, dismissed until next time full charged.
- final String dismissRecordKey = getDismissRecordKey(powerAnomalyEvent);
+ final String dismissRecordKey = mAnomalyEventWrapper.getDismissRecordKey();
if (!TextUtils.isEmpty(dismissRecordKey)) {
DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, dismissRecordKey);
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
index 8aa31e2..b237ef6 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
@@ -53,6 +53,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
/** Controller for battery usage breakdown preference group. */
@@ -93,6 +94,14 @@
BatteryDiffData mBatteryDiffData;
@VisibleForTesting
String mPercentLessThanThresholdText;
+ @VisibleForTesting
+ boolean mIsHighlightSlot;
+ @VisibleForTesting
+ String mAnomalyEventId;
+ @VisibleForTesting
+ String mAnomalyEntryKey;
+ @VisibleForTesting
+ String mAnomalyHintString;
public BatteryUsageBreakdownController(
Context context, Lifecycle lifecycle, SettingsActivity activity,
@@ -137,6 +146,12 @@
return false;
}
+ private String getActionKey(String packageName) {
+ final String actionKey = TextUtils.isEmpty(packageName)
+ ? PACKAGE_NAME_NONE : packageName;
+ return mAnomalyEventId == null ? actionKey : actionKey + "|" + mAnomalyEventId;
+ }
+
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (!(preference instanceof PowerGaugePreference)) {
@@ -151,7 +166,7 @@
? SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM
: SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM,
/* pageId */ SettingsEnums.OPEN_BATTERY_USAGE,
- TextUtils.isEmpty(packageName) ? PACKAGE_NAME_NONE : packageName,
+ getActionKey(packageName),
(int) Math.round(diffEntry.getPercentage()));
Log.d(TAG, String.format("handleClick() label=%s key=%s package=%s",
diffEntry.getAppLabel(), diffEntry.getKey(), packageName));
@@ -211,9 +226,23 @@
* used when showing the footer.
*/
void handleBatteryUsageUpdated(
- BatteryDiffData slotUsageData, String slotTimestamp, boolean isAllUsageDataEmpty) {
+ BatteryDiffData slotUsageData, String slotTimestamp,
+ boolean isAllUsageDataEmpty, boolean isHighlightSlot,
+ Optional<AnomalyEventWrapper> optionalAnomalyEventWrapper) {
mBatteryDiffData = slotUsageData;
mSlotTimestamp = slotTimestamp;
+ mIsHighlightSlot = isHighlightSlot;
+
+ if (optionalAnomalyEventWrapper != null) {
+ final AnomalyEventWrapper anomalyEventWrapper =
+ optionalAnomalyEventWrapper.orElse(null);
+ mAnomalyEventId = anomalyEventWrapper != null
+ ? anomalyEventWrapper.getEventId() : null;
+ mAnomalyEntryKey = anomalyEventWrapper != null
+ ? anomalyEventWrapper.getAnomalyEntryKey() : null;
+ mAnomalyHintString = anomalyEventWrapper != null
+ ? anomalyEventWrapper.getAnomalyHintString() : null;
+ }
showCategoryTitle(slotTimestamp);
showSpinnerAndAppList();
@@ -278,15 +307,15 @@
continue;
}
final String prefKey = entry.getKey();
- PowerGaugePreference pref = mAppListPreferenceGroup.findPreference(prefKey);
+ AnomalyAppItemPreference pref = mAppListPreferenceGroup.findPreference(prefKey);
if (pref != null) {
isAdded = true;
} else {
- pref = (PowerGaugePreference) mPreferenceCache.get(prefKey);
+ pref = (AnomalyAppItemPreference) mPreferenceCache.get(prefKey);
}
- // Creates new innstance if cached preference is not found.
+ // Creates new instance if cached preference is not found.
if (pref == null) {
- pref = new PowerGaugePreference(mPrefContext);
+ pref = new AnomalyAppItemPreference(mPrefContext);
pref.setKey(prefKey);
mPreferenceCache.put(prefKey, pref);
}
@@ -294,6 +323,10 @@
pref.setTitle(appLabel);
pref.setOrder(prefIndex);
pref.setSingleLineTitle(true);
+ // Updates App item preference style
+ pref.setAnomalyHint(mIsHighlightSlot && mAnomalyEntryKey != null
+ && mAnomalyEntryKey.equals(entry.getKey())
+ ? mAnomalyHintString : null);
// Sets the BatteryDiffEntry to preference for launching detailed page.
pref.setBatteryDiffEntry(entry);
pref.setSelectable(entry.validForRestriction());
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index ece9960..658f104 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -28,6 +28,7 @@
import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
+import com.android.settings.overlay.FeatureFactory;
import java.util.List;
import java.util.Map;
@@ -138,6 +139,8 @@
// No app usage data or battery diff data at this time.
loadAppUsageData(context);
preprocessBatteryUsageSlots(context);
+ FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context)
+ .detectSettingsAnomaly(context, /* displayDrain= */ 0);
}
Log.d(TAG, String.format(
"loadUsageDataSafely() in %d/ms", System.currentTimeMillis() - start));
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
index 4e8e396..fb92a76 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
@@ -52,6 +52,7 @@
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.function.Predicate;
/** Advanced power usage. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
@@ -92,9 +93,9 @@
@VisibleForTesting
BatteryUsageBreakdownController mBatteryUsageBreakdownController;
@VisibleForTesting
- PowerAnomalyEvent mPowerAnomalyEvent;
- @VisibleForTesting
Optional<BatteryLevelData> mBatteryLevelData;
+ @VisibleForTesting
+ Optional<AnomalyEventWrapper> mHighlightEventWrapper;
@Override
public void onCreate(Bundle icicle) {
@@ -188,7 +189,7 @@
mIsChartDataLoaded = true;
mBatteryLevelData = null;
mBatteryUsageMap = null;
- mPowerAnomalyEvent = null;
+ mHighlightEventWrapper = null;
restartLoader(LoaderIndex.BATTERY_LEVEL_DATA_LOADER, bundle,
mBatteryLevelDataLoaderCallbacks);
}
@@ -239,8 +240,13 @@
mScreenOnTimeController.handleSceenOnTimeUpdated(
slotUsageData.getScreenOnTime(), slotInformation);
}
+ // Hide card tips if the related highlight slot was clicked.
+ if (isAppsAnomalyEventFocused()) {
+ mBatteryTipsController.acceptTipsCard();
+ }
mBatteryUsageBreakdownController.handleBatteryUsageUpdated(
- slotUsageData, slotInformation, isBatteryUsageMapNullOrEmpty());
+ slotUsageData, slotInformation, isBatteryUsageMapNullOrEmpty(),
+ isAppsAnomalyEventFocused(), mHighlightEventWrapper);
Log.d(TAG, String.format("Battery usage list shows in %d millis",
System.currentTimeMillis() - mResumeTimestamp));
}
@@ -262,49 +268,95 @@
return;
}
Log.d(TAG, "anomalyEventList = " + anomalyEventList);
- final PowerAnomalyEvent displayEvent =
- getHighestScoreAnomalyEvent(getContext(), anomalyEventList);
- onDisplayAnomalyEventUpdated(displayEvent);
+
+ final Set<String> dismissedPowerAnomalyKeys =
+ DatabaseUtils.getDismissedPowerAnomalyKeys(getContext());
+ Log.d(TAG, "dismissedPowerAnomalyKeys = " + dismissedPowerAnomalyKeys);
+
+ // Choose an app anomaly event with highest score to show highlight slot
+ final PowerAnomalyEvent highlightEvent =
+ getAnomalyEvent(anomalyEventList, PowerAnomalyEvent::hasWarningItemInfo);
+ // Choose an event never dismissed to show as card.
+ // If the slot is already highlighted, the tips card should be the corresponding app
+ // or settings anomaly event.
+ final PowerAnomalyEvent tipsCardEvent =
+ getAnomalyEvent(anomalyEventList,
+ event -> !dismissedPowerAnomalyKeys.contains(event.getDismissRecordKey())
+ && (event.equals(highlightEvent) || !event.hasWarningItemInfo()));
+ onDisplayAnomalyEventUpdated(tipsCardEvent, highlightEvent);
}
@VisibleForTesting
- void onDisplayAnomalyEventUpdated(PowerAnomalyEvent event) {
- mPowerAnomalyEvent = event;
+ void onDisplayAnomalyEventUpdated(
+ PowerAnomalyEvent tipsCardEvent, PowerAnomalyEvent highlightEvent) {
if (mBatteryTipsController == null
|| mBatteryChartPreferenceController == null
|| mBatteryUsageBreakdownController == null) {
return;
}
+ final boolean isSameAnomalyEvent = (tipsCardEvent == highlightEvent);
// Update battery tips card preference & behaviour
mBatteryTipsController.setOnAnomalyConfirmListener(null);
mBatteryTipsController.setOnAnomalyRejectListener(null);
- mBatteryTipsController.handleBatteryTipsCardUpdated(mPowerAnomalyEvent);
+ final AnomalyEventWrapper tipsCardEventWrapper = (tipsCardEvent == null) ? null :
+ new AnomalyEventWrapper(getContext(), tipsCardEvent);
+ if (tipsCardEventWrapper != null) {
+ tipsCardEventWrapper.setRelatedBatteryDiffEntry(
+ findRelatedBatteryDiffEntry(tipsCardEventWrapper));
+ }
+ mBatteryTipsController.handleBatteryTipsCardUpdated(
+ tipsCardEventWrapper, isSameAnomalyEvent);
// Update highlight slot effect in battery chart view
Pair<Integer, Integer> 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));
+ mHighlightEventWrapper = Optional.ofNullable(isSameAnomalyEvent ? tipsCardEventWrapper :
+ ((highlightEvent != null)
+ ? new AnomalyEventWrapper(getContext(), highlightEvent) : null));
+ if (mBatteryLevelData != null && mBatteryLevelData.isPresent()
+ && mHighlightEventWrapper.isPresent()
+ && mHighlightEventWrapper.get().hasHighlightSlotPair(mBatteryLevelData.get())) {
+ highlightSlotIndexPair = mHighlightEventWrapper.get()
+ .getHighlightSlotPair(mBatteryLevelData.get());
+ if (isSameAnomalyEvent) {
+ // For main button, focus on highlight slot when clicked
+ mBatteryTipsController.setOnAnomalyConfirmListener(() -> {
+ mBatteryChartPreferenceController.selectHighlightSlotIndex();
+ mBatteryTipsController.acceptTipsCard();
+ });
}
}
mBatteryChartPreferenceController.onHighlightSlotIndexUpdate(
highlightSlotIndexPair.first, highlightSlotIndexPair.second);
}
+ @VisibleForTesting
+ BatteryDiffEntry findRelatedBatteryDiffEntry(AnomalyEventWrapper eventWrapper) {
+ if (eventWrapper == null
+ || mBatteryLevelData == null || mBatteryLevelData.isEmpty()
+ || !eventWrapper.hasHighlightSlotPair(mBatteryLevelData.get())
+ || !eventWrapper.hasAnomalyEntryKey()
+ || mBatteryUsageMap == null) {
+ return null;
+ }
+ final Pair<Integer, Integer> highlightSlotIndexPair =
+ eventWrapper.getHighlightSlotPair(mBatteryLevelData.get());
+ final BatteryDiffData relatedDiffData = mBatteryUsageMap
+ .get(highlightSlotIndexPair.first).get(highlightSlotIndexPair.second);
+ final String anomalyEntryKey = eventWrapper.getAnomalyEntryKey();
+ if (relatedDiffData == null || anomalyEntryKey == null) {
+ return null;
+ }
+ for (BatteryDiffEntry entry : relatedDiffData.getAppDiffEntryList()) {
+ if (anomalyEntryKey.equals(entry.getKey())) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
private void setBatteryChartPreferenceController() {
if (mHistPref != null && mBatteryChartPreferenceController != null) {
mHistPref.setChartPreferenceController(mBatteryChartPreferenceController);
@@ -319,6 +371,11 @@
&& allBatteryDiffData.getSystemDiffEntryList().isEmpty());
}
+ private boolean isAppsAnomalyEventFocused() {
+ return mBatteryChartPreferenceController != null
+ && mBatteryChartPreferenceController.isHighlightSlotFocused();
+ }
+
private void logScreenUsageTime() {
final BatteryDiffData allBatteryDiffData = getAllBatteryDiffData(mBatteryUsageMap);
if (allBatteryDiffData == null) {
@@ -339,25 +396,22 @@
}
@VisibleForTesting
- static PowerAnomalyEvent getHighestScoreAnomalyEvent(
- Context context, PowerAnomalyEventList anomalyEventList) {
+ static PowerAnomalyEvent getAnomalyEvent(
+ PowerAnomalyEventList anomalyEventList, Predicate<PowerAnomalyEvent> predicate) {
if (anomalyEventList == null || anomalyEventList.getPowerAnomalyEventsCount() == 0) {
return null;
}
- final Set<String> dismissedPowerAnomalyKeys =
- DatabaseUtils.getDismissedPowerAnomalyKeys(context);
- Log.d(TAG, "dismissedPowerAnomalyKeys = " + dismissedPowerAnomalyKeys);
- final PowerAnomalyEvent highestScoreEvent = anomalyEventList.getPowerAnomalyEventsList()
+ final PowerAnomalyEvent filterAnomalyEvent = anomalyEventList.getPowerAnomalyEventsList()
.stream()
- .filter(event -> !dismissedPowerAnomalyKeys.contains(
- BatteryTipsController.getDismissRecordKey(event)))
+ .filter(predicate)
.max(Comparator.comparing(PowerAnomalyEvent::getScore))
.orElse(null);
- Log.d(TAG, "highestScoreAnomalyEvent = " + highestScoreEvent);
- return highestScoreEvent;
+ Log.d(TAG, "filterAnomalyEvent = " + filterAnomalyEvent);
+ return filterAnomalyEvent;
}
+
private static BatteryDiffData getAllBatteryDiffData(
Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
return batteryUsageMap == null ? null : batteryUsageMap
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 99df215..caa9c35 100644
--- a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
+++ b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
@@ -18,6 +18,7 @@
WarningBannerInfo warning_banner_info = 6;
WarningItemInfo warning_item_info = 7;
}
+ optional string dismiss_record_key = 8;
}
// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
@@ -32,11 +33,16 @@
// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
// The enum value will be used to decide pre-defined title and button labels.
//
-// Next id: 3
+// Next id: 8
enum PowerAnomalyKey{
KEY_BRIGHTNESS = 0;
KEY_SCREEN_TIMEOUT = 1;
- KEY_APP = 2;
+ KEY_APP_TOTAL_ALWAYS_HIGH = 2;
+ KEY_APP_TOTAL_HIGHER_THAN_USUAL = 3;
+ KEY_APP_BACKGROUND_ALWAYS_HIGH = 4;
+ KEY_APP_BACKGROUND_HIGHER_THAN_USUAL = 5;
+ KEY_APP_FOREGROUND_ALWAYS_HIGH = 6;
+ KEY_APP_FOREGROUND_HIGHER_THAN_USUAL = 7;
}
message WarningBannerInfo {
@@ -60,6 +66,5 @@
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;
+ optional string item_key = 8;
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapperTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapperTest.java
new file mode 100644
index 0000000..60e0af0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapperTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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 org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.TimeZone;
+
+@RunWith(RobolectricTestRunner.class)
+public class AnomalyEventWrapperTest {
+ private AnomalyEventWrapper mAnomalyEventWrapper;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
+ mContext = spy(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void getDismissRecordKey_returnExpectedResult() {
+ mAnomalyEventWrapper = new AnomalyEventWrapper(mContext,
+ BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent());
+ assertThat(mAnomalyEventWrapper.getDismissRecordKey())
+ .isEqualTo("KEY_BRIGHTNESS");
+
+ mAnomalyEventWrapper = new AnomalyEventWrapper(mContext,
+ BatteryTestUtils.createScreenTimeoutAnomalyEvent());
+ assertThat(mAnomalyEventWrapper.getDismissRecordKey())
+ .isEqualTo("KEY_SCREEN_TIMEOUT");
+
+ mAnomalyEventWrapper = new AnomalyEventWrapper(mContext,
+ BatteryTestUtils.createAppAnomalyEvent());
+ assertThat(mAnomalyEventWrapper.getDismissRecordKey())
+ .isEqualTo("KEY_APP_1");
+ }
+}
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 630ff45..63cb1b3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -58,13 +59,14 @@
private BatteryTipsCardPreference mBatteryTipsCardPreference;
private PowerUsageAdvanced mPowerUsageAdvanced;
private BatteryTipsController mBatteryTipsController;
+ private BatteryChartPreferenceController mBatteryChartPreferenceController;
@Mock
private View mFakeView;
@Mock
- private BatteryChartPreferenceController mBatteryChartPreferenceController;
- @Mock
private BatteryUsageBreakdownController mBatteryUsageBreakdownController;
+ @Mock
+ private BatteryDiffEntry mFakeEntry;
@Before
public void setUp() {
@@ -73,8 +75,13 @@
mFeatureFactory = FakeFeatureFactory.setupForTest();
mBatteryTipsCardPreference = new BatteryTipsCardPreference(mContext, /*attrs=*/ null);
mBatteryTipsController = new BatteryTipsController(mContext);
+ mBatteryChartPreferenceController =
+ spy(new BatteryChartPreferenceController(mContext, null, null));
+ mBatteryChartPreferenceController.mPrefContext = mContext;
mBatteryTipsController.mCardPreference = mBatteryTipsCardPreference;
- mPowerUsageAdvanced = new PowerUsageAdvanced();
+
+ mPowerUsageAdvanced = spy(new PowerUsageAdvanced());
+ doReturn(mContext).when(mPowerUsageAdvanced).getContext();
mPowerUsageAdvanced.mBatteryTipsController = mBatteryTipsController;
mPowerUsageAdvanced.mBatteryChartPreferenceController = mBatteryChartPreferenceController;
mPowerUsageAdvanced.mBatteryUsageBreakdownController = mBatteryUsageBreakdownController;
@@ -82,6 +89,7 @@
1694354400000L, 1, // 2023-09-10 22:00:00
1694361600000L, 2, // 2023-09-11 00:00:00
1694368800000L, 3))); // 2023-09-11 02:00:00
+ doReturn("TestEntriesKey").when(mFakeEntry).getKey();
}
@Test
@@ -99,7 +107,8 @@
when(mFakeView.getId()).thenReturn(R.id.main_button);
doNothing().when(mContext).startActivity(captor.capture());
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(adaptiveBrightnessAnomaly);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(
+ adaptiveBrightnessAnomaly, adaptiveBrightnessAnomaly);
mBatteryTipsCardPreference.onClick(mFakeView);
assertThat(mBatteryTipsCardPreference.isVisible()).isFalse();
@@ -110,18 +119,21 @@
assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, -1))
.isEqualTo(SettingsEnums.DISPLAY);
verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "BrightnessAnomaly");
+ verify(mFeatureFactory.metricsFeatureProvider).action(
mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, "BrightnessAnomaly");
}
@Test
- public void onClick_dismissBtn_cardDismissAndLogged() {
+ public void onClick_dismissBtnOfSettingsAnomaly_cardDismissAndLogged() {
final PowerAnomalyEvent screenTimeoutAnomaly =
BatteryTestUtils.createScreenTimeoutAnomalyEvent();
DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext);
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
when(mFakeView.getId()).thenReturn(R.id.dismiss_button);
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(screenTimeoutAnomaly);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(
+ screenTimeoutAnomaly, screenTimeoutAnomaly);
mBatteryTipsCardPreference.onClick(mFakeView);
assertThat(mBatteryTipsCardPreference.isVisible()).isFalse();
@@ -129,6 +141,8 @@
assertThat(DatabaseUtils.getDismissedPowerAnomalyKeys(mContext))
.contains(PowerAnomalyKey.KEY_SCREEN_TIMEOUT.name());
verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "ScreenTimeoutAnomaly");
+ verify(mFeatureFactory.metricsFeatureProvider).action(
mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, "ScreenTimeoutAnomaly");
}
@@ -137,30 +151,40 @@
final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
when(mFakeView.getId()).thenReturn(R.id.main_button);
+ doNothing().when(mBatteryChartPreferenceController).selectHighlightSlotIndex();
+ when(mPowerUsageAdvanced.findRelatedBatteryDiffEntry(any())).thenReturn(mFakeEntry);
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly, appsAnomaly);
mBatteryTipsCardPreference.onClick(mFakeView);
assertThat(mBatteryTipsCardPreference.isVisible()).isFalse();
verify(mContext, never()).startActivity(any(Intent.class));
+ verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(
+ eq(1), eq(0));
verify(mBatteryChartPreferenceController).selectHighlightSlotIndex();
verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "AppAnomaly");
+ verify(mFeatureFactory.metricsFeatureProvider).action(
mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, "AppAnomaly");
}
@Test
- public void onClick_dismissBtnOfAppsAnomaly_removeHighlightSlotIndex() {
+ public void onClick_dismissBtnOfAppsAnomaly_keepHighlightSlotIndex() {
final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
when(mFakeView.getId()).thenReturn(R.id.dismiss_button);
+ when(mPowerUsageAdvanced.findRelatedBatteryDiffEntry(any())).thenReturn(mFakeEntry);
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly, appsAnomaly);
mBatteryTipsCardPreference.onClick(mFakeView);
assertThat(mBatteryTipsCardPreference.isVisible()).isFalse();
+ verify(mContext, never()).startActivity(any(Intent.class));
verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(
- eq(BatteryChartViewModel.SELECTED_INDEX_INVALID),
- eq(BatteryChartViewModel.SELECTED_INDEX_INVALID));
+ eq(1), eq(0));
+ verify(mBatteryChartPreferenceController, never()).selectHighlightSlotIndex();
+ verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "AppAnomaly");
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 913c00a..b8afe98 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
@@ -16,8 +16,6 @@
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;
@@ -70,30 +68,18 @@
@Test
public void handleBatteryTipsCardUpdated_null_hidePreference() {
- mBatteryTipsController.handleBatteryTipsCardUpdated(/* powerAnomalyEvents= */ null);
+ mBatteryTipsController.handleBatteryTipsCardUpdated(/* powerAnomalyEvents= */ null, false);
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();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
- mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+ mBatteryTipsController.handleBatteryTipsCardUpdated(
+ new AnomalyEventWrapper(mContext, event), false);
// Check pre-defined string
verify(mBatteryTipsCardPreference).setTitle(
@@ -114,7 +100,8 @@
PowerAnomalyEvent event = BatteryTestUtils.createScreenTimeoutAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
- mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+ mBatteryTipsController.handleBatteryTipsCardUpdated(
+ new AnomalyEventWrapper(mContext, event), false);
verify(mBatteryTipsCardPreference).setTitle("Reduce screen timeout to extend battery life");
verify(mBatteryTipsCardPreference).setIconResourceId(R.drawable.ic_battery_tips_lightbulb);
@@ -139,7 +126,8 @@
.build();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
- mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+ mBatteryTipsController.handleBatteryTipsCardUpdated(
+ new AnomalyEventWrapper(mContext, event), false);
verify(mBatteryTipsCardPreference).setTitle(testTitle);
verify(mBatteryTipsCardPreference).setIconResourceId(R.drawable.ic_battery_tips_lightbulb);
@@ -157,10 +145,13 @@
PowerAnomalyEvent event = BatteryTestUtils.createAppAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
- mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+ AnomalyEventWrapper eventWrapper = new AnomalyEventWrapper(mContext, event);
+ eventWrapper.setRelatedBatteryDiffEntry(
+ new BatteryDiffEntry(mContext, "", "Chrome", 0));
+ mBatteryTipsController.handleBatteryTipsCardUpdated(eventWrapper, false);
verify(mBatteryTipsCardPreference).setTitle(
- "Chrome used more battery than usual in foreground");
+ "Chrome used more battery than usual");
verify(mBatteryTipsCardPreference).setIconResourceId(
R.drawable.ic_battery_tips_warning_icon);
verify(mBatteryTipsCardPreference).setMainButtonStrokeColorResourceId(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
index d89c06b..a721ad4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
@@ -69,7 +69,7 @@
@Mock
private BatteryHistEntry mBatteryHistEntry;
@Mock
- private PowerGaugePreference mPowerGaugePreference;
+ private AnomalyAppItemPreference mAnomalyAppItemPreference;
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
@@ -123,13 +123,14 @@
BatteryDiffEntry.sResourceCache.put(
"fakeBatteryDiffEntryKey",
new BatteryEntry.NameAndIcon("fakeName", /*icon=*/ null, /*iconId=*/ 1));
+ doReturn(mAnomalyAppItemPreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
}
@Test
public void onDestroy_clearPreferenceCacheAndPreferenceGroupRemoveAll() {
// Ensures the testing environment is correct.
mBatteryUsageBreakdownController.mPreferenceCache.put(
- PREF_KEY, mPowerGaugePreference);
+ PREF_KEY, mAnomalyAppItemPreference);
assertThat(mBatteryUsageBreakdownController.mPreferenceCache).hasSize(1);
mBatteryUsageBreakdownController.onDestroy();
@@ -178,7 +179,6 @@
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel();
doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
mBatteryUsageBreakdownController.addAllPreferences();
@@ -188,27 +188,25 @@
@Test
public void removeAndCacheAllUnusedPreferences_removePref_buildCacheAndRemoveAllPreference() {
doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0);
+ doReturn(mAnomalyAppItemPreference).when(mAppListPreferenceGroup).getPreference(0);
doReturn(PREF_KEY2).when(mBatteryHistEntry).getKey();
- doReturn(PREF_KEY).when(mPowerGaugePreference).getKey();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
+ doReturn(PREF_KEY).when(mAnomalyAppItemPreference).getKey();
// Ensures the testing data is correct.
assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty();
mBatteryUsageBreakdownController.removeAndCacheAllUnusedPreferences();
assertThat(mBatteryUsageBreakdownController.mPreferenceCache.get(PREF_KEY))
- .isEqualTo(mPowerGaugePreference);
- verify(mAppListPreferenceGroup).removePreference(mPowerGaugePreference);
+ .isEqualTo(mAnomalyAppItemPreference);
+ verify(mAppListPreferenceGroup).removePreference(mAnomalyAppItemPreference);
}
@Test
public void removeAndCacheAllUnusedPreferences_keepPref_KeepAllPreference() {
doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0);
+ doReturn(mAnomalyAppItemPreference).when(mAppListPreferenceGroup).getPreference(0);
doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey();
- doReturn(PREF_KEY).when(mPowerGaugePreference).getKey();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
+ doReturn(PREF_KEY).when(mAnomalyAppItemPreference).getKey();
// Ensures the testing data is correct.
assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty();
@@ -232,10 +230,10 @@
@Test
public void handlePreferenceTreeClick_forAppEntry_returnTrue() {
mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY;
- doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
+ doReturn(mBatteryDiffEntry).when(mAnomalyAppItemPreference).getBatteryDiffEntry();
assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick(
- mPowerGaugePreference)).isTrue();
+ mAnomalyAppItemPreference)).isTrue();
verify(mMetricsFeatureProvider)
.action(
SettingsEnums.OPEN_BATTERY_USAGE,
@@ -248,10 +246,10 @@
@Test
public void handlePreferenceTreeClick_forSystemEntry_returnTrue() {
mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_UID_BATTERY;
- doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
+ doReturn(mBatteryDiffEntry).when(mAnomalyAppItemPreference).getBatteryDiffEntry();
assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick(
- mPowerGaugePreference)).isTrue();
+ mAnomalyAppItemPreference)).isTrue();
verify(mMetricsFeatureProvider)
.action(
SettingsEnums.OPEN_BATTERY_USAGE,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
index f06dc63..cd594d3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -72,6 +72,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
mContext = spy(RuntimeEnvironment.application);
ConvertUtils.sUsageSource = ConvertUtils.EMPTY_USAGE_SOURCE;
when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
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 953c2d4..9753bd2 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java
@@ -20,8 +20,8 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -41,7 +41,9 @@
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.TimeZone;
+import java.util.function.Predicate;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowDashboardFragment.class)
@@ -50,6 +52,9 @@
private Context mContext;
private PowerUsageAdvanced mPowerUsageAdvanced;
+ private Predicate<PowerAnomalyEvent> mCardFilterPredicate;
+ private Predicate<PowerAnomalyEvent> mSlotFilterPredicate;
+
@Mock
private BatteryTipsController mBatteryTipsController;
@Mock
@@ -65,7 +70,7 @@
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
mContext = spy(RuntimeEnvironment.application);
- mPowerUsageAdvanced = new PowerUsageAdvanced();
+ mPowerUsageAdvanced = spy(new PowerUsageAdvanced());
mPowerUsageAdvanced.mBatteryTipsController = mBatteryTipsController;
mPowerUsageAdvanced.mBatteryChartPreferenceController = mBatteryChartPreferenceController;
mPowerUsageAdvanced.mScreenOnTimeController = mScreenOnTimeController;
@@ -74,43 +79,63 @@
1694354400000L, 1, // 2023-09-10 22:00:00
1694361600000L, 2, // 2023-09-11 00:00:00
1694368800000L, 3))); // 2023-09-11 02:00:00
+ doReturn(mContext).when(mPowerUsageAdvanced).getContext();
+ mSlotFilterPredicate = PowerAnomalyEvent::hasWarningItemInfo;
}
@Test
- public void getHighestScoreAnomalyEvent_withEmptyOrNullList_getNull() {
- assertThat(PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, null)).isNull();
- assertThat(PowerUsageAdvanced.getHighestScoreAnomalyEvent(
- mContext, BatteryTestUtils.createEmptyPowerAnomalyEventList())).isNull();
+ public void getFilterAnomalyEvent_withEmptyOrNullList_getNull() {
+ prepareCardFilterPredicate(null);
+ assertThat(PowerUsageAdvanced
+ .getAnomalyEvent(null, mCardFilterPredicate)).isNull();
+ assertThat(PowerUsageAdvanced
+ .getAnomalyEvent(null, mSlotFilterPredicate)).isNull();
+ assertThat(PowerUsageAdvanced.getAnomalyEvent(
+ BatteryTestUtils.createEmptyPowerAnomalyEventList(), mCardFilterPredicate))
+ .isNull();
+ assertThat(PowerUsageAdvanced.getAnomalyEvent(
+ BatteryTestUtils.createEmptyPowerAnomalyEventList(), mSlotFilterPredicate))
+ .isNull();
}
@Test
- public void getHighestScoreAnomalyEvent_withoutDismissed_getHighestScoreEvent() {
+ public void getFilterAnomalyEvent_withoutDismissed_getHighestScoreEvent() {
final PowerAnomalyEventList powerAnomalyEventList =
BatteryTestUtils.createNonEmptyPowerAnomalyEventList();
- final PowerAnomalyEvent highestScoreEvent =
- PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList);
+ final PowerAnomalyEvent slotEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mSlotFilterPredicate);
+ prepareCardFilterPredicate(slotEvent);
+ final PowerAnomalyEvent cardEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mCardFilterPredicate);
- assertThat(highestScoreEvent)
- .isEqualTo(BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent());
+ assertThat(cardEvent).isEqualTo(BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent());
+ assertThat(slotEvent).isNull();
}
@Test
- public void getHighestScoreAnomalyEvent_withBrightnessDismissed_getScreenTimeout() {
+ public void getFilterAnomalyEvent_withBrightnessDismissed_getScreenTimeout() {
final PowerAnomalyEventList powerAnomalyEventList =
BatteryTestUtils.createNonEmptyPowerAnomalyEventList();
DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext);
DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, PowerAnomalyKey.KEY_BRIGHTNESS.name());
- final PowerAnomalyEvent highestScoreEvent =
- PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList);
+ final PowerAnomalyEvent slotEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mSlotFilterPredicate);
+ prepareCardFilterPredicate(slotEvent);
+ final PowerAnomalyEvent cardEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mCardFilterPredicate);
- assertThat(highestScoreEvent)
- .isEqualTo(BatteryTestUtils.createScreenTimeoutAnomalyEvent());
+ assertThat(cardEvent).isEqualTo(BatteryTestUtils.createScreenTimeoutAnomalyEvent());
+ assertThat(slotEvent).isNull();
}
@Test
- public void getHighestScoreAnomalyEvent_withAllDismissed_getNull() {
+ public void getFilterAnomalyEvent_withAllDismissed_getNull() {
final PowerAnomalyEventList powerAnomalyEventList =
BatteryTestUtils.createNonEmptyPowerAnomalyEventList();
DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext);
@@ -118,20 +143,26 @@
DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, key.name());
}
- final PowerAnomalyEvent highestScoreEvent =
- PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList);
+ final PowerAnomalyEvent slotEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mSlotFilterPredicate);
+ prepareCardFilterPredicate(slotEvent);
+ final PowerAnomalyEvent cardEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mCardFilterPredicate);
- assertThat(highestScoreEvent).isNull();
+ assertThat(cardEvent).isNull();
+ assertThat(slotEvent).isNull();
}
@Test
public void onDisplayAnomalyEventUpdated_withSettingsAnomalyEvent_skipHighlightSlotEffect() {
final PowerAnomalyEvent event = BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent();
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event, event);
- assertThat(mPowerUsageAdvanced.mPowerAnomalyEvent).isEqualTo(event);
- verify(mBatteryTipsController).handleBatteryTipsCardUpdated(eq(event));
+ assertThat(mPowerUsageAdvanced.mHighlightEventWrapper.get().getEventId())
+ .isEqualTo(event.getEventId());
verify(mPowerUsageAdvanced.mBatteryTipsController).setOnAnomalyConfirmListener(isNull());
verify(mPowerUsageAdvanced.mBatteryTipsController).setOnAnomalyRejectListener(isNull());
verify(mPowerUsageAdvanced.mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(
@@ -140,46 +171,44 @@
}
@Test
- public void onDisplayAnomalyEventUpdated_withAppAnomalyEvent_setHighlightSlotEffect() {
+ public void onDisplayAnomalyEventUpdated_onlyAppAnomalyEvent_setHighlightSlotEffect() {
final PowerAnomalyEvent event = BatteryTestUtils.createAppAnomalyEvent();
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event, event);
- assertThat(mPowerUsageAdvanced.mPowerAnomalyEvent).isEqualTo(event);
- verify(mBatteryTipsController).handleBatteryTipsCardUpdated(eq(event));
+ assertThat(mPowerUsageAdvanced.mHighlightEventWrapper.get().getEventId())
+ .isEqualTo(event.getEventId());
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();
+ public void onDisplayAnomalyEventUpdated_withSettingsCardAndAppsSlotEvent_showExpected() {
+ final PowerAnomalyEvent settingsEvent =
+ BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent();
+ final PowerAnomalyEvent appsEvent =
+ BatteryTestUtils.createAppAnomalyEvent();
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event);
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(null);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(settingsEvent, appsEvent);
- 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));
+ assertThat(mPowerUsageAdvanced.mHighlightEventWrapper.get().getEventId())
+ .isEqualTo(appsEvent.getEventId());
+ verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(eq(1), eq(0));
+ verify(mBatteryTipsController).setOnAnomalyConfirmListener(isNull());
+ verify(mBatteryTipsController).setOnAnomalyRejectListener(isNull());
}
-}
+
+ private void prepareCardFilterPredicate(PowerAnomalyEvent slotEvent) {
+ final Set<String> dismissedPowerAnomalyKeys =
+ DatabaseUtils.getDismissedPowerAnomalyKeys(mContext);
+ mCardFilterPredicate = event -> !dismissedPowerAnomalyKeys.contains(
+ event.getDismissRecordKey())
+ && (event.equals(slotEvent) || !event.hasWarningItemInfo());
+ }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
index 1035560..e98ea1b 100644
--- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
@@ -247,6 +247,7 @@
.setEventId("BrightnessAnomaly")
.setType(PowerAnomalyType.TYPE_SETTINGS_BANNER)
.setKey(PowerAnomalyKey.KEY_BRIGHTNESS)
+ .setDismissRecordKey(PowerAnomalyKey.KEY_BRIGHTNESS.name())
.setScore(1.2f)
.setWarningBannerInfo(WarningBannerInfo.newBuilder()
.setMainButtonDestination(DisplaySettings.class.getName())
@@ -264,6 +265,7 @@
.setEventId("ScreenTimeoutAnomaly")
.setType(PowerAnomalyType.TYPE_SETTINGS_BANNER)
.setKey(PowerAnomalyKey.KEY_SCREEN_TIMEOUT)
+ .setDismissRecordKey(PowerAnomalyKey.KEY_SCREEN_TIMEOUT.name())
.setScore(1.1f)
.setWarningBannerInfo(WarningBannerInfo.newBuilder()
.setMainButtonDestination(ScreenTimeoutSettings.class.getName())
@@ -280,15 +282,12 @@
return PowerAnomalyEvent.newBuilder()
.setEventId("AppAnomaly")
.setType(PowerAnomalyType.TYPE_APPS_ITEM)
- .setKey(PowerAnomalyKey.KEY_APP)
+ .setKey(PowerAnomalyKey.KEY_APP_TOTAL_HIGHER_THAN_USUAL)
+ .setDismissRecordKey("KEY_APP_1")
.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")
.build())
.build();
}