Snap for 8891587 from 0dc8d58de5 to udc-release

Change-Id: I2666daf4de325aa4ba22ecc3b9f1a65f5532761f
This commit is contained in:
Android Build Coastguard Worker
2022-08-01 21:46:16 +00:00
18 changed files with 1252 additions and 137 deletions

View File

@@ -22,6 +22,7 @@
<com.android.settings.accessibility.LocalePreference
android:key="captioning_locale"
android:summary="%s"
android:title="@string/captioning_locale"
settings:controller="com.android.settings.accessibility.CaptionLocalePreferenceController"/>

View File

@@ -42,7 +42,6 @@ public class CaptionCustomController extends BasePreferenceController
private Preference mCustom;
private final CaptionHelper mCaptionHelper;
private final ContentResolver mContentResolver;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@VisibleForTesting
AccessibilitySettingsContentObserver mSettingsContentObserver;
@VisibleForTesting
@@ -54,7 +53,8 @@ public class CaptionCustomController extends BasePreferenceController
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
mContentResolver = context.getContentResolver();
mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
mSettingsContentObserver = new AccessibilitySettingsContentObserver(
new Handler(Looper.getMainLooper()));
mSettingsContentObserver.registerKeysToObserverCallback(CAPTIONING_FEATURE_KEYS,
key -> refreshShowingCustom());
}

View File

@@ -33,7 +33,6 @@ public class CaptionFontSizeController extends BasePreferenceController
private final CaptioningManager mCaptioningManager;
private final CaptionHelper mCaptionHelper;
private ListPreference mPreference;
public CaptionFontSizeController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -49,19 +48,19 @@ public class CaptionFontSizeController extends BasePreferenceController
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
final ListPreference listPreference = screen.findPreference(getPreferenceKey());
final float fontSize = mCaptioningManager.getFontScale();
mPreference.setValue(Float.toString(fontSize));
listPreference.setValue(Float.toString(fontSize));
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final ListPreference listPreference = (ListPreference) preference;
final ContentResolver cr = mContext.getContentResolver();
Settings.Secure.putFloat(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
Float.parseFloat((String) newValue));
mPreference.setValue((String) newValue);
listPreference.setValue((String) newValue);
mCaptionHelper.setEnabled(true);
return false;
}

View File

@@ -188,17 +188,27 @@ public class CaptionHelper {
}
/**
* Sets the caption raw user style.
* Sets the captioning raw user style.
*
* @param type The caption raw user style
* @param type The captioning raw user style
*/
public void setRawUserStyle(int type) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, type);
}
/** Returns the caption raw user style.*/
/** Returns the captioning raw preset number.*/
public int getRawUserStyle() {
return mCaptioningManager.getRawUserStyle();
}
/** Returns the captioning visual properties.*/
public CaptionStyle getUserStyle() {
return mCaptioningManager.getUserStyle();
}
/** Returns the captioning locale language.*/
public Locale getLocale() {
return mCaptioningManager.getLocale();
}
}

View File

@@ -30,7 +30,6 @@ public class CaptionLocalePreferenceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private final CaptioningManager mCaptioningManager;
private LocalePreference mPreference;
public CaptionLocalePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -45,22 +44,17 @@ public class CaptionLocalePreferenceController extends BasePreferenceController
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
final LocalePreference localePreference = screen.findPreference(getPreferenceKey());
final String rawLocale = mCaptioningManager.getRawLocale();
mPreference.setValue(rawLocale == null ? "" : rawLocale);
}
@Override
public CharSequence getSummary() {
return mPreference.getEntry();
localePreference.setValue(rawLocale == null ? "" : rawLocale);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final LocalePreference localePreference = (LocalePreference) preference;
Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, (String) newValue);
mPreference.setValue((String) newValue);
mPreference.setSummary(mPreference.getEntry());
localePreference.setValue((String) newValue);
return true;
}
}

View File

@@ -21,7 +21,7 @@ import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.View;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
@@ -57,16 +57,11 @@ public class CaptionPreviewPreferenceController extends BasePreferenceController
private final Handler mHandler = new Handler(Looper.getMainLooper());
@VisibleForTesting
AccessibilitySettingsContentObserver mSettingsContentObserver;
private CaptioningManager mCaptioningManager;
private CaptionHelper mCaptionHelper;
private LayoutPreference mPreference;
private SubtitleView mPreviewText;
private View mPreviewWindow;
private View mPreviewViewport;
public CaptionPreviewPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptioningManager = context.getSystemService(CaptioningManager.class);
mCaptionHelper = new CaptionHelper(context);
mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
mSettingsContentObserver.registerKeysToObserverCallback(CAPTIONING_FEATURE_KEYS,
@@ -92,16 +87,14 @@ public class CaptionPreviewPreferenceController extends BasePreferenceController
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
mPreviewText = mPreference.findViewById(R.id.preview_text);
mPreviewWindow = mPreference.findViewById(R.id.preview_window);
mPreviewViewport = mPreference.findViewById(R.id.preview_viewport);
mPreviewViewport.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
final View previewViewport = mPreference.findViewById(R.id.preview_viewport);
previewViewport.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if ((oldRight - oldLeft) != (right - left)) {
// Remove the listener once the callback is triggered.
mPreviewViewport.removeOnLayoutChangeListener(this);
previewViewport.removeOnLayoutChangeListener(this);
mHandler.post(() -> refreshPreviewText());
}
}
@@ -109,26 +102,28 @@ public class CaptionPreviewPreferenceController extends BasePreferenceController
}
private void refreshPreviewText() {
if (mPreviewText != null) {
final int styleId = mCaptioningManager.getRawUserStyle();
mCaptionHelper.applyCaptionProperties(mPreviewText, mPreviewViewport, styleId);
final SubtitleView previewText = mPreference.findViewById(R.id.preview_text);
if (previewText != null) {
final View previewViewport = mPreference.findViewById(R.id.preview_viewport);
final int styleId = mCaptionHelper.getRawUserStyle();
mCaptionHelper.applyCaptionProperties(previewText, previewViewport, styleId);
final Locale locale = mCaptioningManager.getLocale();
final Locale locale = mCaptionHelper.getLocale();
if (locale != null) {
final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
mContext, locale, R.string.captioning_preview_text);
mPreviewText.setText(localizedText);
previewText.setText(localizedText);
} else {
mPreviewText.setText(R.string.captioning_preview_text);
previewText.setText(R.string.captioning_preview_text);
}
final CaptioningManager.CaptionStyle style = mCaptioningManager.getUserStyle();
final View previewWindow = mPreference.findViewById(R.id.preview_window);
final CaptionStyle style = mCaptionHelper.getUserStyle();
if (style.hasWindowColor()) {
mPreviewWindow.setBackgroundColor(style.windowColor);
previewWindow.setBackgroundColor(style.windowColor);
} else {
final CaptioningManager.CaptionStyle defStyle =
CaptioningManager.CaptionStyle.DEFAULT;
mPreviewWindow.setBackgroundColor(defStyle.windowColor);
final CaptionStyle defStyle = CaptionStyle.DEFAULT;
previewWindow.setBackgroundColor(defStyle.windowColor);
}
}
}

View File

@@ -57,9 +57,9 @@ public class CaptionTogglePreferenceController extends TogglePreferenceControlle
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
SettingsMainSwitchPreference pref = screen.findPreference(getPreferenceKey());
pref.addOnSwitchChangeListener(this);
pref.setChecked(isChecked());
SettingsMainSwitchPreference preference = screen.findPreference(getPreferenceKey());
preference.addOnSwitchChangeListener(this);
preference.setChecked(isChecked());
}
@Override

View File

