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
This commit is contained in:
mxyyiyi
2023-08-30 17:38:32 +08:00
parent dd34fa54e4
commit e9378d27f7
22 changed files with 859 additions and 263 deletions

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -221,14 +221,20 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
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 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
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);
}

View File

@@ -120,12 +120,10 @@ public class BatteryTipsCardPreference extends Preference implements View.OnClic
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();
}

View File

@@ -18,21 +18,15 @@ 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;
import java.util.function.Function;
/** Controls the update for battery tips card */
public class BatteryTipsController extends BasePreferenceController {
@@ -59,6 +53,10 @@ public class BatteryTipsController extends BasePreferenceController {
@VisibleForTesting
BatteryTipsCardPreference mCardPreference;
@VisibleForTesting
AnomalyEventWrapper mAnomalyEventWrapper = null;
@VisibleForTesting
Boolean mIsAcceptable = false;
public BatteryTipsController(Context context) {
super(context, ROOT_PREFERENCE_KEY);
@@ -85,132 +83,56 @@ public class BatteryTipsController extends BasePreferenceController {
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);
}

View File

@@ -53,6 +53,7 @@ import java.util.ArrayList;
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 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
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 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
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 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
? 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 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
* 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 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
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 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
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());

View File

@@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting;
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 @@ public final class BatteryUsageDataLoader {
// 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));

View File

@@ -52,6 +52,7 @@ import java.util.Optional;
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 @@ public class PowerUsageAdvanced extends PowerUsageBase {
@VisibleForTesting
BatteryUsageBreakdownController mBatteryUsageBreakdownController;
@VisibleForTesting
PowerAnomalyEvent mPowerAnomalyEvent;
@VisibleForTesting
Optional<BatteryLevelData> mBatteryLevelData;
@VisibleForTesting
Optional<AnomalyEventWrapper> mHighlightEventWrapper;
@Override
public void onCreate(Bundle icicle) {
@@ -188,7 +189,7 @@ public class PowerUsageAdvanced extends PowerUsageBase {
mIsChartDataLoaded = true;
mBatteryLevelData = null;
mBatteryUsageMap = null;
mPowerAnomalyEvent = null;
mHighlightEventWrapper = null;
restartLoader(LoaderIndex.BATTERY_LEVEL_DATA_LOADER, bundle,
mBatteryLevelDataLoaderCallbacks);
}
@@ -239,8 +240,13 @@ public class PowerUsageAdvanced extends PowerUsageBase {
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 @@ public class PowerUsageAdvanced extends PowerUsageBase {
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 @@ public class PowerUsageAdvanced extends PowerUsageBase {
&& 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 @@ public class PowerUsageAdvanced extends PowerUsageBase {
}
@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

View File

@@ -18,6 +18,7 @@ message PowerAnomalyEvent {
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 @@ enum PowerAnomalyType{
// 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 @@ 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;
optional string item_key = 8;
}