From b9194c4eb83484e7e3089478f6035d294aa460a8 Mon Sep 17 00:00:00 2001 From: menghanli Date: Fri, 29 Jul 2022 08:26:44 +0800 Subject: [PATCH 1/4] Minor change for CaptionAppearanceFragment 1. Move unnecessary public variables into local variables 2. Use %s to update locale preference summary 3. Correct the test case name of CaptionPreviewPreferenceControllerTest Bug: 197695932 Test: make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.accessibility Change-Id: I58d4d590b9b6d46c27d389217f846a780d755891 --- res/xml/captioning_more_options.xml | 1 + .../CaptionCustomController.java | 4 +- .../CaptionFontSizeController.java | 9 ++--- .../settings/accessibility/CaptionHelper.java | 16 ++++++-- .../CaptionLocalePreferenceController.java | 14 ++----- .../CaptionPreviewPreferenceController.java | 39 ++++++++----------- .../CaptionTogglePreferenceController.java | 6 +-- .../CaptionTypefaceController.java | 9 ++--- ...CaptionLocalePreferenceControllerTest.java | 10 ++--- ...aptionPreviewPreferenceControllerTest.java | 2 +- 10 files changed, 54 insertions(+), 56 deletions(-) diff --git a/res/xml/captioning_more_options.xml b/res/xml/captioning_more_options.xml index c4fc5297205..e1fd002bf57 100644 --- a/res/xml/captioning_more_options.xml +++ b/res/xml/captioning_more_options.xml @@ -22,6 +22,7 @@ diff --git a/src/com/android/settings/accessibility/CaptionCustomController.java b/src/com/android/settings/accessibility/CaptionCustomController.java index e1674a2d3ce..23ef73841f8 100644 --- a/src/com/android/settings/accessibility/CaptionCustomController.java +++ b/src/com/android/settings/accessibility/CaptionCustomController.java @@ -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()); } diff --git a/src/com/android/settings/accessibility/CaptionFontSizeController.java b/src/com/android/settings/accessibility/CaptionFontSizeController.java index a8cdce598ae..a8fee3882a4 100644 --- a/src/com/android/settings/accessibility/CaptionFontSizeController.java +++ b/src/com/android/settings/accessibility/CaptionFontSizeController.java @@ -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; } diff --git a/src/com/android/settings/accessibility/CaptionHelper.java b/src/com/android/settings/accessibility/CaptionHelper.java index eb76b6d0ec2..453049325b2 100644 --- a/src/com/android/settings/accessibility/CaptionHelper.java +++ b/src/com/android/settings/accessibility/CaptionHelper.java @@ -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(); + } } diff --git a/src/com/android/settings/accessibility/CaptionLocalePreferenceController.java b/src/com/android/settings/accessibility/CaptionLocalePreferenceController.java index 1b1179d76d9..7566282efff 100644 --- a/src/com/android/settings/accessibility/CaptionLocalePreferenceController.java +++ b/src/com/android/settings/accessibility/CaptionLocalePreferenceController.java @@ -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; } } diff --git a/src/com/android/settings/accessibility/CaptionPreviewPreferenceController.java b/src/com/android/settings/accessibility/CaptionPreviewPreferenceController.java index a8187f18da9..793eaa30df7 100644 --- a/src/com/android/settings/accessibility/CaptionPreviewPreferenceController.java +++ b/src/com/android/settings/accessibility/CaptionPreviewPreferenceController.java @@ -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); } } } diff --git a/src/com/android/settings/accessibility/CaptionTogglePreferenceController.java b/src/com/android/settings/accessibility/CaptionTogglePreferenceController.java index bc305c1893f..b85200209fd 100644 --- a/src/com/android/settings/accessibility/CaptionTogglePreferenceController.java +++ b/src/com/android/settings/accessibility/CaptionTogglePreferenceController.java @@ -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 diff --git a/src/com/android/settings/accessibility/CaptionTypefaceController.java b/src/com/android/settings/accessibility/CaptionTypefaceController.java index 44049b07840..5e452d916ef 100644 --- a/src/com/android/settings/accessibility/CaptionTypefaceController.java +++ b/src/com/android/settings/accessibility/CaptionTypefaceController.java @@ -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; } diff --git a/tests/robotests/src/com/android/settings/accessibility/CaptionLocalePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/CaptionLocalePreferenceControllerTest.java index 56025883693..cb0d67c1c88 100644 --- a/tests/robotests/src/com/android/settings/accessibility/CaptionLocalePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/CaptionLocalePreferenceControllerTest.java @@ -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"); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/CaptionPreviewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/CaptionPreviewPreferenceControllerTest.java index 2ca755ce999..dda4886ece7 100644 --- a/tests/robotests/src/com/android/settings/accessibility/CaptionPreviewPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/CaptionPreviewPreferenceControllerTest.java @@ -84,7 +84,7 @@ public class CaptionPreviewPreferenceControllerTest { } @Test - public void onPause_unregisterContentObserver() { + public void onStop_unregisterContentObserver() { mController.onStop(); verify(mContentResolver).unregisterContentObserver(mController.mSettingsContentObserver); From e9ab2f536596e79c009054961188070a52714662 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Tue, 26 Jul 2022 21:08:13 +0800 Subject: [PATCH 2/4] Improve X axis labels in battery chart (1) Support showing labels under the trapezoids instead of between the trapezoids. For daily chart, the labels are under the trapezoids, for hourly chart, the labels are between the trapezoids. Test: manual Bug: 239491373 Bug: 236101166 Change-Id: I2efb5192d4baafc7745ce269224639511848293e --- .../BatteryChartPreferenceControllerV2.java | 12 +++++---- .../batteryusage/BatteryChartViewModel.java | 27 ++++++++++++++----- .../batteryusage/BatteryChartViewV2.java | 24 +++++++++++++++-- ...atteryChartPreferenceControllerV2Test.java | 24 +++++++++++------ .../batteryusage/BatteryChartViewV2Test.java | 10 +++++-- 5 files changed, 74 insertions(+), 23 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java index 175bba7189a..f7470fbd848 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java @@ -279,15 +279,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. diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java index 74f87fd29d9..493891f45bc 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java @@ -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 mLevels; private final List mTexts; + private final AxisLabelPosition mAxisLabelPosition; private int mSelectedIndex; BatteryChartViewModel( - @NonNull List levels, @NonNull List texts, int selectedIndex) { + @NonNull List levels, @NonNull List 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); } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java index 0995a0843a2..fa452a29dbc 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java @@ -441,11 +441,19 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT; index++) { xOffsets[index] = baselineX + index * offsetX * slotBarOffset; } - drawAxisLabel(canvas, xOffsets); + switch (mViewModel.axisLabelPosition()) { + case CENTER_OF_TRAPEZOIDS: + drawAxisLabelsCenterOfTrapezoids(canvas, xOffsets, unitWidth); + break; + case BETWEEN_TRAPEZOIDS: + default: + drawAxisLabelsBetweenTrapezoids(canvas, xOffsets); + break; + } } } - 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), @@ -471,6 +479,18 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli } } + private void drawAxisLabelsCenterOfTrapezoids( + Canvas canvas, float[] xOffsets, float unitWidth) { + for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT - 1; index++) { + canvas.drawText( + mAxisLabels[index], + xOffsets[index] + (unitWidth - (mAxisLabelsBounds[index].width() + - mAxisLabelsBounds[index].left)) * .5f, + getAxisLabelY(index), + mTextPaint); + } + } + private int getAxisLabelY(int index) { return getHeight() - mAxisLabelsBounds[index].height() diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java index b8862d43b88..a5f193be41d 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java @@ -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 diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java index a8b8d10e07c..174733536bb 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java @@ -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); From a14c9dfc4fd33eefffbd65f046a46184870a78cb Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Wed, 27 Jul 2022 14:57:43 +0800 Subject: [PATCH 3/4] Improve X axis labels in battery chart (2) Support showing different numbers of labels for different count of trapezoids. screen_record: https://drive.google.com/file/d/1CIgppQllHlKC3BGG7hBvOjGDGVOs6OYC/view?usp=sharing&resourcekey=0-mbofu5KOO2dqCwL7K0zakg Test: manual Bug: 239491373 Bug: 236101166 Change-Id: I865dae760b491bdd7c93bbd29fd7dbc33204a60a --- .../batteryusage/BatteryChartViewV2.java | 95 +++++++++++-------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java index fa452a29dbc..bbe1a8f6dcd 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java @@ -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 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 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 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,14 +416,12 @@ 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: @@ -456,21 +438,21 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli 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, @@ -481,9 +463,9 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private void drawAxisLabelsCenterOfTrapezoids( Canvas canvas, float[] xOffsets, float unitWidth) { - for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT - 1; index++) { + for (int index = 0; index < mLabelsIndexes.length - 1; index++) { canvas.drawText( - mAxisLabels[index], + getAxisLabelText(index), xOffsets[index] + (unitWidth - (mAxisLabelsBounds[index].width() - mAxisLabelsBounds[index].left)) * .5f, getAxisLabelY(index), @@ -574,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 @@ -627,6 +613,33 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli return false; } + private static Map buildModelSizeToLabelIndexesMap() { + final Map 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 { From 0dc8d58de58e6bb750740b70b4a120ccebe7c23f Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Tue, 26 Jul 2022 13:22:39 +0800 Subject: [PATCH 4/4] Add the async task to compute diff usage data and load labels and icons. Bug: 236101687 Test: make RunSettingsRoboTests Change-Id: Ie24ea89fa6cfd351c73e64de40e2c9315867af9a --- .../BatteryChartPreferenceControllerV2.java | 3 +- .../batteryusage/BatteryDiffData.java | 34 +- .../fuelgauge/batteryusage/DataProcessor.java | 550 +++++++++++++++++- .../batteryusage/DataProcessorTest.java | 504 +++++++++++++++- 4 files changed, 1072 insertions(+), 19 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java index f7470fbd848..2932984b550 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java @@ -266,8 +266,9 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro final Map> 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; diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java index 5743cac6017..b5d4dde883c 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java @@ -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 mAppEntries; private final List mSystemEntries; + /** Constructor for the diff entries which already have totalConsumePower value. */ public BatteryDiffData( - List appDiffEntries, List systemDiffEntries) { - mAppEntries = appDiffEntries == null ? new ArrayList<>() : appDiffEntries; - mSystemEntries = systemDiffEntries == null ? new ArrayList<>() : systemDiffEntries; + @NonNull List appDiffEntries, + @NonNull List systemDiffEntries) { + mAppEntries = appDiffEntries; + mSystemEntries = systemDiffEntries; + sortEntries(); + } + + /** Constructor for the diff entries which have not set totalConsumePower value. */ + public BatteryDiffData( + @NonNull List appDiffEntries, + @NonNull List systemDiffEntries, + final double totalConsumePower) { + mAppEntries = appDiffEntries; + mSystemEntries = systemDiffEntries; + setTotalConsumePowerForAllEntries(totalConsumePower); + sortEntries(); } public List 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); + } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java index d8650a651eb..a004a519423 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java @@ -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 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> batteryUsageMap); + } + private DataProcessor() { } @@ -58,11 +90,14 @@ public final class DataProcessor { @Nullable public static BatteryLevelData getBatteryLevelData( Context context, - @Nullable final Map> batteryHistoryMap) { + @Nullable Handler handler, + @Nullable final Map> 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> 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 entryList) { if (entryList == null) { - Log.d(TAG, "entryList is nul in isFromFullCharge()"); + Log.d(TAG, "entryList is null in isFromFullCharge()"); return false; } final List entryKeys = new ArrayList<>(entryList.keySet()); @@ -205,14 +262,14 @@ public final class DataProcessor { static long[] findNearestTimestamp(final List 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> getBatteryUsageMap( + final Context context, + final List hourlyBatteryLevelsPerDay, + final Map> batteryHistoryMap) { + if (batteryHistoryMap.isEmpty()) { + return null; + } + final Map> 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 hourlyBatteryLevelsPerDay, + final Map> batteryHistoryMap, + final Map> 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 dailyDiffMap = new HashMap<>(); + resultMap.put(dailyIndex, dailyDiffMap); + if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) { + continue; + } + final List 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 hourlyBatteryLevelsPerDay, + final Map> resultMap) { + for (int index = 0; index < hourlyBatteryLevelsPerDay.size(); index++) { + Map 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> resultMap) { + final List diffDataList = new ArrayList<>(); + resultMap.keySet().forEach( + key -> diffDataList.add(resultMap.get(key).get(SELECTED_INDEX_ALL))); + final Map 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 timestamps, + final Map> batteryHistoryMap) { + final List appEntries = new ArrayList<>(); + final List 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 currentBatteryHistMap = + batteryHistoryMap.getOrDefault(currentTimestamp, EMPTY_BATTERY_MAP); + final Map nextBatteryHistMap = + batteryHistoryMap.getOrDefault(nextTimestamp, EMPTY_BATTERY_MAP); + final Map 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 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 diffEntryListData) { + double totalConsumePower = 0f; + final Map diffEntryMap = new HashMap<>(); + final List appEntries = new ArrayList<>(); + final List 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 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 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> resultMap) { + final Set backgroundUsageTimeHideList = + FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context) + .getHideBackgroundUsageTimeSet(context); + final CharSequence[] notAllowShowEntryPackages = + FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context) + .getHideApplicationEntries(context); + resultMap.keySet().forEach(dailyKey -> { + final Map 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 entries, + final Set backgroundUsageTimeHideList, + final CharSequence[] notAllowShowEntryPackages) { + final Iterator 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> batteryUsageMap, + final List 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 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 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>> { + + private Context mApplicationContext; + private Handler mHandler; + private UsageMapAsyncResponse mAsyncResponseDelegate; + private List mHourlyBatteryLevelsPerDay; + private Map> mBatteryHistoryMap; + + private ComputeUsageMapAndLoadItemsTask( + Context context, + Handler handler, + final UsageMapAsyncResponse asyncResponseDelegate, + final List hourlyBatteryLevelsPerDay, + final Map> batteryHistoryMap) { + mApplicationContext = context.getApplicationContext(); + mHandler = handler; + mAsyncResponseDelegate = asyncResponseDelegate; + mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay; + mBatteryHistoryMap = batteryHistoryMap; + } + + @Override + protected Map> 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> 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> 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); + }); + } + } + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java index 0306c4b2afa..1b1e469d1d9 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java @@ -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> 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 expectedDailyTimestamps = List.of(timestamps[0], timestamps[2]); final List expectedDailyLevels = List.of(levels[0], levels[2]); @@ -377,6 +401,450 @@ public class DataProcessorTest { .isEqualTo(1640966400000L); } + @Test + public void getBatteryUsageMap_emptyHistoryMap_returnNull() { + final List 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> 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> 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 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 hourlyBatteryLevelsPerDay = + new ArrayList<>(); + // Adds the day 1 data. + List timestamps = + List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); + final List 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> 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> batteryHistoryMap = new HashMap<>(); + final int currentUserId = mContext.getUserId(); + // Adds the index = 0 data. + Map 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 hourlyBatteryLevelsPerDay = + new ArrayList<>(); + List timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); + final List levels = List.of(100, 100); + hourlyBatteryLevelsPerDay.add( + new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); + + final Map> 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> batteryHistoryMap = new HashMap<>(); + final int currentUserId = mContext.getUserId(); + // Adds the index = 0 data. + Map 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 hourlyBatteryLevelsPerDay = + new ArrayList<>(); + List timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); + final List levels = List.of(100, 100); + hourlyBatteryLevelsPerDay.add( + new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); + + final Map> 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> batteryHistoryMap = new HashMap<>(); + final int currentUserId = mContext.getUserId(); + // Adds the index = 0 data. + Map 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 hourlyBatteryLevelsPerDay = + new ArrayList<>(); + List timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); + final List levels = List.of(100, 100); + hourlyBatteryLevelsPerDay.add( + new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); + when(mPowerUsageFeatureProvider.getHideApplicationEntries(mContext)) + .thenReturn(new CharSequence[]{"package1"}); + + final Map> 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> batteryHistoryMap = new HashMap<>(); + final int currentUserId = mContext.getUserId(); + // Adds the index = 0 data. + Map 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 hourlyBatteryLevelsPerDay = + new ArrayList<>(); + List timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); + final List 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> 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> createHistoryMap( final long[] timestamps, final int[] levels) { final Map> 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 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); + } }