@@ -32,7 +32,6 @@ public class CaptionTypefaceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private final CaptionHelper mCaptionHelper;
private ListPreference mPreference;
public CaptionTypefaceController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -47,20 +46,20 @@ public class CaptionTypefaceController extends BasePreferenceController
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
final ListPreference listPreference = screen.findPreference(getPreferenceKey());
final ContentResolver cr = mContext.getContentResolver();
final CaptionStyle attrs = CaptionStyle.getCustomStyle(cr);
final String rawTypeface = attrs.mRawTypeface;
mPreference.setValue(rawTypeface == null ? "" : rawTypeface);
listPreference.setValue(rawTypeface == null ? "" : rawTypeface);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final ListPreference listPreference = (ListPreference) preference;
final ContentResolver cr = mContext.getContentResolver();
Settings.Secure.putString(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, (String) newValue);
mPreference.setValue((String) newValue);
listPreference.setValue((String) newValue);
mCaptionHelper.setEnabled(true);
return false;
}

View File

@@ -266,8 +266,9 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null"
: ("size=" + batteryHistoryMap.size())));
// TODO: implement the callback function.
final BatteryLevelData batteryLevelData =
DataProcessor.getBatteryLevelData(mContext, batteryHistoryMap);
DataProcessor.getBatteryLevelData(mContext, mHandler, batteryHistoryMap, null);
Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
if (batteryLevelData == null) {
mDailyViewModel = null;
@@ -279,15 +280,17 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro
batteryLevelData.getDailyBatteryLevels().getLevels(),
generateTimestampDayOfWeekTexts(
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps()),
mDailyChartIndex);
mDailyChartIndex,
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
mHourlyViewModels = new ArrayList<>();
for (BatteryLevelData.PeriodBatteryLevelData perDayData :
for (BatteryLevelData.PeriodBatteryLevelData hourlyBatteryLevelsPerDay :
batteryLevelData.getHourlyBatteryLevelsPerDay()) {
mHourlyViewModels.add(new BatteryChartViewModel(
perDayData.getLevels(),
hourlyBatteryLevelsPerDay.getLevels(),
generateTimestampHourTexts(
mContext, perDayData.getTimestamps()),
mHourlyChartIndex));
mContext, hourlyBatteryLevelsPerDay.getTimestamps()),
mHourlyChartIndex,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
refreshUi();
// TODO: Loads item icon and label and build mBatteryIndexedMap.

View File

@@ -33,23 +33,31 @@ class BatteryChartViewModel {
// We need at least 2 levels to draw a trapezoid.
private static final int MIN_LEVELS_DATA_SIZE = 2;
enum AxisLabelPosition {
BETWEEN_TRAPEZOIDS,
CENTER_OF_TRAPEZOIDS,
}
private final List<Integer> mLevels;
private final List<String> mTexts;
private final AxisLabelPosition mAxisLabelPosition;
private int mSelectedIndex;
BatteryChartViewModel(
@NonNull List<Integer> levels, @NonNull List<String> texts, int selectedIndex) {
@NonNull List<Integer> levels, @NonNull List<String> texts, int selectedIndex,
@NonNull AxisLabelPosition axisLabelPosition) {
Preconditions.checkArgument(
levels.size() == texts.size()
&& levels.size() >= MIN_LEVELS_DATA_SIZE
&& selectedIndex >= SELECTED_INDEX_ALL
&& selectedIndex < levels.size(),
String.format(Locale.getDefault(), "Invalid BatteryChartViewModel"
String.format(Locale.ENGLISH, "Invalid BatteryChartViewModel"
+ " levels.size: %d\ntexts.size: %d\nselectedIndex: %d.",
levels.size(), texts.size(), selectedIndex));
mLevels = levels;
mTexts = texts;
mSelectedIndex = selectedIndex;
mAxisLabelPosition = axisLabelPosition;
}
public int size() {
@@ -72,9 +80,13 @@ class BatteryChartViewModel {
mSelectedIndex = index;
}
public AxisLabelPosition axisLabelPosition() {
return mAxisLabelPosition;
}
@Override
public int hashCode() {
return Objects.hash(mLevels, mTexts, mSelectedIndex);
return Objects.hash(mLevels, mTexts, mSelectedIndex, mAxisLabelPosition);
}
@Override
@@ -87,12 +99,15 @@ class BatteryChartViewModel {
final BatteryChartViewModel batteryChartViewModel = (BatteryChartViewModel) other;
return Objects.equals(mLevels, batteryChartViewModel.mLevels)
&& Objects.equals(mTexts, batteryChartViewModel.mTexts)
&& mSelectedIndex == batteryChartViewModel.mSelectedIndex;
&& mSelectedIndex == batteryChartViewModel.mSelectedIndex
&& mAxisLabelPosition == batteryChartViewModel.mAxisLabelPosition;
}
@Override
public String toString() {
return String.format(Locale.getDefault(), "levels: %s\ntexts: %s\nselectedIndex: %d",
Objects.toString(mLevels), Objects.toString(mTexts), mSelectedIndex);
return String.format(Locale.ENGLISH,
"levels: %s\ntexts: %s\nselectedIndex: %d, axisLabelPosition: %s",
Objects.toString(mLevels), Objects.toString(mTexts), mSelectedIndex,
mAxisLabelPosition);
}
}

View File

@@ -47,8 +47,11 @@ import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/** A widget component to draw chart graph. */
public class BatteryChartViewV2 extends AppCompatImageView implements View.OnClickListener,
@@ -57,10 +60,10 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
private static final int DEFAULT_AXIS_LABEL_COUNT = 4;
private static final int AXIS_LABEL_GAPS_COUNT = DEFAULT_AXIS_LABEL_COUNT - 1;
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
private static final long UPDATE_STATE_DELAYED_TIME = 500L;
private static final Map<Integer, Integer[]> MODEL_SIZE_TO_LABEL_INDEXES_MAP =
buildModelSizeToLabelIndexesMap();
/** A callback listener for selected group index is updated. */
public interface OnSelectListener {
@@ -76,11 +79,10 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
private float mTrapezoidHOffset;
private boolean mIsSlotsClickabled;
private String[] mPercentages = getPercentages();
private Integer[] mLabelsIndexes;
@VisibleForTesting
int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
@VisibleForTesting
String[] mAxisLabels;
// Colors for drawing the trapezoid shape and dividers.
private int mTrapezoidColor;
@@ -92,8 +94,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
private final Rect[] mPercentageBounds =
new Rect[]{new Rect(), new Rect(), new Rect()};
// For drawing the axis label information.
private final Rect[] mAxisLabelsBounds =
new Rect[]{new Rect(), new Rect(), new Rect(), new Rect()};
private final Rect[] mAxisLabelsBounds = initializeAxisLabelsBounds();
@VisibleForTesting
Handler mHandler = new Handler();
@@ -137,9 +138,8 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.",
viewModel.size(), viewModel.selectedIndex()));
mViewModel = viewModel;
mLabelsIndexes = MODEL_SIZE_TO_LABEL_INDEXES_MAP.get(mViewModel.size());
initializeTrapezoidSlots(viewModel.size() - 1);
initializeAxisLabels(viewModel.texts());
setClickable(hasAnyValidTrapezoid(viewModel));
requestLayout();
}
@@ -176,12 +176,11 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
mIndent.top = mPercentageBounds[0].height();
mIndent.right = mPercentageBounds[0].width() + mTextPadding;
if (mAxisLabels != null) {
if (mViewModel != null) {
int maxHeight = 0;
for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT; index++) {
mTextPaint.getTextBounds(
mAxisLabels[index], 0, mAxisLabels[index].length(),
mAxisLabelsBounds[index]);
for (int index = 0; index < mLabelsIndexes.length; index++) {
final String text = getAxisLabelText(index);
mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds[index]);
maxHeight = Math.max(maxHeight, mAxisLabelsBounds[index].height());
}
mIndent.bottom = maxHeight + round(mTextPadding * 1.5f);
@@ -333,21 +332,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
}
}
/**
* Initializes the displayed X-axis labels list selected from the model all texts list.
*/
private void initializeAxisLabels(@NonNull List<String> allTexts) {
if (mAxisLabels == null) {
mAxisLabels = new String[DEFAULT_AXIS_LABEL_COUNT];
}
// Current logic is always showing {@code AXIS_LABEL_GAPS_COUNT} labels.
// TODO: Support different count of labels for different levels sizes.
final int step = (allTexts.size() - 1) / AXIS_LABEL_GAPS_COUNT;
for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT; index++) {
mAxisLabels[index] = allTexts.get(index * step);
}
}
private void initializeColors(Context context) {
setBackgroundColor(Color.TRANSPARENT);
mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
@@ -432,37 +416,43 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
startX = nextX;
}
// Draws the axis label slot information.
if (mAxisLabels != null) {
final float[] xOffsets = new float[DEFAULT_AXIS_LABEL_COUNT];
if (mViewModel != null) {
final float[] xOffsets = new float[mLabelsIndexes.length];
final float baselineX = mDividerWidth * .5f;
final float offsetX = mDividerWidth + unitWidth;
// TODO: Support different count of labels for different levels sizes.
final int slotBarOffset = (/*total 12 bars*/ 12) / AXIS_LABEL_GAPS_COUNT;
for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT; index++) {
xOffsets[index] = baselineX + index * offsetX * slotBarOffset;
for (int index = 0; index < mLabelsIndexes.length; index++) {
xOffsets[index] = baselineX + mLabelsIndexes[index] * offsetX;
}
switch (mViewModel.axisLabelPosition()) {
case CENTER_OF_TRAPEZOIDS:
drawAxisLabelsCenterOfTrapezoids(canvas, xOffsets, unitWidth);
break;
case BETWEEN_TRAPEZOIDS:
default:
drawAxisLabelsBetweenTrapezoids(canvas, xOffsets);
break;
}
drawAxisLabel(canvas, xOffsets);
}
}
private void drawAxisLabel(Canvas canvas, float[] xOffsets) {
private void drawAxisLabelsBetweenTrapezoids(Canvas canvas, float[] xOffsets) {
// Draws the 1st axis label info.
canvas.drawText(
mAxisLabels[0], xOffsets[0] - mAxisLabelsBounds[0].left, getAxisLabelY(0),
getAxisLabelText(0), xOffsets[0] - mAxisLabelsBounds[0].left, getAxisLabelY(0),
mTextPaint);
final int latestIndex = DEFAULT_AXIS_LABEL_COUNT - 1;
final int latestIndex = mLabelsIndexes.length - 1;
// Draws the last axis label info.
canvas.drawText(
mAxisLabels[latestIndex],
getAxisLabelText(latestIndex),
xOffsets[latestIndex]
- mAxisLabelsBounds[latestIndex].width()
- mAxisLabelsBounds[latestIndex].left,
getAxisLabelY(latestIndex),
mTextPaint);
// Draws the rest of axis label info since it is located in the center.
for (int index = 1; index <= DEFAULT_AXIS_LABEL_COUNT - 2; index++) {
for (int index = 1; index <= mLabelsIndexes.length - 2; index++) {
canvas.drawText(
mAxisLabels[index],
getAxisLabelText(index),
xOffsets[index]
- (mAxisLabelsBounds[index].width() - mAxisLabelsBounds[index].left)
* .5f,
@@ -471,6 +461,18 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
}
}
private void drawAxisLabelsCenterOfTrapezoids(
Canvas canvas, float[] xOffsets, float unitWidth) {
for (int index = 0; index < mLabelsIndexes.length - 1; index++) {
canvas.drawText(
getAxisLabelText(index),
xOffsets[index] + (unitWidth - (mAxisLabelsBounds[index].width()
- mAxisLabelsBounds[index].left)) * .5f,
getAxisLabelY(index),
mTextPaint);
}
}
private int getAxisLabelY(int index) {
return getHeight()
- mAxisLabelsBounds[index].height()
@@ -554,6 +556,10 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
}
private String getAxisLabelText(int labelIndex) {
return mViewModel.texts().get(mLabelsIndexes[labelIndex]);
}
private static boolean isTrapezoidValid(
@NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel.levels().get(trapezoidIndex) != null
@@ -607,6 +613,33 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli
return false;
}
private static Map<Integer, Integer[]> buildModelSizeToLabelIndexesMap() {
final Map<Integer, Integer[]> result = new HashMap<>();
result.put(2, new Integer[]{0, 1});
result.put(3, new Integer[]{0, 1, 2});
result.put(4, new Integer[]{0, 1, 2, 3});
result.put(5, new Integer[]{0, 1, 2, 3, 4});
result.put(6, new Integer[]{0, 1, 2, 3, 4, 5});
result.put(7, new Integer[]{0, 1, 2, 3, 4, 5, 6});
result.put(8, new Integer[]{0, 1, 2, 3, 4, 5, 6, 7});
result.put(9, new Integer[]{0, 2, 4, 6, 8});
result.put(10, new Integer[]{0, 3, 6, 9});
result.put(11, new Integer[]{0, 5, 10});
result.put(12, new Integer[]{0, 4, 7, 11});
result.put(13, new Integer[]{0, 4, 8, 12});
return result;
}
private static Rect[] initializeAxisLabelsBounds() {
final int maxLabelsLength = MODEL_SIZE_TO_LABEL_INDEXES_MAP.values().stream().max(
Comparator.comparingInt(indexes -> indexes.length)).get().length;
final Rect[] bounds = new Rect[maxLabelsLength];
for (int i = 0; i < maxLabelsLength; i++) {
bounds[i] = new Rect();
}
return bounds;
}
// A container class for each trapezoid left and right location.
@VisibleForTesting
static final class TrapezoidSlot {

View File

@@ -16,7 +16,9 @@
package com.android.settings.fuelgauge.batteryusage;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import java.util.Collections;
import java.util.List;
/** Wraps the battery usage diff data for each entry used for battery usage app list. */
@@ -24,10 +26,24 @@ public class BatteryDiffData {
private final List<BatteryDiffEntry> mAppEntries;
private final List<BatteryDiffEntry> mSystemEntries;
/** Constructor for the diff entries which already have totalConsumePower value. */
public BatteryDiffData(
List<BatteryDiffEntry> appDiffEntries, List<BatteryDiffEntry> systemDiffEntries) {
mAppEntries = appDiffEntries == null ? new ArrayList<>() : appDiffEntries;
mSystemEntries = systemDiffEntries == null ? new ArrayList<>() : systemDiffEntries;
@NonNull List<BatteryDiffEntry> appDiffEntries,
@NonNull List<BatteryDiffEntry> systemDiffEntries) {
mAppEntries = appDiffEntries;
mSystemEntries = systemDiffEntries;
sortEntries();
}
/** Constructor for the diff entries which have not set totalConsumePower value. */
public BatteryDiffData(
@NonNull List<BatteryDiffEntry> appDiffEntries,
@NonNull List<BatteryDiffEntry> systemDiffEntries,
final double totalConsumePower) {
mAppEntries = appDiffEntries;
mSystemEntries = systemDiffEntries;
setTotalConsumePowerForAllEntries(totalConsumePower);
sortEntries();
}
public List<BatteryDiffEntry> getAppDiffEntryList() {
@@ -38,9 +54,15 @@ public class BatteryDiffData {
return mSystemEntries;
}
/** Sets total consume power for each entry. */
public void setTotalConsumePowerForAllEntries(double totalConsumePower) {
// Sets total consume power for each entry.
private void setTotalConsumePowerForAllEntries(final double totalConsumePower) {
mAppEntries.forEach(diffEntry -> diffEntry.setTotalConsumePower(totalConsumePower));
mSystemEntries.forEach(diffEntry -> diffEntry.setTotalConsumePower(totalConsumePower));
}
// Sorts entries based on consumed percentage.
private void sortEntries() {
Collections.sort(mAppEntries, BatteryDiffEntry.COMPARATOR);
Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
}
}

View File

@@ -18,21 +18,36 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTime;
import android.content.ContentValues;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.Utils;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.fuelgauge.BatteryStatus;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A utility class to process data loaded from database and make the data easy to use for battery
@@ -43,10 +58,27 @@ public final class DataProcessor {
private static final String TAG = "DataProcessor";
private static final int MIN_DAILY_DATA_SIZE = 2;
private static final int MIN_TIMESTAMP_DATA_SIZE = 2;
// Maximum total time value for each hourly slot cumulative data at most 2 hours.
private static final float TOTAL_HOURLY_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2;
private static final Map<String, BatteryHistEntry> EMPTY_BATTERY_MAP = new HashMap<>();
private static final BatteryHistEntry EMPTY_BATTERY_HIST_ENTRY =
new BatteryHistEntry(new ContentValues());
@VisibleForTesting
static final double PERCENTAGE_OF_TOTAL_THRESHOLD = 1f;
@VisibleForTesting
static final int SELECTED_INDEX_ALL = BatteryChartViewModel.SELECTED_INDEX_ALL;
/** A fake package name to represent no BatteryEntry data. */
public static final String FAKE_PACKAGE_NAME = "fake_package";
/** A callback listener when battery usage loading async task is executed. */
public interface UsageMapAsyncResponse {
/** The callback function when batteryUsageMap is loaded. */
void onBatteryUsageMapLoaded(
Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap);
}
private DataProcessor() {
}
@@ -58,11 +90,14 @@ public final class DataProcessor {
@Nullable
public static BatteryLevelData getBatteryLevelData(
Context context,
@Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
@Nullable Handler handler,
@Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
final UsageMapAsyncResponse asyncResponseDelegate) {
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
Log.d(TAG, "getBatteryLevelData() returns null");
return null;
}
handler = handler != null ? handler : new Handler(Looper.getMainLooper());
// Process raw history map data into hourly timestamps.
final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap =
getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap);
@@ -70,11 +105,33 @@ public final class DataProcessor {
final BatteryLevelData batteryLevelData =
getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap);
//TODO: Add the async task to compute diff usage data and load labels and icons.
// Start the async task to compute diff usage data and load labels and icons.
if (batteryLevelData != null) {
new ComputeUsageMapAndLoadItemsTask(
context,
handler,
asyncResponseDelegate,
batteryLevelData.getHourlyBatteryLevelsPerDay(),
processedBatteryHistoryMap).execute();
}
return batteryLevelData;
}
/**
* @return Returns whether the target is in the CharSequence array.
*/
public static boolean contains(String target, CharSequence[] packageNames) {
if (target != null && packageNames != null) {
for (CharSequence packageName : packageNames) {
if (TextUtils.equals(target, packageName)) {
return true;
}
}
}
return false;
}
/**
* @return Returns the processed history map which has interpolated to every hour data.
* The start and end timestamp must be the even hours.
@@ -187,7 +244,7 @@ public final class DataProcessor {
@VisibleForTesting
static boolean isFromFullCharge(@Nullable final Map<String, BatteryHistEntry> entryList) {
if (entryList == null) {
Log.d(TAG, "entryList is nul in isFromFullCharge()");
Log.d(TAG, "entryList is null in isFromFullCharge()");
return false;
}
final List<String> entryKeys = new ArrayList<>(entryList.keySet());
@@ -205,14 +262,14 @@ public final class DataProcessor {
static long[] findNearestTimestamp(final List<Long> timestamps, final long target) {
final long[] results = new long[] {Long.MIN_VALUE, Long.MAX_VALUE};
// Searches the nearest lower and upper timestamp value.
for (long timestamp : timestamps) {
timestamps.forEach(timestamp -> {
if (timestamp <= target && timestamp > results[0]) {
results[0] = timestamp;
}
if (timestamp >= target && timestamp < results[1]) {
results[1] = timestamp;
}
}
});
// Uses zero value to represent invalid searching result.
results[0] = results[0] == Long.MIN_VALUE ? 0 : results[0];
results[1] = results[1] == Long.MAX_VALUE ? 0 : results[1];
@@ -224,7 +281,7 @@ public final class DataProcessor {
* timezone.
*/
@VisibleForTesting
static long getTimestampOfNextDay(long timestamp) {
static long getTimestampOfNextDay(long timestamp) {
final Calendar nextDayCalendar = Calendar.getInstance();
nextDayCalendar.setTimeInMillis(timestamp);
nextDayCalendar.add(Calendar.DAY_OF_YEAR, 1);
@@ -234,6 +291,40 @@ public final class DataProcessor {
return nextDayCalendar.getTimeInMillis();
}
/**
* @return Returns the indexed battery usage data for each corresponding time slot.
*
* There could be 2 cases of the returned value:
* 1) null: empty or invalid data.
* 2) non-null: must be a 2d map and composed by 3 parts:
* 1 - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]
* 2 - [0][SELECTED_INDEX_ALL] ~ [maxDailyIndex][SELECTED_INDEX_ALL]
* 3 - [0][0] ~ [maxDailyIndex][maxHourlyIndex]
*/
@VisibleForTesting
@Nullable
static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMap(
final Context context,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
if (batteryHistoryMap.isEmpty()) {
return null;
}
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new HashMap<>();
// Insert diff data from [0][0] to [maxDailyIndex][maxHourlyIndex].
insertHourlyUsageDiffData(
context, hourlyBatteryLevelsPerDay, batteryHistoryMap, resultMap);
// Insert diff data from [0][SELECTED_INDEX_ALL] to [maxDailyIndex][SELECTED_INDEX_ALL].
insertDailyUsageDiffData(hourlyBatteryLevelsPerDay, resultMap);
// Insert diff data [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL].
insertAllUsageDiffData(resultMap);
purgeLowPercentageAndFakeData(context, resultMap);
if (!isUsageMapValid(resultMap, hourlyBatteryLevelsPerDay)) {
return null;
}
return resultMap;
}
/**
* Interpolates history map based on expected timestamp slots and processes the corner case when
* the expected start timestamp is earlier than what we have.
@@ -458,11 +549,454 @@ public final class DataProcessor {
return Math.round(batteryLevelCounter / entryMap.size());
}
private static void log(Context context, String content, long timestamp,
BatteryHistEntry entry) {
private static void insertHourlyUsageDiffData(
Context context,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
final int currentUserId = context.getUserId();
final UserHandle userHandle =
Utils.getManagedProfile(context.getSystemService(UserManager.class));
final int workProfileUserId =
userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
// Each time slot usage diff data =
// Math.abs(timestamp[i+2] data - timestamp[i+1] data) +
// Math.abs(timestamp[i+1] data - timestamp[i] data);
// since we want to aggregate every two hours data into a single time slot.
for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
final Map<Integer, BatteryDiffData> dailyDiffMap = new HashMap<>();
resultMap.put(dailyIndex, dailyDiffMap);
if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
continue;
}
final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
final BatteryDiffData hourlyBatteryDiffData =
insertHourlyUsageDiffDataPerSlot(
context,
currentUserId,
workProfileUserId,
hourlyIndex,
timestamps,
batteryHistoryMap);
dailyDiffMap.put(hourlyIndex, hourlyBatteryDiffData);
}
}
}
private static void insertDailyUsageDiffData(
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
for (int index = 0; index < hourlyBatteryLevelsPerDay.size(); index++) {
Map<Integer, BatteryDiffData> dailyUsageMap = resultMap.get(index);
if (dailyUsageMap == null) {
dailyUsageMap = new HashMap<>();
resultMap.put(index, dailyUsageMap);
}
dailyUsageMap.put(
SELECTED_INDEX_ALL,
getAccumulatedUsageDiffData(dailyUsageMap.values()));
}
}
private static void insertAllUsageDiffData(
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
final List<BatteryDiffData> diffDataList = new ArrayList<>();
resultMap.keySet().forEach(
key -> diffDataList.add(resultMap.get(key).get(SELECTED_INDEX_ALL)));
final Map<Integer, BatteryDiffData> allUsageMap = new HashMap<>();
allUsageMap.put(SELECTED_INDEX_ALL, getAccumulatedUsageDiffData(diffDataList));
resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
}
@Nullable
private static BatteryDiffData insertHourlyUsageDiffDataPerSlot(
Context context,
final int currentUserId,
final int workProfileUserId,
final int currentIndex,
final List<Long> timestamps,
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
final List<BatteryDiffEntry> appEntries = new ArrayList<>();
final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
final Long currentTimestamp = timestamps.get(currentIndex);
final Long nextTimestamp = currentTimestamp + DateUtils.HOUR_IN_MILLIS;
final Long nextTwoTimestamp = nextTimestamp + DateUtils.HOUR_IN_MILLIS;
// Fetches BatteryHistEntry data from corresponding time slot.
final Map<String, BatteryHistEntry> currentBatteryHistMap =
batteryHistoryMap.getOrDefault(currentTimestamp, EMPTY_BATTERY_MAP);
final Map<String, BatteryHistEntry> nextBatteryHistMap =
batteryHistoryMap.getOrDefault(nextTimestamp, EMPTY_BATTERY_MAP);
final Map<String, BatteryHistEntry> nextTwoBatteryHistMap =
batteryHistoryMap.getOrDefault(nextTwoTimestamp, EMPTY_BATTERY_MAP);
// We should not get the empty list since we have at least one fake data to record
// the battery level and status in each time slot, the empty list is used to
// represent there is no enough data to apply interpolation arithmetic.
if (currentBatteryHistMap.isEmpty()
|| nextBatteryHistMap.isEmpty()
|| nextTwoBatteryHistMap.isEmpty()) {
return null;
}
// Collects all keys in these three time slot records as all populations.
final Set<String> allBatteryHistEntryKeys = new ArraySet<>();
allBatteryHistEntryKeys.addAll(currentBatteryHistMap.keySet());
allBatteryHistEntryKeys.addAll(nextBatteryHistMap.keySet());
allBatteryHistEntryKeys.addAll(nextTwoBatteryHistMap.keySet());
double totalConsumePower = 0.0;
double consumePowerFromOtherUsers = 0f;
// Calculates all packages diff usage data in a specific time slot.
for (String key : allBatteryHistEntryKeys) {
final BatteryHistEntry currentEntry =
currentBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
final BatteryHistEntry nextEntry =
nextBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
final BatteryHistEntry nextTwoEntry =
nextTwoBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
// Cumulative values is a specific time slot for a specific app.
long foregroundUsageTimeInMs =
getDiffValue(
currentEntry.mForegroundUsageTimeInMs,
nextEntry.mForegroundUsageTimeInMs,
nextTwoEntry.mForegroundUsageTimeInMs);
long backgroundUsageTimeInMs =
getDiffValue(
currentEntry.mBackgroundUsageTimeInMs,
nextEntry.mBackgroundUsageTimeInMs,
nextTwoEntry.mBackgroundUsageTimeInMs);
double consumePower =
getDiffValue(
currentEntry.mConsumePower,
nextEntry.mConsumePower,
nextTwoEntry.mConsumePower);
// Excludes entry since we don't have enough data to calculate.
if (foregroundUsageTimeInMs == 0
&& backgroundUsageTimeInMs == 0
&& consumePower == 0) {
continue;
}
final BatteryHistEntry selectedBatteryEntry =
selectBatteryHistEntry(currentEntry, nextEntry, nextTwoEntry);
if (selectedBatteryEntry == null) {
continue;
}
// Forces refine the cumulative value since it may introduce deviation error since we
// will apply the interpolation arithmetic.
final float totalUsageTimeInMs =
foregroundUsageTimeInMs + backgroundUsageTimeInMs;
if (totalUsageTimeInMs > TOTAL_HOURLY_TIME_THRESHOLD) {
final float ratio = TOTAL_HOURLY_TIME_THRESHOLD / totalUsageTimeInMs;
if (DEBUG) {
Log.w(TAG, String.format("abnormal usage time %d|%d for:\n%s",
Duration.ofMillis(foregroundUsageTimeInMs).getSeconds(),
Duration.ofMillis(backgroundUsageTimeInMs).getSeconds(),
currentEntry));
}
foregroundUsageTimeInMs =
Math.round(foregroundUsageTimeInMs * ratio);
backgroundUsageTimeInMs =
Math.round(backgroundUsageTimeInMs * ratio);
consumePower = consumePower * ratio;
}
totalConsumePower += consumePower;
final boolean isFromOtherUsers = isConsumedFromOtherUsers(
currentUserId, workProfileUserId, selectedBatteryEntry);
if (isFromOtherUsers) {
consumePowerFromOtherUsers += consumePower;
} else {
final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
context,
foregroundUsageTimeInMs,
backgroundUsageTimeInMs,
consumePower,
selectedBatteryEntry);
if (currentBatteryDiffEntry.isSystemEntry()) {
systemEntries.add(currentBatteryDiffEntry);
} else {
appEntries.add(currentBatteryDiffEntry);
}
}
}
if (consumePowerFromOtherUsers != 0) {
systemEntries.add(createOtherUsersEntry(context, consumePowerFromOtherUsers));
}
// If there is no data, return null instead of empty item.
if (appEntries.isEmpty() && systemEntries.isEmpty()) {
return null;
}
final BatteryDiffData resultDiffData =
new BatteryDiffData(appEntries, systemEntries, totalConsumePower);
return resultDiffData;
}
private static boolean isConsumedFromOtherUsers(
final int currentUserId,
final int workProfileUserId,
final BatteryHistEntry batteryHistEntry) {
return batteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY
&& batteryHistEntry.mUserId != currentUserId
&& batteryHistEntry.mUserId != workProfileUserId;
}
@Nullable
private static BatteryDiffData getAccumulatedUsageDiffData(
final Collection<BatteryDiffData> diffEntryListData) {
double totalConsumePower = 0f;
final Map<String, BatteryDiffEntry> diffEntryMap = new HashMap<>();
final List<BatteryDiffEntry> appEntries = new ArrayList<>();
final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
for (BatteryDiffData diffEntryList : diffEntryListData) {
if (diffEntryList == null) {
continue;
}
for (BatteryDiffEntry entry : diffEntryList.getAppDiffEntryList()) {
computeUsageDiffDataPerEntry(entry, diffEntryMap);
totalConsumePower += entry.mConsumePower;
}
for (BatteryDiffEntry entry : diffEntryList.getSystemDiffEntryList()) {
computeUsageDiffDataPerEntry(entry, diffEntryMap);
totalConsumePower += entry.mConsumePower;
}
}
final Collection<BatteryDiffEntry> diffEntryList = diffEntryMap.values();
for (BatteryDiffEntry entry : diffEntryList) {
// Sets total daily consume power data into all BatteryDiffEntry.
entry.setTotalConsumePower(totalConsumePower);
if (entry.isSystemEntry()) {
systemEntries.add(entry);
} else {
appEntries.add(entry);
}
}
return diffEntryList.isEmpty() ? null : new BatteryDiffData(appEntries, systemEntries);
}
private static void computeUsageDiffDataPerEntry(
final BatteryDiffEntry entry,
final Map<String, BatteryDiffEntry> diffEntryMap) {
final String key = entry.mBatteryHistEntry.getKey();
final BatteryDiffEntry oldBatteryDiffEntry = diffEntryMap.get(key);
// Creates new BatteryDiffEntry if we don't have it.
if (oldBatteryDiffEntry == null) {
diffEntryMap.put(key, entry.clone());
} else {
// Sums up some field data into the existing one.
oldBatteryDiffEntry.mForegroundUsageTimeInMs +=
entry.mForegroundUsageTimeInMs;
oldBatteryDiffEntry.mBackgroundUsageTimeInMs +=
entry.mBackgroundUsageTimeInMs;
oldBatteryDiffEntry.mConsumePower += entry.mConsumePower;
}
}
// Removes low percentage data and fake usage data, which will be zero value.
private static void purgeLowPercentageAndFakeData(
final Context context,
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
final Set<CharSequence> backgroundUsageTimeHideList =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getHideBackgroundUsageTimeSet(context);
final CharSequence[] notAllowShowEntryPackages =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getHideApplicationEntries(context);
resultMap.keySet().forEach(dailyKey -> {
final Map<Integer, BatteryDiffData> dailyUsageMap = resultMap.get(dailyKey);
dailyUsageMap.values().forEach(diffEntryLists -> {
if (diffEntryLists == null) {
return;
}
purgeLowPercentageAndFakeData(
diffEntryLists.getAppDiffEntryList(), backgroundUsageTimeHideList,
notAllowShowEntryPackages);
purgeLowPercentageAndFakeData(
diffEntryLists.getSystemDiffEntryList(), backgroundUsageTimeHideList,
notAllowShowEntryPackages);
});
});
}
private static void purgeLowPercentageAndFakeData(
final List<BatteryDiffEntry> entries,
final Set<CharSequence> backgroundUsageTimeHideList,
final CharSequence[] notAllowShowEntryPackages) {
final Iterator<BatteryDiffEntry> iterator = entries.iterator();
while (iterator.hasNext()) {
final BatteryDiffEntry entry = iterator.next();
final String packageName = entry.getPackageName();
if (entry.getPercentOfTotal() < PERCENTAGE_OF_TOTAL_THRESHOLD
|| FAKE_PACKAGE_NAME.equals(packageName)
|| contains(packageName, notAllowShowEntryPackages)) {
iterator.remove();
}
if (packageName != null
&& !backgroundUsageTimeHideList.isEmpty()
&& contains(packageName, backgroundUsageTimeHideList)) {
entry.mBackgroundUsageTimeInMs = 0;
}
}
}
private static boolean isUsageMapValid(
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) {
if (batteryUsageMap.get(SELECTED_INDEX_ALL) == null
|| batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL) == null) {
Log.e(TAG, "no [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL] in batteryUsageMap");
return false;
}
for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
if (batteryUsageMap.get(dailyIndex) == null
|| !batteryUsageMap.get(dailyIndex).containsKey(SELECTED_INDEX_ALL)) {
Log.e(TAG, "no [" + dailyIndex + "][SELECTED_INDEX_ALL] in batteryUsageMap, "
+ "daily size is: " + hourlyBatteryLevelsPerDay.size());
return false;
}
if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
continue;
}
final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
// Length of hourly usage map should be the length of hourly level data - 1.
for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
if (!batteryUsageMap.get(dailyIndex).containsKey(hourlyIndex)) {
Log.e(TAG, "no [" + dailyIndex + "][" + hourlyIndex + "] in batteryUsageMap, "
+ "hourly size is: " + (timestamps.size() - 1));
return false;
}
}
}
return true;
}
private static boolean contains(String target, Set<CharSequence> packageNames) {
if (target != null && packageNames != null) {
for (CharSequence packageName : packageNames) {
if (TextUtils.equals(target, packageName)) {
return true;
}
}
}
return false;
}
private static long getDiffValue(long v1, long v2, long v3) {
return (v2 > v1 ? v2 - v1 : 0) + (v3 > v2 ? v3 - v2 : 0);
}
private static double getDiffValue(double v1, double v2, double v3) {
return (v2 > v1 ? v2 - v1 : 0) + (v3 > v2 ? v3 - v2 : 0);
}
@Nullable
private static BatteryHistEntry selectBatteryHistEntry(
final BatteryHistEntry... batteryHistEntries) {
for (BatteryHistEntry entry : batteryHistEntries) {
if (entry != null && entry != EMPTY_BATTERY_HIST_ENTRY) {
return entry;
}
}
return null;
}
private static BatteryDiffEntry createOtherUsersEntry(
Context context, final double consumePower) {
final ContentValues values = new ContentValues();
values.put(BatteryHistEntry.KEY_UID, BatteryUtils.UID_OTHER_USERS);
values.put(BatteryHistEntry.KEY_USER_ID, BatteryUtils.UID_OTHER_USERS);
values.put(BatteryHistEntry.KEY_CONSUMER_TYPE, ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
// We will show the percentage for the "other users" item only, the aggregated
// running time information is useless for users to identify individual apps.
final BatteryDiffEntry batteryDiffEntry = new BatteryDiffEntry(
context,
/*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ 0,
consumePower,
new BatteryHistEntry(values));
return batteryDiffEntry;
}
private static void log(Context context, final String content, final long timestamp,
final BatteryHistEntry entry) {
if (DEBUG) {
Log.d(TAG, String.format(entry != null ? "%s %s:\n%s" : "%s %s:%s",
utcToLocalTime(context, timestamp), content, entry));
}
}
// Compute diff map and loads all items (icon and label) in the background.
private static final class ComputeUsageMapAndLoadItemsTask
extends AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>> {
private Context mApplicationContext;
private Handler mHandler;
private UsageMapAsyncResponse mAsyncResponseDelegate;
private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
private ComputeUsageMapAndLoadItemsTask(
Context context,
Handler handler,
final UsageMapAsyncResponse asyncResponseDelegate,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
mApplicationContext = context.getApplicationContext();
mHandler = handler;
mAsyncResponseDelegate = asyncResponseDelegate;
mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
mBatteryHistoryMap = batteryHistoryMap;
}
@Override
protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) {
if (mApplicationContext == null
|| mHandler == null
|| mAsyncResponseDelegate == null
|| mBatteryHistoryMap == null
|| mHourlyBatteryLevelsPerDay == null) {
Log.e(TAG, "invalid input for ComputeUsageMapAndLoadItemsTask()");
return null;
}
final long startTime = System.currentTimeMillis();
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
getBatteryUsageMap(
mApplicationContext, mHourlyBatteryLevelsPerDay, mBatteryHistoryMap);
if (batteryUsageMap != null) {
// Pre-loads each BatteryDiffEntry relative icon and label for all slots.
final BatteryDiffData batteryUsageMapForAll =
batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL);
if (batteryUsageMapForAll != null) {
batteryUsageMapForAll.getAppDiffEntryList().forEach(
entry -> entry.loadLabelAndIcon());
batteryUsageMapForAll.getSystemDiffEntryList().forEach(
entry -> entry.loadLabelAndIcon());
}
}
Log.d(TAG, String.format("execute ComputeUsageMapAndLoadItemsTask in %d/ms",
(System.currentTimeMillis() - startTime)));
return batteryUsageMap;
}
@Override
protected void onPostExecute(
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
mApplicationContext = null;
mHourlyBatteryLevelsPerDay = null;
mBatteryHistoryMap = null;
// Post results back to main thread to refresh UI.
if (mHandler != null && mAsyncResponseDelegate != null) {
mHandler.post(() -> {
mAsyncResponseDelegate.onBatteryUsageMapLoaded(batteryUsageMap);
});
}
}
}
}

View File

@@ -64,21 +64,21 @@ public class CaptionLocalePreferenceControllerTest {
}
@Test
public void getSummary_byDefault_shouldReturnDefault() {
public void displayPreference_byDefault_shouldReturnDefault() {
mController.displayPreference(mScreen);
assertThat(mController.getSummary().toString()).isEqualTo(
assertThat(mPreference.getEntry().toString()).isEqualTo(
mContext.getResources().getString(R.string.locale_default));
}
@Test
public void getSummary_byArabicLocale_shouldReturnArabic() {
public void displayPreference_byArabicLocale_shouldReturnArabic() {
Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, "af_ZA");
mController.displayPreference(mScreen);
assertThat(mController.getSummary().toString()).isEqualTo("Afrikaans");
assertThat(mPreference.getEntry().toString()).isEqualTo("Afrikaans");
}
@Test
@@ -87,6 +87,6 @@ public class CaptionLocalePreferenceControllerTest {
mController.onPreferenceChange(mPreference, "af_ZA");
assertThat(mPreference.getSummary().toString()).isEqualTo("Afrikaans");
assertThat(mPreference.getEntry().toString()).isEqualTo("Afrikaans");
}
}

View File

@@ -84,7 +84,7 @@ public class CaptionPreviewPreferenceControllerTest {
}
@Test
public void onPause_unregisterContentObserver() {
public void onStop_unregisterContentObserver() {
mController.onStop();
verify(mContentResolver).unregisterContentObserver(mController.mSettingsContentObserver);

View File

@@ -178,7 +178,8 @@ public final class BatteryChartPreferenceControllerV2Test {
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 97, 95),
List.of("8 am", "10 am", "12 pm"),
BatteryChartViewModel.SELECTED_INDEX_ALL));
BatteryChartViewModel.SELECTED_INDEX_ALL,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
@Test
@@ -193,7 +194,8 @@ public final class BatteryChartPreferenceControllerV2Test {
verify(mDailyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 83, 59, 41),
List.of("SAT", "SUN", "MON", "MON"),
BatteryChartViewModel.SELECTED_INDEX_ALL));
BatteryChartViewModel.SELECTED_INDEX_ALL,
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS));
reset(mDailyChartView);
reset(mHourlyChartView);
@@ -204,12 +206,14 @@ public final class BatteryChartPreferenceControllerV2Test {
verify(mDailyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 83, 59, 41),
List.of("SAT", "SUN", "MON", "MON"),
0));
0,
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS));
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 97, 95, 93, 91, 89, 87, 85, 83),
List.of("8 am", "10 am", "12 pm", "2 pm", "4 pm", "6 pm", "8 pm", "10 pm",
"12 am"),
BatteryChartViewModel.SELECTED_INDEX_ALL));
BatteryChartViewModel.SELECTED_INDEX_ALL,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
reset(mDailyChartView);
reset(mHourlyChartView);
@@ -221,12 +225,14 @@ public final class BatteryChartPreferenceControllerV2Test {
verify(mDailyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 83, 59, 41),
List.of("SAT", "SUN", "MON", "MON"),
1));
1,
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS));
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59),
List.of("12 am", "2 am", "4 am", "6 am", "8 am", "10 am", "12 pm", "2 pm",
"4 pm", "6 pm", "8 pm", "10 pm", "12 am"),
6));
6,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
reset(mDailyChartView);
reset(mHourlyChartView);
@@ -239,12 +245,14 @@ public final class BatteryChartPreferenceControllerV2Test {
verify(mDailyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 83, 59, 41),
List.of("SAT", "SUN", "MON", "MON"),
2));
2,
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS));
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(59, 57, 55, 53, 51, 49, 47, 45, 43, 41),
List.of("12 am", "2 am", "4 am", "6 am", "8 am", "10 am", "12 pm", "2 pm",
"4 pm", "6 pm"),
BatteryChartViewModel.SELECTED_INDEX_ALL));
BatteryChartViewModel.SELECTED_INDEX_ALL,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
@Test

View File

@@ -104,7 +104,8 @@ public final class BatteryChartViewV2Test {
final int originalSelectedIndex = 2;
mBatteryChartView.setViewModel(
new BatteryChartViewModel(List.of(90, 80, 70, 60), List.of("", "", "", ""),
originalSelectedIndex));
originalSelectedIndex,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) {
mBatteryChartView.mTrapezoidSlots[i] = new BatteryChartViewV2.TrapezoidSlot();
mBatteryChartView.mTrapezoidSlots[i].mLeft = i;
@@ -136,6 +137,7 @@ public final class BatteryChartViewV2Test {
.thenReturn(false);
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isFalse();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
}
@@ -148,6 +150,7 @@ public final class BatteryChartViewV2Test {
doReturn(false).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@@ -163,6 +166,7 @@ public final class BatteryChartViewV2Test {
.getEnabledAccessibilityServiceList(anyInt());
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@@ -175,6 +179,7 @@ public final class BatteryChartViewV2Test {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isFalse();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
}
@@ -188,7 +193,8 @@ public final class BatteryChartViewV2Test {
texts.add("");
}
mBatteryChartView.setViewModel(new BatteryChartViewModel(
levels, texts, BatteryChartViewModel.SELECTED_INDEX_ALL));
levels, texts, BatteryChartViewModel.SELECTED_INDEX_ALL,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);

View File

@@ -19,11 +19,16 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.ContentValues;
import android.content.Context;
import android.text.format.DateUtils;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,6 +40,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
@@ -45,18 +51,30 @@ public class DataProcessorTest {
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
mContext = spy(RuntimeEnvironment.application);
mFeatureFactory = FakeFeatureFactory.setupForTest();
mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
}
@Test
public void getBatteryLevelData_emptyHistoryMap_returnNull() {
assertThat(DataProcessor.getBatteryLevelData(mContext, null)).isNull();
assertThat(DataProcessor.getBatteryLevelData(mContext, new HashMap<>())).isNull();
assertThat(DataProcessor.getBatteryLevelData(
mContext,
/*handler=*/ null,
/*batteryHistoryMap=*/ null,
/*asyncResponseDelegate=*/ null))
.isNull();
assertThat(DataProcessor.getBatteryLevelData(
mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null))
.isNull();
}
@Test
@@ -67,7 +85,9 @@ public class DataProcessorTest {
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
assertThat(DataProcessor.getBatteryLevelData(mContext, batteryHistoryMap)).isNull();
assertThat(DataProcessor.getBatteryLevelData(
mContext, /*handler=*/ null, batteryHistoryMap, /*asyncResponseDelegate=*/ null))
.isNull();
}
@Test
@@ -79,7 +99,11 @@ public class DataProcessorTest {
createHistoryMap(timestamps, levels);
final BatteryLevelData resultData =
DataProcessor.getBatteryLevelData(mContext, batteryHistoryMap);
DataProcessor.getBatteryLevelData(
mContext,
/*handler=*/ null,
batteryHistoryMap,
/*asyncResponseDelegate=*/ null);
final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[2]);
final List<Integer> expectedDailyLevels = List.of(levels[0], levels[2]);
@@ -377,6 +401,450 @@ public class DataProcessorTest {
.isEqualTo(1640966400000L);
}
@Test
public void getBatteryUsageMap_emptyHistoryMap_returnNull() {
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
assertThat(DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, new HashMap<>())).isNull();
}
@Test
public void getBatteryUsageMap_emptyHourlyData_returnNull() {
final long[] timestamps = {1000000L, 2000000L};
final int[] levels = {100, 99};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
assertThat(DataProcessor.getBatteryUsageMap(
mContext, new ArrayList<>(), batteryHistoryMap)).isNull();
}
@Test
public void getBatteryUsageMap_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641045600000L, // 2022-01-01 22:00:00
1641049200000L, // 2022-01-01 23:00:00
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L, // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
final BatteryHistEntry fakeEntry = createBatteryHistEntry(
ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0, /*uid=*/ 0L,
currentUserId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
/*foregroundUsageTimeInMs=*/ 0L, /*backgroundUsageTimeInMs=*/ 0L);
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 5.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 20.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 15L,
25L);
entryMap.put(entry.getKey(), entry);
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
// Adds the index = 3 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 40.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 25L,
/*backgroundUsageTimeInMs=*/ 35L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 3L, currentUserId,
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*foregroundUsageTimeInMs=*/ 40L,
/*backgroundUsageTimeInMs=*/ 50L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package3", "label3", /*consumePower=*/ 15.0, /*uid=*/ 4L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 5L,
/*backgroundUsageTimeInMs=*/ 5L);
entryMap.put(entry.getKey(), entry);
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[3], entryMap);
// Adds the index = 4 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 40.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
/*backgroundUsageTimeInMs=*/ 40L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 20.0, /*uid=*/ 3L, currentUserId,
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*foregroundUsageTimeInMs=*/ 50L,
/*backgroundUsageTimeInMs=*/ 60L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package3", "label3", /*consumePower=*/ 40.0, /*uid=*/ 4L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 5L,
/*backgroundUsageTimeInMs=*/ 5L);
entryMap.put(entry.getKey(), entry);
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[4], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
// Adds the day 1 data.
List<Long> timestamps =
List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
// Adds the day 2 data.
timestamps = List.of(batteryHistoryKeys[2], batteryHistoryKeys[4]);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 40.0,
/*foregroundUsageTimeInMs=*/ 30, /*backgroundUsageTimeInMs=*/ 40);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(1), currentUserId, /*uid=*/ 4L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 40.0,
/*foregroundUsageTimeInMs=*/ 5, /*backgroundUsageTimeInMs=*/ 5);
assertBatteryDiffEntry(
resultDiffData.getSystemDiffEntryList().get(0), currentUserId, /*uid=*/ 3L,
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 20.0,
/*foregroundUsageTimeInMs=*/ 50, /*backgroundUsageTimeInMs=*/ 60);
resultDiffData = resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 100.0,
/*foregroundUsageTimeInMs=*/ 15, /*backgroundUsageTimeInMs=*/ 25);
resultDiffData = resultMap.get(1).get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 4L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0,
/*foregroundUsageTimeInMs=*/ 5, /*backgroundUsageTimeInMs=*/ 5);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(1), currentUserId, /*uid=*/ 2L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 25.0,
/*foregroundUsageTimeInMs=*/ 15, /*backgroundUsageTimeInMs=*/ 15);
assertBatteryDiffEntry(
resultDiffData.getSystemDiffEntryList().get(0), currentUserId, /*uid=*/ 3L,
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 25.0,
/*foregroundUsageTimeInMs=*/ 50, /*backgroundUsageTimeInMs=*/ 60);
}
@Test
public void getBatteryUsageMap_multipleUsers_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 5.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId + 1,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 5.0, /*uid=*/ 3L, currentUserId + 2,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 15.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 30.0, /*uid=*/ 2L, currentUserId + 1,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 15.0, /*uid=*/ 3L, currentUserId + 2,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 25.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 50.0, /*uid=*/ 2L, currentUserId + 1,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 25.0, /*uid=*/ 3L, currentUserId + 2,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
final BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 1L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 25.0,
/*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 10);
assertBatteryDiffEntry(
resultDiffData.getSystemDiffEntryList().get(0), BatteryUtils.UID_OTHER_USERS,
/*uid=*/ BatteryUtils.UID_OTHER_USERS, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
/*consumePercentage=*/ 75.0, /*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ 0);
assertThat(resultMap.get(0).get(0)).isNotNull();
assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull();
}
@Test
public void getBatteryUsageMap_usageTimeExceed_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 500.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 3600000L,
/*backgroundUsageTimeInMs=*/ 7200000L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
final BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
// Verifies the clipped usage time.
final float ratio = (float) (7200) / (float) (3600 + 7200);
final BatteryDiffEntry resultEntry = resultDiffData.getAppDiffEntryList().get(0);
assertThat(resultEntry.mForegroundUsageTimeInMs)
.isEqualTo(Math.round(entry.mForegroundUsageTimeInMs * ratio));
assertThat(resultEntry.mBackgroundUsageTimeInMs)
.isEqualTo(Math.round(entry.mBackgroundUsageTimeInMs * ratio));
assertThat(resultEntry.mConsumePower)
.isEqualTo(entry.mConsumePower * ratio);
assertThat(resultMap.get(0).get(0)).isNotNull();
assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull();
}
@Test
public void getBatteryUsageMap_hideApplicationEntries_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
when(mPowerUsageFeatureProvider.getHideApplicationEntries(mContext))
.thenReturn(new CharSequence[]{"package1"});
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
final BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0,
/*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 20);
}
@Test
public void getBatteryUsageMap_hideBackgroundUsageTime_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
when(mPowerUsageFeatureProvider.getHideBackgroundUsageTimeSet(mContext))
.thenReturn(new HashSet(Arrays.asList((CharSequence) "package2")));
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
final BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
BatteryDiffEntry resultEntry = resultDiffData.getAppDiffEntryList().get(0);
assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(20);
resultEntry = resultDiffData.getAppDiffEntryList().get(1);
assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(0);
}
private static Map<Long, Map<String, BatteryHistEntry>> createHistoryMap(
final long[] timestamps, final int[] levels) {
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
@@ -391,6 +859,23 @@ public class DataProcessorTest {
return batteryHistoryMap;
}
private static BatteryHistEntry createBatteryHistEntry(
final String packageName, final String appLabel, final double consumePower,
final long uid, final long userId, final int consumerType,
final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs) {
// Only insert required fields.
final ContentValues values = new ContentValues();
values.put(BatteryHistEntry.KEY_PACKAGE_NAME, packageName);
values.put(BatteryHistEntry.KEY_APP_LABEL, appLabel);
values.put(BatteryHistEntry.KEY_UID, uid);
values.put(BatteryHistEntry.KEY_USER_ID, userId);
values.put(BatteryHistEntry.KEY_CONSUMER_TYPE, consumerType);
values.put(BatteryHistEntry.KEY_CONSUME_POWER, consumePower);
values.put(BatteryHistEntry.KEY_FOREGROUND_USAGE_TIME, foregroundUsageTimeInMs);
values.put(BatteryHistEntry.KEY_BACKGROUND_USAGE_TIME, backgroundUsageTimeInMs);
return new BatteryHistEntry(values);
}
private static void verifyExpectedBatteryLevelData(
final BatteryLevelData resultData,
final List<Long> expectedDailyTimestamps,
@@ -451,4 +936,15 @@ public class DataProcessorTest {
.isEqualTo(expectedEnd.getTimeInMillis());
}
private static void assertBatteryDiffEntry(
final BatteryDiffEntry entry, final long userId, final long uid,
final int consumerType, final double consumePercentage,
final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs) {
assertThat(entry.mBatteryHistEntry.mUserId).isEqualTo(userId);
assertThat(entry.mBatteryHistEntry.mUid).isEqualTo(uid);
assertThat(entry.mBatteryHistEntry.mConsumerType).isEqualTo(consumerType);
assertThat(entry.getPercentOfTotal()).isEqualTo(consumePercentage);
assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
}
}