From 67506342596189cf6258c1427da9e6a403062339 Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Tue, 22 Aug 2023 18:09:42 +0800 Subject: [PATCH] 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")