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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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());
|
||||
|
@@ -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));
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user