Port new version battery usage chart implementation from master to tm-qpr-dev.

This cl is a merge of the following 36 cls:
ag/19250259	Duplicate BatteryChartPreferenceController and BatteryChartView into new files for better diff review purpose
ag/19279660	Use Mockito 4.6.1 API for BatteryChartPreferenceControllerV2Test
ag/19267975	Add class BatteryLevelData used to parcel the battery timestamps and levels. It behaves as an interface between UI and data.
ag/19289086	Refactor BatteryChartView X-axis labels. Instead of only timestamps, also support any string[] labels.
ag/19238586	Add interpolation for the history data since last full charge loaded from database.
ag/19331746	Return raw history map in function getHistoryMapSinceLastFullCharge.
ag/19308838	In BatteryChartViewV2, use levels.length-1 to replace mTrapezoidCount. So the chartview could show any number of slots as the given levels length-1.
ag/19332266	Add class BatteryDiffData used to parcel battery usage data
ag/19331467	Refactor Battery Chart View State Controll
ag/19358207	Add DataProcessor to process raw history map for UI.
ag/19332276	Add battery chart view model.
ag/19394744	Update trapezoid validation in battery chart view.
ag/19379730	Support daily and hourly battery chartview.
ag/19428426	Improve X axis labels in battery chart (1)
ag/19446215	Improve X axis labels in battery chart (2)
ag/19394745	Add the async task to compute diff usage data and load labels and icons.
ag/19447624	Support showing app usage list for two battery charts
ag/19500907	Updates battery usage messages from last 24hr to last full charge. (Part1: V2 files)
ag/19505324	Update the selected period message in battery chart
ag/19500905	Updates battery usage messages from last 24hr to last full charge. (Part2: non-V2 files)
ag/19510363	Update usage data for EBS app usage list and App usage detail from 24 hours to last full charge.
ag/19523184	Update usage data for EBS app usage list and App usage detail from 24 hours to last full charge.
ag/19534864	Add margin between battery daily and hourly charts
ag/19491093	Always do interpolation for battery level data in daily chart.
ag/19565630	Avoid NullPointerException when batteryLevelData is null.
ag/19561239	Fix b/241872474 Battery usage page will crash when selecting the last hour chart bar, going to app detail page, and going back
ag/19565633	Fix b/241885070: Unexpected texts moving when going back to battery usage page
ag/19534850	New way to draw battery chart axis labels
ag/19561240	Switch Battery Usage Chart from V1 to V2.
ag/19561338	Switch Battery Usage Chart from V1 to V2.
ag/19600174	Fix b/242254055 Battery usage initial screen improvements (long data loading time)
ag/19600284	Fix b/242252080: Add padding space on the top of the battery chart
ag/19647338	Consider usage map valid even if [all][all] is null.
ag/19634227	Use new content uri everytime to avoid cache
ag/19600177	Fix b/242009481: Refine the battery usage chart timestamp label rule
ag/19647337	Fix b/242809981 Charge battery to 100% when battery usage page opened, the chart will refresh, but the app list isn't refreshed in that case.

Test: manual
Bug: 239491373
Bug: 236101166
Bug: 236101687
Fix: 236101166
Change-Id: I7de8d9dcee14627da10752534991f1ec9f616020
Merged-In: I9142c0d4e00dea3771777ba9aedeab07b635fa1a
This commit is contained in:
Kuan Wang
2022-07-12 15:34:21 +08:00
committed by Zaiyue Xue
parent 1215a5dff3
commit 4db5c6ba57
22 changed files with 3095 additions and 800 deletions

View File

@@ -29,14 +29,24 @@
android:layout_marginVertical="16dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:text="@string/battery_usage_chart_graph_hint" />
android:text="@string/battery_usage_chart_graph_hint_last_full_charge" />
<com.android.settings.fuelgauge.batteryusage.BatteryChartView
android:id="@+id/battery_chart"
android:id="@+id/daily_battery_chart"
android:layout_width="match_parent"
android:layout_height="170dp"
android:layout_marginBottom="6dp"
android:visibility="invisible"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:contentDescription="@string/battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" />
<com.android.settings.fuelgauge.batteryusage.BatteryChartView
android:id="@+id/hourly_battery_chart"
android:layout_width="match_parent"
android:layout_height="170dp"
android:layout_marginBottom="16dp"
android:visibility="visible"
android:contentDescription="@string/battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" />

View File

@@ -6744,10 +6744,16 @@
<!-- [CHAR_LIMIT=NONE] Battery percentage: Description for preference -->
<string name="battery_percentage_description">Show battery percentage in status bar</string>
<!-- [CHAR_LIMIT=NONE] Battery usage main screen chart graph hint since last full charge -->
<string name="battery_usage_chart_graph_hint_last_full_charge">Battery level since last full charge</string>
<!-- [CHAR_LIMIT=NONE] Battery usage main screen chart graph hint -->
<string name="battery_usage_chart_graph_hint">Battery level for past 24 hr</string>
<!-- [CHAR_LIMIT=NONE] Battery app usage section header since last full charge -->
<string name="battery_app_usage">App usage since last full charge</string>
<!-- [CHAR_LIMIT=NONE] Battery app usage section header for past 24 hour -->
<string name="battery_app_usage_for_past_24">App usage for past 24 hr</string>
<!-- [CHAR_LIMIT=NONE] Battery system usage section header since last full charge -->
<string name="battery_system_usage">System usage since last full charge</string>
<!-- [CHAR_LIMIT=NONE] Battery system usage section header for past 24 hour -->
<string name="battery_system_usage_for_past_24">System usage for past 24 hr</string>
<!-- [CHAR_LIMIT=NONE] Battery system usage section header -->

View File

@@ -179,7 +179,7 @@ public class AppBatteryPreferenceController extends BasePreferenceController
return null;
}
final BatteryDiffEntry entry =
BatteryChartPreferenceController.getBatteryLast24HrUsageData(
BatteryChartPreferenceController.getAppBatteryUsageData(
mContext, mPackageName, mUserId);
Log.d(TAG, "loadBatteryDiffEntries():\n" + entry);
return entry;
@@ -200,10 +200,10 @@ public class AppBatteryPreferenceController extends BasePreferenceController
mBatteryPercent = Utils.formatPercentage(
mBatteryDiffEntry.getPercentOfTotal(), /* round */ true);
mPreference.setSummary(mContext.getString(
R.string.battery_summary_24hr, mBatteryPercent));
R.string.battery_summary, mBatteryPercent));
} else {
mPreference.setSummary(
mContext.getString(R.string.no_battery_summary_24hr));
mContext.getString(R.string.no_battery_summary));
}
}

View File

@@ -539,15 +539,12 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
return null;
}
if (totalTimeMs == 0) {
final int batteryWithoutUsageTime = consumedPower > 0
? R.string.battery_usage_without_time : R.string.battery_not_usage_24hr;
usageTimeSummary = getText(isChartGraphEnabled
? batteryWithoutUsageTime : R.string.battery_not_usage);
usageTimeSummary = getText(
isChartGraphEnabled && consumedPower > 0 ? R.string.battery_usage_without_time
: R.string.battery_not_usage);
} else if (slotTime == null) {
// Shows summary text with past 24 hr or full charge if slot time is null.
usageTimeSummary = isChartGraphEnabled
? getAppPast24HrActiveSummary(foregroundTimeMs, backgroundTimeMs, totalTimeMs)
: getAppFullChargeActiveSummary(
// Shows summary text with last full charge if slot time is null.
usageTimeSummary = getAppFullChargeActiveSummary(
foregroundTimeMs, backgroundTimeMs, totalTimeMs);
} else {
// Shows summary text with slot time.

View File

@@ -20,7 +20,6 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -28,7 +27,9 @@ import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
@@ -53,8 +54,6 @@ import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.FooterPreference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -62,27 +61,23 @@ import java.util.Map;
/** Controls the update for chart graph and the list items. */
public class BatteryChartPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy,
OnSaveInstanceState, BatteryChartView.OnSelectListener, OnResume,
ExpandDividerPreference.OnExpandListener {
OnSaveInstanceState, OnResume, ExpandDividerPreference.OnExpandListener {
private static final String TAG = "BatteryChartPreferenceController";
private static final String KEY_FOOTER_PREF = "battery_graph_footer";
private static final String PACKAGE_NAME_NONE = "none";
/** Desired battery history size for timestamp slots. */
public static final int DESIRED_HISTORY_SIZE = 25;
private static final int CHART_LEVEL_ARRAY_SIZE = 13;
private static final int CHART_KEY_ARRAY_SIZE = DESIRED_HISTORY_SIZE;
private static final long VALID_USAGE_TIME_DURATION = DateUtils.HOUR_IN_MILLIS * 2;
private static final long VALID_DIFF_DURATION = DateUtils.MINUTE_IN_MILLIS * 3;
// Keys for bundle instance to restore configurations.
private static final String KEY_EXPAND_SYSTEM_INFO = "expand_system_info";
private static final String KEY_CURRENT_TIME_SLOT = "current_time_slot";
private static final String KEY_DAILY_CHART_INDEX = "daily_chart_index";
private static final String KEY_HOURLY_CHART_INDEX = "hourly_chart_index";
private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
@VisibleForTesting
Map<Integer, List<BatteryDiffEntry>> mBatteryIndexedMap;
Map<Integer, Map<Integer, BatteryDiffData>> mBatteryUsageMap;
@VisibleForTesting
Context mPrefContext;
@@ -91,28 +86,34 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@VisibleForTesting
PreferenceGroup mAppListPrefGroup;
@VisibleForTesting
BatteryChartView mBatteryChartView;
@VisibleForTesting
ExpandDividerPreference mExpandDividerPreference;
@VisibleForTesting
boolean mIsExpanded = false;
@VisibleForTesting
int[] mBatteryHistoryLevels;
@VisibleForTesting
long[] mBatteryHistoryKeys;
@VisibleForTesting
int mTrapezoidIndex = BatteryChartView.SELECTED_INDEX_INVALID;
private boolean mIs24HourFormat = false;
@VisibleForTesting
BatteryChartView mDailyChartView;
@VisibleForTesting
BatteryChartView mHourlyChartView;
@VisibleForTesting
int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
@VisibleForTesting
int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
private boolean mIs24HourFormat;
private boolean mIsFooterPrefAdded = false;
private PreferenceScreen mPreferenceScreen;
private FooterPreference mFooterPreference;
// Daily view model only saves abbreviated day of week texts (e.g. MON). This field saves the
// full day of week texts (e.g. Monday), which is used in category title and battery detail
// page.
private List<String> mDailyTimestampFullTexts;
private BatteryChartViewModel mDailyViewModel;
private List<BatteryChartViewModel> mHourlyViewModels;
private final String mPreferenceKey;
private final SettingsActivity mActivity;
private final InstrumentedPreferenceFragment mFragment;
private final CharSequence[] mNotAllowShowEntryPackages;
private final CharSequence[] mNotAllowShowSummaryPackages;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -120,8 +121,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
// Preference cache to avoid create new instance each time.
@VisibleForTesting
final Map<String, Preference> mPreferenceCache = new HashMap<>();
@VisibleForTesting
final List<BatteryDiffEntry> mSystemEntries = new ArrayList<>();
public BatteryChartPreferenceController(
Context context, String preferenceKey,
@@ -134,10 +133,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mIs24HourFormat = DateFormat.is24HourFormat(context);
mMetricsFeatureProvider =
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
mNotAllowShowEntryPackages =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getHideApplicationEntries(context);
mNotAllowShowSummaryPackages =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
@@ -152,12 +147,14 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
if (savedInstanceState == null) {
return;
}
mTrapezoidIndex =
savedInstanceState.getInt(KEY_CURRENT_TIME_SLOT, mTrapezoidIndex);
mDailyChartIndex =
savedInstanceState.getInt(KEY_DAILY_CHART_INDEX, mDailyChartIndex);
mHourlyChartIndex =
savedInstanceState.getInt(KEY_HOURLY_CHART_INDEX, mHourlyChartIndex);
mIsExpanded =
savedInstanceState.getBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded);
Log.d(TAG, String.format("onCreate() slotIndex=%d isExpanded=%b",
mTrapezoidIndex, mIsExpanded));
Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
}
@Override
@@ -179,10 +176,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
if (savedInstance == null) {
return;
}
savedInstance.putInt(KEY_CURRENT_TIME_SLOT, mTrapezoidIndex);
savedInstance.putInt(KEY_DAILY_CHART_INDEX, mDailyChartIndex);
savedInstance.putInt(KEY_HOURLY_CHART_INDEX, mHourlyChartIndex);
savedInstance.putBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded);
Log.d(TAG, String.format("onSaveInstanceState() slotIndex=%d isExpanded=%b",
mTrapezoidIndex, mIsExpanded));
Log.d(TAG, String.format("onSaveInstanceState() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
}
@Override
@@ -204,8 +202,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mPrefContext = screen.getContext();
mAppListPrefGroup = screen.findPreference(mPreferenceKey);
mAppListPrefGroup.setOrderingAsAdded(false);
mAppListPrefGroup.setTitle(
mPrefContext.getString(R.string.battery_app_usage_for_past_24));
mAppListPrefGroup.setTitle(mPrefContext.getString(R.string.battery_app_usage));
mFooterPreference = screen.findPreference(KEY_FOOTER_PREF);
// Removes footer first until usage data is loaded to avoid flashing.
if (mFooterPreference != null) {
@@ -249,17 +246,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
return true;
}
@Override
public void onSelect(int trapezoidIndex) {
Log.d(TAG, "onChartSelect:" + trapezoidIndex);
refreshUi(trapezoidIndex, /*isForce=*/ false);
mMetricsFeatureProvider.action(
mPrefContext,
trapezoidIndex == BatteryChartView.SELECTED_INDEX_ALL
? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
: SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
}
@Override
public void onExpand(boolean isExpanded) {
mIsExpanded = isExpanded;
@@ -272,81 +258,120 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
void setBatteryHistoryMap(
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
// Resets all battery history data relative variables.
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
mBatteryIndexedMap = null;
mBatteryHistoryKeys = null;
mBatteryHistoryLevels = null;
addFooterPreferenceIfNeeded(false);
Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null"
: ("size=" + batteryHistoryMap.size())));
final BatteryLevelData batteryLevelData =
DataProcessor.getBatteryLevelData(mContext, mHandler, batteryHistoryMap,
batteryUsageMap -> {
mBatteryUsageMap = batteryUsageMap;
refreshUi();
});
Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
if (batteryLevelData == null) {
mDailyTimestampFullTexts = null;
mDailyViewModel = null;
mHourlyViewModels = null;
refreshUi();
return;
}
mBatteryHistoryKeys = getBatteryHistoryKeys(batteryHistoryMap);
mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE];
for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) {
final long timestamp = mBatteryHistoryKeys[index * 2];
final Map<String, BatteryHistEntry> entryMap = batteryHistoryMap.get(timestamp);
if (entryMap == null || entryMap.isEmpty()) {
Log.e(TAG, "abnormal entry list in the timestamp:"
+ ConvertUtils.utcToLocalTime(mPrefContext, timestamp));
continue;
mDailyTimestampFullTexts = generateTimestampDayOfWeekTexts(
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
/* isAbbreviation= */ false);
mDailyViewModel = new BatteryChartViewModel(
batteryLevelData.getDailyBatteryLevels().getLevels(),
generateTimestampDayOfWeekTexts(
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
/* isAbbreviation= */ true),
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
mHourlyViewModels = new ArrayList<>();
for (BatteryLevelData.PeriodBatteryLevelData hourlyBatteryLevelsPerDay :
batteryLevelData.getHourlyBatteryLevelsPerDay()) {
mHourlyViewModels.add(new BatteryChartViewModel(
hourlyBatteryLevelsPerDay.getLevels(),
generateTimestampHourTexts(
mContext, hourlyBatteryLevelsPerDay.getTimestamps()),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
// Averages the battery level in each time slot to avoid corner conditions.
float batteryLevelCounter = 0;
for (BatteryHistEntry entry : entryMap.values()) {
batteryLevelCounter += entry.mBatteryLevel;
}
mBatteryHistoryLevels[index] =
Math.round(batteryLevelCounter / entryMap.size());
}
forceRefreshUi();
Log.d(TAG, String.format(
"setBatteryHistoryMap() size=%d key=%s\nlevels=%s",
batteryHistoryMap.size(),
ConvertUtils.utcToLocalTime(mPrefContext,
mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1]),
Arrays.toString(mBatteryHistoryLevels)));
// Loads item icon and label in the background.
new LoadAllItemsInfoTask(batteryHistoryMap).execute();
refreshUi();
}
void setBatteryChartView(final BatteryChartView batteryChartView) {
if (mBatteryChartView != batteryChartView) {
mHandler.post(() -> setBatteryChartViewInner(batteryChartView));
void setBatteryChartView(@NonNull final BatteryChartView dailyChartView,
@NonNull final BatteryChartView hourlyChartView) {
if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) {
mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView));
}
}
private void setBatteryChartViewInner(final BatteryChartView batteryChartView) {
mBatteryChartView = batteryChartView;
mBatteryChartView.setOnSelectListener(this);
forceRefreshUi();
private void setBatteryChartViewInner(@NonNull final BatteryChartView dailyChartView,
@NonNull final BatteryChartView hourlyChartView) {
mDailyChartView = dailyChartView;
mDailyChartView.setOnSelectListener(trapezoidIndex -> {
if (mDailyChartIndex == trapezoidIndex) {
return;
}
private void forceRefreshUi() {
final int refreshIndex =
mTrapezoidIndex == BatteryChartView.SELECTED_INDEX_INVALID
? BatteryChartView.SELECTED_INDEX_ALL
: mTrapezoidIndex;
if (mBatteryChartView != null) {
mBatteryChartView.setLevels(mBatteryHistoryLevels);
mBatteryChartView.setSelectedIndex(refreshIndex);
setTimestampLabel();
Log.d(TAG, "onDailyChartSelect:" + trapezoidIndex);
mDailyChartIndex = trapezoidIndex;
mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
refreshUi();
// TODO: Change to log daily data.
});
mHourlyChartView = hourlyChartView;
mHourlyChartView.setOnSelectListener(trapezoidIndex -> {
if (mHourlyChartIndex == trapezoidIndex) {
return;
}
refreshUi(refreshIndex, /*isForce=*/ true);
Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex);
mHourlyChartIndex = trapezoidIndex;
refreshUi();
mMetricsFeatureProvider.action(
mPrefContext,
trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
: SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
});
refreshUi();
}
@VisibleForTesting
boolean refreshUi(int trapezoidIndex, boolean isForce) {
// Invalid refresh condition.
if (mBatteryIndexedMap == null
|| mBatteryChartView == null
|| (mTrapezoidIndex == trapezoidIndex && !isForce)) {
boolean refreshUi() {
if (mDailyChartView == null || mHourlyChartView == null) {
// Chart views are not initialized.
return false;
}
if (mDailyViewModel == null || mHourlyViewModels == null) {
// Fail to get battery level data, show an empty hourly chart view.
mDailyChartView.setVisibility(View.GONE);
mHourlyChartView.setVisibility(View.VISIBLE);
mHourlyChartView.setViewModel(null);
removeAndCacheAllPrefs();
addFooterPreferenceIfNeeded(false);
return false;
}
if (mBatteryUsageMap == null) {
// Battery usage data is not ready, wait for data ready to refresh UI.
return false;
}
Log.d(TAG, String.format("refreshUi: index=%d size=%d isForce:%b",
trapezoidIndex, mBatteryIndexedMap.size(), isForce));
mTrapezoidIndex = trapezoidIndex;
if (isBatteryLevelDataInOneDay()) {
// Only 1 day data, hide the daily chart view.
mDailyChartView.setVisibility(View.GONE);
mDailyChartIndex = 0;
} else {
mDailyChartView.setVisibility(View.VISIBLE);
mDailyViewModel.setSelectedIndex(mDailyChartIndex);
mDailyChartView.setViewModel(mDailyViewModel);
}
if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
// Multiple days are selected, hide the hourly chart view.
mHourlyChartView.setVisibility(View.GONE);
} else {
mHourlyChartView.setVisibility(View.VISIBLE);
final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex);
hourlyViewModel.setSelectedIndex(mHourlyChartIndex);
mHourlyChartView.setViewModel(hourlyViewModel);
}
mHandler.post(() -> {
final long start = System.currentTimeMillis();
removeAndCacheAllPrefs();
@@ -359,43 +384,22 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
private void addAllPreferences() {
final List<BatteryDiffEntry> entries =
mBatteryIndexedMap.get(Integer.valueOf(mTrapezoidIndex));
addFooterPreferenceIfNeeded(entries != null && !entries.isEmpty());
if (entries == null) {
Log.w(TAG, "cannot find BatteryDiffEntry for:" + mTrapezoidIndex);
final BatteryDiffData batteryDiffData =
mBatteryUsageMap.get(mDailyChartIndex).get(mHourlyChartIndex);
addFooterPreferenceIfNeeded(batteryDiffData != null
&& (!batteryDiffData.getAppDiffEntryList().isEmpty()
|| !batteryDiffData.getSystemDiffEntryList().isEmpty()));
if (batteryDiffData == null) {
Log.w(TAG, "cannot find BatteryDiffEntry for daily_index: " + mDailyChartIndex
+ " hourly_index: " + mHourlyChartIndex);
return;
}
// Separates data into two groups and sort them individually.
final List<BatteryDiffEntry> appEntries = new ArrayList<>();
mSystemEntries.clear();
entries.forEach(entry -> {
final String packageName = entry.getPackageName();
if (!isValidToShowEntry(packageName)) {
Log.w(TAG, "ignore showing item:" + packageName);
return;
}
if (entry.isSystemEntry()) {
mSystemEntries.add(entry);
} else {
appEntries.add(entry);
}
// Validates the usage time if users click a specific slot.
if (mTrapezoidIndex >= 0) {
validateUsageTime(entry);
}
});
Collections.sort(appEntries, BatteryDiffEntry.COMPARATOR);
Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
Log.d(TAG, String.format("addAllPreferences() app=%d system=%d",
appEntries.size(), mSystemEntries.size()));
// Adds app entries to the list if it is not empty.
if (!appEntries.isEmpty()) {
addPreferenceToScreen(appEntries);
if (!batteryDiffData.getAppDiffEntryList().isEmpty()) {
addPreferenceToScreen(batteryDiffData.getAppDiffEntryList());
}
// Adds the expabable divider if we have system entries data.
if (!mSystemEntries.isEmpty()) {
if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) {
if (mExpandDividerPreference == null) {
mExpandDividerPreference = new ExpandDividerPreference(mPrefContext);
mExpandDividerPreference.setOnExpandListener(this);
@@ -469,11 +473,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
private void refreshExpandUi() {
final List<BatteryDiffEntry> systemEntries = mBatteryUsageMap.get(mDailyChartIndex).get(
mHourlyChartIndex).getSystemDiffEntryList();
if (mIsExpanded) {
addPreferenceToScreen(mSystemEntries);
addPreferenceToScreen(systemEntries);
} else {
// Removes and recycles all system entries to hide all of them.
for (BatteryDiffEntry entry : mSystemEntries) {
for (BatteryDiffEntry entry : systemEntries) {
final String prefKey = entry.mBatteryHistEntry.getKey();
final Preference pref = mAppListPrefGroup.findPreference(prefKey);
if (pref != null) {
@@ -499,11 +505,12 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
private String getSlotInformation(boolean isApp, String slotInformation) {
// TODO: Updates the right slot information from daily and hourly chart selection.
// Null means we show all information without a specific time slot.
if (slotInformation == null) {
return isApp
? mPrefContext.getString(R.string.battery_app_usage_for_past_24)
: mPrefContext.getString(R.string.battery_system_usage_for_past_24);
? mPrefContext.getString(R.string.battery_app_usage)
: mPrefContext.getString(R.string.battery_system_usage);
} else {
return isApp
? mPrefContext.getString(R.string.battery_app_usage_for, slotInformation)
@@ -511,17 +518,33 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
}
private String getSlotInformation() {
if (mTrapezoidIndex < 0) {
@VisibleForTesting
String getSlotInformation() {
if (mDailyTimestampFullTexts == null || mDailyViewModel == null
|| mHourlyViewModels == null) {
// No data
return null;
}
final String fromHour = ConvertUtils.utcToLocalTimeHour(mPrefContext,
mBatteryHistoryKeys[mTrapezoidIndex * 2], mIs24HourFormat);
final String toHour = ConvertUtils.utcToLocalTimeHour(mPrefContext,
mBatteryHistoryKeys[(mTrapezoidIndex + 1) * 2], mIs24HourFormat);
return mIs24HourFormat
? String.format("%s%s", fromHour, toHour)
: String.format("%s %s", fromHour, toHour);
if (isAllSelected()) {
return null;
}
final String selectedDayText = mDailyTimestampFullTexts.get(mDailyChartIndex);
if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
return selectedDayText;
}
final String fromHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
mHourlyChartIndex);
final String toHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
mHourlyChartIndex + 1);
final String selectedHourText =
String.format("%s%s%s", fromHourText, mIs24HourFormat ? "-" : " - ", toHourText);
if (isBatteryLevelDataInOneDay()) {
return selectedHourText;
}
return String.format("%s %s", selectedDayText, selectedHourText);
}
@VisibleForTesting
@@ -575,22 +598,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@VisibleForTesting
boolean isValidToShowSummary(String packageName) {
return !contains(packageName, mNotAllowShowSummaryPackages);
}
@VisibleForTesting
boolean isValidToShowEntry(String packageName) {
return !contains(packageName, mNotAllowShowEntryPackages);
}
@VisibleForTesting
void setTimestampLabel() {
if (mBatteryChartView == null || mBatteryHistoryKeys == null) {
return;
}
final long latestTimestamp =
mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1];
mBatteryChartView.setLatestTimestamp(latestTimestamp);
return !DataProcessor.contains(packageName, mNotAllowShowSummaryPackages);
}
private void addFooterPreferenceIfNeeded(boolean containAppItems) {
@@ -605,60 +613,65 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference));
}
private 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;
private boolean isBatteryLevelDataInOneDay() {
return mHourlyViewModels != null && mHourlyViewModels.size() == 1;
}
@VisibleForTesting
static boolean validateUsageTime(BatteryDiffEntry entry) {
final long foregroundUsageTimeInMs = entry.mForegroundUsageTimeInMs;
final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs;
final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs;
if (foregroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
|| backgroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
|| totalUsageTimeInMs > VALID_USAGE_TIME_DURATION) {
Log.e(TAG, "validateUsageTime() fail for\n" + entry);
return false;
private boolean isAllSelected() {
return (isBatteryLevelDataInOneDay()
|| mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL)
&& mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL;
}
return true;
private static List<String> generateTimestampDayOfWeekTexts(@NonNull final Context context,
@NonNull final List<Long> timestamps, final boolean isAbbreviation) {
final ArrayList<String> texts = new ArrayList<>();
for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeDayOfWeek(context, timestamp, isAbbreviation));
}
return texts;
}
private static List<String> generateTimestampHourTexts(
@NonNull final Context context, @NonNull final List<Long> timestamps) {
final boolean is24HourFormat = DateFormat.is24HourFormat(context);
final ArrayList<String> texts = new ArrayList<>();
for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeHour(context, timestamp, is24HourFormat));
}
return texts;
}
/** Used for {@link AppBatteryPreferenceController}. */
public static List<BatteryDiffEntry> getBatteryLast24HrUsageData(Context context) {
public static List<BatteryDiffEntry> getAppBatteryUsageData(Context context) {
final long start = System.currentTimeMillis();
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getBatteryHistory(context);
.getBatteryHistorySinceLastFullCharge(context);
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
return null;
}
Log.d(TAG, String.format("getBatteryLast24HrData() size=%d time=&d/ms",
Log.d(TAG, String.format("getBatterySinceLastFullChargeUsageData() size=%d time=%d/ms",
batteryHistoryMap.size(), (System.currentTimeMillis() - start)));
final Map<Integer, List<BatteryDiffEntry>> batteryIndexedMap =
ConvertUtils.getIndexedUsageMap(
context,
/*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1,
getBatteryHistoryKeys(batteryHistoryMap),
batteryHistoryMap,
/*purgeLowPercentageAndFakeData=*/ true);
return batteryIndexedMap.get(BatteryChartView.SELECTED_INDEX_ALL);
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageData =
DataProcessor.getBatteryUsageData(context, batteryHistoryMap);
return batteryUsageData == null
? null
: batteryUsageData
.get(BatteryChartViewModel.SELECTED_INDEX_ALL)
.get(BatteryChartViewModel.SELECTED_INDEX_ALL)
.getAppDiffEntryList();
}
/** Used for {@link AppBatteryPreferenceController}. */
public static BatteryDiffEntry getBatteryLast24HrUsageData(
public static BatteryDiffEntry getAppBatteryUsageData(
Context context, String packageName, int userId) {
if (packageName == null) {
return null;
}
final List<BatteryDiffEntry> entries = getBatteryLast24HrUsageData(context);
final List<BatteryDiffEntry> entries = getAppBatteryUsageData(context);
if (entries == null) {
return null;
}
@@ -673,65 +686,4 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
return null;
}
private static long[] getBatteryHistoryKeys(
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
final List<Long> batteryHistoryKeyList =
new ArrayList<>(batteryHistoryMap.keySet());
Collections.sort(batteryHistoryKeyList);
final long[] batteryHistoryKeys = new long[CHART_KEY_ARRAY_SIZE];
for (int index = 0; index < CHART_KEY_ARRAY_SIZE; index++) {
batteryHistoryKeys[index] = batteryHistoryKeyList.get(index);
}
return batteryHistoryKeys;
}
// Loads all items icon and label in the background.
private final class LoadAllItemsInfoTask
extends AsyncTask<Void, Void, Map<Integer, List<BatteryDiffEntry>>> {
private long[] mBatteryHistoryKeysCache;
private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
private LoadAllItemsInfoTask(
Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
this.mBatteryHistoryMap = batteryHistoryMap;
this.mBatteryHistoryKeysCache = mBatteryHistoryKeys;
}
@Override
protected Map<Integer, List<BatteryDiffEntry>> doInBackground(Void... voids) {
if (mPrefContext == null || mBatteryHistoryKeysCache == null) {
return null;
}
final long startTime = System.currentTimeMillis();
final Map<Integer, List<BatteryDiffEntry>> indexedUsageMap =
ConvertUtils.getIndexedUsageMap(
mPrefContext, /*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1,
mBatteryHistoryKeysCache, mBatteryHistoryMap,
/*purgeLowPercentageAndFakeData=*/ true);
// Pre-loads each BatteryDiffEntry relative icon and label for all slots.
for (List<BatteryDiffEntry> entries : indexedUsageMap.values()) {
entries.forEach(entry -> entry.loadLabelAndIcon());
}
Log.d(TAG, String.format("execute LoadAllItemsInfoTask in %d/ms",
(System.currentTimeMillis() - startTime)));
return indexedUsageMap;
}
@Override
protected void onPostExecute(
Map<Integer, List<BatteryDiffEntry>> indexedUsageMap) {
mBatteryHistoryMap = null;
mBatteryHistoryKeysCache = null;
if (indexedUsageMap == null) {
return;
}
// Posts results back to main thread to refresh UI.
mHandler.post(() -> {
mBatteryIndexedMap = indexedUsageMap;
forceRefreshUi();
});
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.Utils.formatPercentage;
import static java.lang.Math.round;
import static java.util.Objects.requireNonNull;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
@@ -29,8 +30,6 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Handler;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.HapticFeedbackConstants;
@@ -39,6 +38,7 @@ import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.widget.AppCompatImageView;
@@ -46,7 +46,7 @@ import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -58,36 +58,26 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
private static final int DEFAULT_TRAPEZOID_COUNT = 12;
private static final int DEFAULT_TIMESTAMP_COUNT = 4;
private static final int TIMESTAMP_GAPS_COUNT = DEFAULT_TIMESTAMP_COUNT - 1;
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
private static final long UPDATE_STATE_DELAYED_TIME = 500L;
/** Selects all trapezoid shapes. */
public static final int SELECTED_INDEX_ALL = -1;
public static final int SELECTED_INDEX_INVALID = -2;
/** A callback listener for selected group index is updated. */
public interface OnSelectListener {
/** The callback function for selected group index is updated. */
void onSelect(int trapezoidIndex);
}
private BatteryChartViewModel mViewModel;
private int mDividerWidth;
private int mDividerHeight;
private int mTrapezoidCount;
private float mTrapezoidVOffset;
private float mTrapezoidHOffset;
private boolean mIsSlotsClickabled;
private String[] mPercentages = getPercentages();
@VisibleForTesting
int mHoveredIndex = SELECTED_INDEX_INVALID;
@VisibleForTesting
int mSelectedIndex = SELECTED_INDEX_INVALID;
@VisibleForTesting
String[] mTimestamps;
int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
// Colors for drawing the trapezoid shape and dividers.
private int mTrapezoidColor;
@@ -98,25 +88,26 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private final Rect mIndent = new Rect();
private final Rect[] mPercentageBounds =
new Rect[]{new Rect(), new Rect(), new Rect()};
// For drawing the timestamp information.
private final Rect[] mTimestampsBounds =
new Rect[]{new Rect(), new Rect(), new Rect(), new Rect()};
// For drawing the axis label information.
private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
@VisibleForTesting
Handler mHandler = new Handler();
@VisibleForTesting
final Runnable mUpdateClickableStateRun = () -> updateClickableState();
private int[] mLevels;
private Paint mTextPaint;
private Paint mDividerPaint;
private Paint mTrapezoidPaint;
@VisibleForTesting
Paint mTrapezoidCurvePaint = null;
private TrapezoidSlot[] mTrapezoidSlots;
@VisibleForTesting
TrapezoidSlot[] mTrapezoidSlots;
// Records the location to calculate selected index.
private float mTouchUpEventX = Float.MIN_VALUE;
@VisibleForTesting
float mTouchUpEventX = Float.MIN_VALUE;
private BatteryChartView.OnSelectListener mOnSelectListener;
public BatteryChartView(Context context) {
@@ -128,57 +119,25 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
initializeColors(context);
// Registers the click event listener.
setOnClickListener(this);
setSelectedIndex(SELECTED_INDEX_ALL);
setTrapezoidCount(DEFAULT_TRAPEZOID_COUNT);
setClickable(false);
setLatestTimestamp(0);
requestLayout();
}
/** Sets the total trapezoid count for drawing. */
public void setTrapezoidCount(int trapezoidCount) {
Log.i(TAG, "trapezoidCount:" + trapezoidCount);
mTrapezoidCount = trapezoidCount;
mTrapezoidSlots = new TrapezoidSlot[trapezoidCount];
// Allocates the trapezoid slot array.
for (int index = 0; index < trapezoidCount; index++) {
mTrapezoidSlots[index] = new TrapezoidSlot();
}
/** Sets the data model of this view. */
public void setViewModel(BatteryChartViewModel viewModel) {
if (viewModel == null) {
mViewModel = null;
invalidate();
}
/** Sets all levels value to draw the trapezoid shape */
public void setLevels(int[] levels) {
Log.d(TAG, "setLevels() " + (levels == null ? "null" : levels.length));
if (levels == null) {
mLevels = null;
return;
}
// We should provide trapezoid count + 1 data to draw all trapezoids.
mLevels = levels.length == mTrapezoidCount + 1 ? levels : null;
setClickable(false);
invalidate();
if (mLevels == null) {
return;
}
// Sets the chart is clickable if there is at least one valid item in it.
for (int index = 0; index < mLevels.length - 1; index++) {
if (mLevels[index] != 0 && mLevels[index + 1] != 0) {
setClickable(true);
break;
}
}
}
/** Sets the selected group index to draw highlight effect. */
public void setSelectedIndex(int index) {
if (mSelectedIndex != index) {
mSelectedIndex = index;
invalidate();
// Callbacks to the listener if we have.
if (mOnSelectListener != null) {
mOnSelectListener.onSelect(mSelectedIndex);
}
}
Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.",
viewModel.size(), viewModel.selectedIndex()));
mViewModel = viewModel;
initializeAxisLabelsBounds();
initializeTrapezoidSlots(viewModel.size() - 1);
setClickable(hasAnyValidTrapezoid(viewModel));
requestLayout();
}
/** Sets the callback to monitor the selected group index. */
@@ -195,29 +154,6 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
} else {
mTextPaint = null;
}
setVisibility(View.VISIBLE);
requestLayout();
}
/** Sets the latest timestamp for drawing into x-axis information. */
public void setLatestTimestamp(long latestTimestamp) {
if (latestTimestamp == 0) {
latestTimestamp = Clock.systemUTC().millis();
}
if (mTimestamps == null) {
mTimestamps = new String[DEFAULT_TIMESTAMP_COUNT];
}
final long timeSlotOffset =
DateUtils.HOUR_IN_MILLIS * (/*total 24 hours*/ 24 / TIMESTAMP_GAPS_COUNT);
final boolean is24HourFormat = DateFormat.is24HourFormat(getContext());
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
mTimestamps[index] =
ConvertUtils.utcToLocalTimeHour(
getContext(),
latestTimestamp - (TIMESTAMP_GAPS_COUNT - index)
* timeSlotOffset,
is24HourFormat);
}
requestLayout();
}
@@ -226,6 +162,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Measures text bounds and updates indent configuration.
if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.LEFT);
for (int index = 0; index < mPercentages.length; index++) {
mTextPaint.getTextBounds(
mPercentages[index], 0, mPercentages[index].length(),
@@ -235,15 +172,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mIndent.top = mPercentageBounds[0].height();
mIndent.right = mPercentageBounds[0].width() + mTextPadding;
if (mTimestamps != null) {
int maxHeight = 0;
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
mTextPaint.getTextBounds(
mTimestamps[index], 0, mTimestamps[index].length(),
mTimestampsBounds[index]);
maxHeight = Math.max(maxHeight, mTimestampsBounds[index].height());
if (mViewModel != null) {
int maxTop = 0;
for (int index = 0; index < mViewModel.size(); index++) {
final String text = mViewModel.texts().get(index);
mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index));
maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top);
}
mIndent.bottom = maxHeight + round(mTextPadding * 1.5f);
mIndent.bottom = maxTop + round(mTextPadding * 2f);
}
Log.d(TAG, "setIndent:" + mPercentageBounds[0]);
} else {
@@ -254,7 +190,12 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// Before mLevels initialized, the count of trapezoids is unknown. Only draws the
// horizontal percentages and dividers.
drawHorizontalDividers(canvas);
if (mViewModel == null) {
return;
}
drawVerticalDividers(canvas);
drawTrapezoids(canvas);
}
@@ -294,7 +235,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
public void onHoverChanged(boolean hovered) {
super.onHoverChanged(hovered);
if (!hovered) {
mHoveredIndex = SELECTED_INDEX_INVALID; // reset
mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
invalidate();
}
}
@@ -307,15 +248,15 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
}
final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX);
// Ignores the click event if the level is zero.
if (trapezoidIndex == SELECTED_INDEX_INVALID
|| !isValidToDraw(trapezoidIndex)) {
if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
|| !isValidToDraw(mViewModel, trapezoidIndex)) {
return;
}
if (mOnSelectListener != null) {
// Selects all if users click the same trapezoid item two times.
if (trapezoidIndex == mSelectedIndex) {
setSelectedIndex(SELECTED_INDEX_ALL);
} else {
setSelectedIndex(trapezoidIndex);
mOnSelectListener.onSelect(
trapezoidIndex == mViewModel.selectedIndex()
? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex);
}
view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
}
@@ -364,8 +305,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2);
} else if (mIsSlotsClickabled) {
mTrapezoidCurvePaint = null;
// Sets levels again to force update the click state.
setLevels(mLevels);
// Sets view model again to force update the click state.
setViewModel(mViewModel);
}
invalidate();
}
@@ -380,6 +321,13 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
super.setClickable(clickable);
}
private void initializeTrapezoidSlots(int count) {
mTrapezoidSlots = new TrapezoidSlot[count];
for (int index = 0; index < mTrapezoidSlots.length; index++) {
mTrapezoidSlots[index] = new TrapezoidSlot();
}
}
private void initializeColors(Context context) {
setBackgroundColor(Color.TRANSPARENT);
mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
@@ -434,10 +382,10 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private void drawPercentage(Canvas canvas, int index, float offsetY) {
if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(
mPercentages[index],
getWidth() - mPercentageBounds[index].width()
- mPercentageBounds[index].left,
getWidth(),
offsetY + mPercentageBounds[index].height() * .5f,
mTextPaint);
}
@@ -445,9 +393,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private void drawVerticalDividers(Canvas canvas) {
final int width = getWidth() - mIndent.right;
final int dividerCount = mTrapezoidCount + 1;
final int dividerCount = mTrapezoidSlots.length + 1;
final float dividerSpace = dividerCount * mDividerWidth;
final float unitWidth = (width - dividerSpace) / (float) mTrapezoidCount;
final float unitWidth = (width - dividerSpace) / (float) mTrapezoidSlots.length;
final float bottomY = getHeight() - mIndent.bottom;
final float startY = bottomY - mDividerHeight;
final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * .5f;
@@ -463,66 +411,140 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
}
startX = nextX;
}
// Draws the timestamp slot information.
if (mTimestamps != null) {
final float[] xOffsets = new float[DEFAULT_TIMESTAMP_COUNT];
final float baselineX = mDividerWidth * .5f;
final float offsetX = mDividerWidth + unitWidth;
final int slotBarOffset = (/*total 12 bars*/ 12) / TIMESTAMP_GAPS_COUNT;
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
xOffsets[index] = baselineX + index * offsetX * slotBarOffset;
// Draws the axis label slot information.
if (mViewModel != null) {
final float baselineY = getHeight() - mTextPadding * 1.5f;
Rect[] axisLabelDisplayAreas;
switch (mViewModel.axisLabelPosition()) {
case CENTER_OF_TRAPEZOIDS:
axisLabelDisplayAreas = getAxisLabelDisplayAreas(
/* size= */ mViewModel.size() - 1,
/* baselineX= */ mDividerWidth + unitWidth * .5f,
/* offsetX= */ mDividerWidth + unitWidth,
baselineY,
/* shiftFirstAndLast= */ false);
break;
case BETWEEN_TRAPEZOIDS:
default:
axisLabelDisplayAreas = getAxisLabelDisplayAreas(
/* size= */ mViewModel.size(),
/* baselineX= */ mDividerWidth * .5f,
/* offsetX= */ mDividerWidth + unitWidth,
baselineY,
/* shiftFirstAndLast= */ true);
break;
}
drawTimestamp(canvas, xOffsets);
drawAxisLabels(canvas, axisLabelDisplayAreas, baselineY);
}
}
private void drawTimestamp(Canvas canvas, float[] xOffsets) {
// Draws the 1st timestamp info.
canvas.drawText(
mTimestamps[0],
xOffsets[0] - mTimestampsBounds[0].left,
getTimestampY(0), mTextPaint);
final int latestIndex = DEFAULT_TIMESTAMP_COUNT - 1;
// Draws the last timestamp info.
canvas.drawText(
mTimestamps[latestIndex],
xOffsets[latestIndex] - mTimestampsBounds[latestIndex].width()
- mTimestampsBounds[latestIndex].left,
getTimestampY(latestIndex), mTextPaint);
// Draws the rest of timestamp info since it is located in the center.
for (int index = 1; index <= DEFAULT_TIMESTAMP_COUNT - 2; index++) {
canvas.drawText(
mTimestamps[index],
xOffsets[index]
- (mTimestampsBounds[index].width() - mTimestampsBounds[index].left)
* .5f,
getTimestampY(index), mTextPaint);
/** Gets all the axis label texts displaying area positions if they are shown. */
private Rect[] getAxisLabelDisplayAreas(final int size, final float baselineX,
final float offsetX, final float baselineY, final boolean shiftFirstAndLast) {
final Rect[] result = new Rect[size];
for (int index = 0; index < result.length; index++) {
final float width = mAxisLabelsBounds.get(index).width();
float middle = baselineX + index * offsetX;
if (shiftFirstAndLast) {
if (index == 0) {
middle += width * .5f;
}
if (index == size - 1) {
middle -= width * .5f;
}
}
final float left = middle - width * .5f;
final float right = left + width;
final float top = baselineY + mAxisLabelsBounds.get(index).top;
final float bottom = top + mAxisLabelsBounds.get(index).height();
result[index] = new Rect(round(left), round(top), round(right), round(bottom));
}
return result;
}
private void drawAxisLabels(Canvas canvas, final Rect[] displayAreas, final float baselineY) {
final int lastIndex = displayAreas.length - 1;
// Suppose first and last labels are always able to draw.
drawAxisLabelText(canvas, 0, displayAreas[0], baselineY);
drawAxisLabelText(canvas, lastIndex, displayAreas[lastIndex], baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(canvas, displayAreas, 0, lastIndex, baselineY);
}
/**
* Recursively draws axis labels between the start index and the end index. If the inner number
* can be exactly divided into 2 parts, check and draw the middle index label and then
* recursively draw the 2 parts. Otherwise, divide into 3 parts. Check and draw the middle two
* labels and then recursively draw the 3 parts. If there are any overlaps, skip drawing and go
* back to the uplevel of the recursion.
*/
private void drawAxisLabelsBetweenStartIndexAndEndIndex(Canvas canvas,
final Rect[] displayAreas, final int startIndex, final int endIndex,
final float baselineY) {
if (endIndex - startIndex <= 1) {
return;
}
if ((endIndex - startIndex) % 2 == 0) {
int middleIndex = (startIndex + endIndex) / 2;
if (hasOverlap(displayAreas, startIndex, middleIndex)
|| hasOverlap(displayAreas, middleIndex, endIndex)) {
return;
}
drawAxisLabelText(canvas, middleIndex, displayAreas[middleIndex], baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, startIndex, middleIndex, baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, middleIndex, endIndex, baselineY);
} else {
int middleIndex1 = startIndex + round((endIndex - startIndex) / 3f);
int middleIndex2 = startIndex + round((endIndex - startIndex) * 2 / 3f);
if (hasOverlap(displayAreas, startIndex, middleIndex1)
|| hasOverlap(displayAreas, middleIndex1, middleIndex2)
|| hasOverlap(displayAreas, middleIndex2, endIndex)) {
return;
}
drawAxisLabelText(canvas, middleIndex1, displayAreas[middleIndex1], baselineY);
drawAxisLabelText(canvas, middleIndex2, displayAreas[middleIndex2], baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, startIndex, middleIndex1, baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, middleIndex1, middleIndex2, baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, middleIndex2, endIndex, baselineY);
}
}
private int getTimestampY(int index) {
return getHeight() - mTimestampsBounds[index].height()
+ (mTimestampsBounds[index].height() + mTimestampsBounds[index].top)
+ round(mTextPadding * 1.5f);
private boolean hasOverlap(
final Rect[] displayAreas, final int leftIndex, final int rightIndex) {
return displayAreas[leftIndex].right + mTextPadding * 2f > displayAreas[rightIndex].left;
}
private void drawAxisLabelText(
Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
mTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(
mViewModel.texts().get(index),
displayArea.centerX(),
baselineY,
mTextPaint);
}
private void drawTrapezoids(Canvas canvas) {
// Ignores invalid trapezoid data.
if (mLevels == null) {
if (mViewModel == null) {
return;
}
final float trapezoidBottom =
getHeight() - mIndent.bottom - mDividerHeight - mDividerWidth
- mTrapezoidVOffset;
final float availableSpace = trapezoidBottom - mDividerWidth * .5f - mIndent.top;
final float availableSpace =
trapezoidBottom - mDividerWidth * .5f - mIndent.top - mTrapezoidVOffset;
final float unitHeight = availableSpace / 100f;
// Draws all trapezoid shapes into the canvas.
final Path trapezoidPath = new Path();
Path trapezoidCurvePath = null;
for (int index = 0; index < mTrapezoidCount; index++) {
for (int index = 0; index < mTrapezoidSlots.length; index++) {
// Not draws the trapezoid for corner or not initialization cases.
if (!isValidToDraw(index)) {
if (!isValidToDraw(mViewModel, index)) {
if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
trapezoidCurvePath = null;
@@ -530,17 +552,18 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
continue;
}
// Configures the trapezoid paint color.
final int trapezoidColor =
!mIsSlotsClickabled
? mTrapezoidColor
: mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL
final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index
|| mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL)
? mTrapezoidSolidColor : mTrapezoidColor;
final boolean isHoverState =
mIsSlotsClickabled && mHoveredIndex == index && isValidToDraw(mHoveredIndex);
mIsSlotsClickabled && mHoveredIndex == index
&& isValidToDraw(mViewModel, mHoveredIndex);
mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor);
final float leftTop = round(trapezoidBottom - mLevels[index] * unitHeight);
final float rightTop = round(trapezoidBottom - mLevels[index + 1] * unitHeight);
final float leftTop = round(
trapezoidBottom - requireNonNull(mViewModel.levels().get(index)) * unitHeight);
final float rightTop = round(trapezoidBottom
- requireNonNull(mViewModel.levels().get(index + 1)) * unitHeight);
trapezoidPath.reset();
trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
@@ -579,15 +602,37 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
return index;
}
}
return SELECTED_INDEX_INVALID;
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
}
private boolean isValidToDraw(int trapezoidIndex) {
return mLevels != null
private void initializeAxisLabelsBounds() {
mAxisLabelsBounds.clear();
for (int i = 0; i < mViewModel.size(); i++) {
mAxisLabelsBounds.add(new Rect());
}
}
private static boolean isTrapezoidValid(
@NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel.levels().get(trapezoidIndex) != null
&& viewModel.levels().get(trapezoidIndex + 1) != null;
}
private static boolean isValidToDraw(BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel != null
&& trapezoidIndex >= 0
&& trapezoidIndex < mLevels.length - 1
&& mLevels[trapezoidIndex] != 0
&& mLevels[trapezoidIndex + 1] != 0;
&& trapezoidIndex < viewModel.size() - 1
&& isTrapezoidValid(viewModel, trapezoidIndex);
}
private static boolean hasAnyValidTrapezoid(@NonNull BatteryChartViewModel viewModel) {
// Sets the chart is clickable if there is at least one valid item in it.
for (int trapezoidIndex = 0; trapezoidIndex < viewModel.size() - 1; trapezoidIndex++) {
if (isTrapezoidValid(viewModel, trapezoidIndex)) {
return true;
}
}
return false;
}
private static String[] getPercentages() {
@@ -621,7 +666,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
}
// A container class for each trapezoid left and right location.
private static final class TrapezoidSlot {
@VisibleForTesting
static final class TrapezoidSlot {
public float mLeft;
public float mRight;

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batteryusage;
import androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/** The view model of {@code BatteryChartView} */
class BatteryChartViewModel {
private static final String TAG = "BatteryChartViewModel";
public static final int SELECTED_INDEX_ALL = -1;
public static final int SELECTED_INDEX_INVALID = -2;
// 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 = SELECTED_INDEX_ALL;
BatteryChartViewModel(
@NonNull List<Integer> levels, @NonNull List<String> texts,
@NonNull AxisLabelPosition axisLabelPosition) {
Preconditions.checkArgument(
levels.size() == texts.size() && levels.size() >= MIN_LEVELS_DATA_SIZE,
String.format(Locale.ENGLISH,
"Invalid BatteryChartViewModel levels.size: %d, texts.size: %d.",
levels.size(), texts.size()));
mLevels = levels;
mTexts = texts;
mAxisLabelPosition = axisLabelPosition;
}
public int size() {
return mLevels.size();
}
public List<Integer> levels() {
return mLevels;
}
public List<String> texts() {
return mTexts;
}
public AxisLabelPosition axisLabelPosition() {
return mAxisLabelPosition;
}
public int selectedIndex() {
return mSelectedIndex;
}
public void setSelectedIndex(int index) {
mSelectedIndex = index;
}
@Override
public int hashCode() {
return Objects.hash(mLevels, mTexts, mSelectedIndex, mAxisLabelPosition);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (!(other instanceof BatteryChartViewModel)) {
return false;
}
final BatteryChartViewModel batteryChartViewModel = (BatteryChartViewModel) other;
return Objects.equals(mLevels, batteryChartViewModel.mLevels)
&& Objects.equals(mTexts, batteryChartViewModel.mTexts)
&& mAxisLabelPosition == batteryChartViewModel.mAxisLabelPosition
&& mSelectedIndex == batteryChartViewModel.mSelectedIndex;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH,
"levels: %s,\ntexts: %s,\naxisLabelPosition: %s, selectedIndex: %d",
Objects.toString(mLevels), Objects.toString(mTexts), mAxisLabelPosition,
mSelectedIndex);
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batteryusage;
import 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. */
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(
@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() {
return mAppEntries;
}
public List<BatteryDiffEntry> getSystemDiffEntryList() {
return mSystemEntries;
}
// 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

@@ -43,6 +43,6 @@ public class BatteryHistoryLoader
public Map<Long, Map<String, BatteryHistEntry>> loadInBackground() {
final PowerUsageFeatureProvider powerUsageFeatureProvider =
FeatureFactory.getFactory(mContext).getPowerUsageFeatureProvider(mContext);
return powerUsageFeatureProvider.getBatteryHistory(mContext);
return powerUsageFeatureProvider.getBatteryHistorySinceLastFullCharge(mContext);
}
}

View File

@@ -49,7 +49,8 @@ public class BatteryHistoryPreference extends Preference {
private TextView mSummaryView;
private CharSequence mSummaryContent;
private BatteryChartView mBatteryChartView;
private BatteryChartView mDailyChartView;
private BatteryChartView mHourlyChartView;
private BatteryChartPreferenceController mChartPreferenceController;
public BatteryHistoryPreference(Context context, AttributeSet attrs) {
@@ -92,8 +93,8 @@ public class BatteryHistoryPreference extends Preference {
void setChartPreferenceController(BatteryChartPreferenceController controller) {
mChartPreferenceController = controller;
if (mBatteryChartView != null) {
mChartPreferenceController.setBatteryChartView(mBatteryChartView);
if (mDailyChartView != null && mHourlyChartView != null) {
mChartPreferenceController.setBatteryChartView(mDailyChartView, mHourlyChartView);
}
}
@@ -105,11 +106,14 @@ public class BatteryHistoryPreference extends Preference {
return;
}
if (mIsChartGraphEnabled) {
mBatteryChartView = (BatteryChartView) view.findViewById(R.id.battery_chart);
mBatteryChartView.setCompanionTextView(
mDailyChartView = (BatteryChartView) view.findViewById(R.id.daily_battery_chart);
mDailyChartView.setCompanionTextView(
(TextView) view.findViewById(R.id.companion_text));
mHourlyChartView = (BatteryChartView) view.findViewById(R.id.hourly_battery_chart);
mHourlyChartView.setCompanionTextView(
(TextView) view.findViewById(R.id.companion_text));
if (mChartPreferenceController != null) {
mChartPreferenceController.setBatteryChartView(mBatteryChartView);
mChartPreferenceController.setBatteryChartView(mDailyChartView, mHourlyChartView);
}
} else {
final TextView chargeView = (TextView) view.findViewById(R.id.charge);

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batteryusage;
import androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/** Wraps the battery timestamp and level data used for battery usage chart. */
public final class BatteryLevelData {
/** A container for the battery timestamp and level data. */
public static final class PeriodBatteryLevelData {
// The length of mTimestamps and mLevels must be the same. mLevels[index] might be null when
// there is no level data for the corresponding timestamp.
private final List<Long> mTimestamps;
private final List<Integer> mLevels;
public PeriodBatteryLevelData(
@NonNull List<Long> timestamps, @NonNull List<Integer> levels) {
Preconditions.checkArgument(timestamps.size() == levels.size(),
/* errorMessage= */ "Timestamp: " + timestamps.size() + ", Level: "
+ levels.size());
mTimestamps = timestamps;
mLevels = levels;
}
public List<Long> getTimestamps() {
return mTimestamps;
}
public List<Integer> getLevels() {
return mLevels;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH, "timestamps: %s; levels: %s",
Objects.toString(mTimestamps), Objects.toString(mLevels));
}
}
/**
* There could be 2 cases for the daily battery levels:
* 1) length is 2: The usage data is within 1 day. Only contains start and end data, such as
* data of 2022-01-01 06:00 and 2022-01-01 16:00.
* 2) length > 2: The usage data is more than 1 days. The data should be the start, end and 0am
* data of every day between the start and end, such as data of 2022-01-01 06:00,
* 2022-01-02 00:00, 2022-01-03 00:00 and 2022-01-03 08:00.
*/
private final PeriodBatteryLevelData mDailyBatteryLevels;
// The size of hourly data must be the size of daily data - 1.
private final List<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
public BatteryLevelData(
@NonNull PeriodBatteryLevelData dailyBatteryLevels,
@NonNull List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) {
final long dailySize = dailyBatteryLevels.getTimestamps().size();
final long hourlySize = hourlyBatteryLevelsPerDay.size();
Preconditions.checkArgument(hourlySize == dailySize - 1,
/* errorMessage= */ "DailySize: " + dailySize + ", HourlySize: " + hourlySize);
mDailyBatteryLevels = dailyBatteryLevels;
mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
}
public PeriodBatteryLevelData getDailyBatteryLevels() {
return mDailyBatteryLevels;
}
public List<PeriodBatteryLevelData> getHourlyBatteryLevelsPerDay() {
return mHourlyBatteryLevelsPerDay;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH,
"dailyBatteryLevels: %s; hourlyBatteryLevelsPerDay: %s",
Objects.toString(mDailyBatteryLevels),
Objects.toString(mHourlyBatteryLevelsPerDay));
}
}

View File

@@ -140,7 +140,7 @@ public final class ConvertUtils {
/** Converts UTC timestamp to local time hour data. */
public static String utcToLocalTimeHour(
Context context, long timestamp, boolean is24HourFormat) {
final Context context, final long timestamp, final boolean is24HourFormat) {
final Locale locale = getLocale(context);
// e.g. for 12-hour format: 9 pm
// e.g. for 24-hour format: 09:00
@@ -149,6 +149,15 @@ public final class ConvertUtils {
return DateFormat.format(pattern, timestamp).toString().toLowerCase(locale);
}
/** Converts UTC timestamp to local time day of week data. */
public static String utcToLocalTimeDayOfWeek(
final Context context, final long timestamp, final boolean isAbbreviation) {
final Locale locale = getLocale(context);
final String pattern = DateFormat.getBestDateTimePattern(locale,
isAbbreviation ? "E" : "EEEE");
return DateFormat.format(pattern, timestamp).toString();
}
/** Gets indexed battery usage data for each corresponding time slot. */
public static Map<Integer, List<BatteryDiffEntry>> getIndexedUsageMap(
final Context context,
@@ -267,7 +276,7 @@ public final class ConvertUtils {
diffEntry.setTotalConsumePower(totalConsumePower);
}
}
insert24HoursData(BatteryChartView.SELECTED_INDEX_ALL, resultMap);
insert24HoursData(BatteryChartViewModel.SELECTED_INDEX_ALL, resultMap);
resolveMultiUsersData(context, resultMap);
if (purgeLowPercentageAndFakeData) {
purgeLowPercentageAndFakeData(context, resultMap);

File diff suppressed because it is too large Load Diff

View File

@@ -259,10 +259,7 @@ public class PowerUsageSummary extends PowerUsageBase implements
@VisibleForTesting
void initPreference() {
mBatteryUsagePreference = findPreference(KEY_BATTERY_USAGE);
mBatteryUsagePreference.setSummary(
mPowerFeatureProvider.isChartGraphEnabled(getContext())
? getString(R.string.advanced_battery_preference_summary_with_hours)
: getString(R.string.advanced_battery_preference_summary));
mBatteryUsagePreference.setSummary(getString(R.string.advanced_battery_preference_summary));
mHelpPreference = findPreference(KEY_BATTERY_ERROR);
mHelpPreference.setVisible(false);

View File

@@ -162,7 +162,8 @@ public class AppBatteryPreferenceControllerTest {
mController.updateBatteryWithDiffEntry();
assertThat(mBatteryPreference.getSummary()).isEqualTo("No battery use for past 24 hours");
assertThat(mBatteryPreference.getSummary().toString()).isEqualTo(
"No battery use since last full charge");
}
@Test
@@ -175,7 +176,8 @@ public class AppBatteryPreferenceControllerTest {
mController.updateBatteryWithDiffEntry();
assertThat(mBatteryPreference.getSummary()).isEqualTo("60% use for past 24 hours");
assertThat(mBatteryPreference.getSummary().toString()).isEqualTo(
"60% use since last full charge");
}
@Test

View File

@@ -232,7 +232,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testGetPreferenceScreenResId_returnNewLayout() {
public void setPreferenceScreenResId_returnNewLayout() {
assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.power_usage_detail);
}
@@ -252,7 +252,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_HasAppEntry_BuildByAppEntry() {
public void initHeader_HasAppEntry_BuildByAppEntry() {
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
new InstantAppDataProvider() {
@Override
@@ -269,7 +269,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_HasAppEntry_InstantApp() {
public void initHeader_HasAppEntry_InstantApp() {
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
new InstantAppDataProvider() {
@Override
@@ -286,7 +286,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_noUsageTimeAndGraphDisabled_hasCorrectSummary() {
public void initHeader_noUsageTimeAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -304,7 +304,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_bgTwoMinFgZeroAndGraphDisabled_hasCorrectSummary() {
public void initHeader_bgTwoMinFgZeroAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -324,7 +324,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_bgLessThanAMinFgZeroAndGraphDisabled_hasCorrectSummary() {
public void initHeader_bgLessThanAMinFgZeroAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -345,7 +345,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_totalUsageLessThanAMinAndGraphDisabled_hasCorrectSummary() {
public void initHeader_totalUsageLessThanAMinAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -367,7 +367,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_TotalAMinutesBgLessThanAMinAndGraphDisabled_hasCorrectSummary() {
public void initHeader_TotalAMinutesBgLessThanAMinAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -387,7 +387,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_TotalAMinBackgroundZeroAndGraphDisabled_hasCorrectSummary() {
public void initHeader_TotalAMinBackgroundZeroAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
final long backgroundTimeZero = 0;
@@ -406,7 +406,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_fgTwoMinBgFourMinAndGraphDisabled_hasCorrectSummary() {
public void initHeader_fgTwoMinBgFourMinAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
final long backgroundTimeFourMinute = 240000;
@@ -424,7 +424,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_noUsageTime_hasCorrectSummary() {
public void initHeader_noUsageTime_hasCorrectSummary() {
Bundle bundle = new Bundle(2);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, /* value */ 0);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, /* value */ 0);
@@ -435,11 +435,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("No usage for past 24 hr");
.isEqualTo("No usage from last full charge");
}
@Test
public void testInitHeader_noUsageTimeButConsumedPower_hasEmptySummary() {
public void initHeader_noUsageTimeButConsumedPower_hasEmptySummary() {
Bundle bundle = new Bundle(3);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, /* value */ 0);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, /* value */ 0);
@@ -454,7 +454,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_backgroundTwoMinForegroundZero_hasCorrectSummary() {
public void initHeader_backgroundTwoMinForegroundZero_hasCorrectSummary() {
final long backgroundTimeTwoMinutes = 120000;
final long foregroundTimeZero = 0;
Bundle bundle = new Bundle(2);
@@ -467,11 +467,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("2 min background for past 24 hr");
.isEqualTo("2 min background from last full charge");
}
@Test
public void testInitHeader_backgroundLessThanAMinForegroundZero_hasCorrectSummary() {
public void initHeader_backgroundLessThanAMinForegroundZero_hasCorrectSummary() {
final long backgroundTimeLessThanAMinute = 59999;
final long foregroundTimeZero = 0;
Bundle bundle = new Bundle(2);
@@ -485,11 +485,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("Background less than a minute for past 24 hr");
.isEqualTo("Background less than a minute from last full charge");
}
@Test
public void testInitHeader_totalUsageLessThanAMin_hasCorrectSummary() {
public void initHeader_totalUsageLessThanAMin_hasCorrectSummary() {
final long backgroundTimeLessThanHalfMinute = 20000;
final long foregroundTimeLessThanHalfMinute = 20000;
Bundle bundle = new Bundle(2);
@@ -504,11 +504,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("Total less than a minute for past 24 hr");
.isEqualTo("Total less than a minute from last full charge");
}
@Test
public void testInitHeader_TotalAMinutesBackgroundLessThanAMin_hasCorrectSummary() {
public void initHeader_TotalAMinutesBackgroundLessThanAMin_hasCorrectSummary() {
final long backgroundTimeZero = 59999;
final long foregroundTimeTwoMinutes = 1;
Bundle bundle = new Bundle(2);
@@ -521,11 +521,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("1 min total • background less than a minute\nfor past 24 hr");
.isEqualTo("1 min total • background less than a minute\nfrom last full charge");
}
@Test
public void testInitHeader_TotalAMinBackgroundZero_hasCorrectSummary() {
public void initHeader_TotalAMinBackgroundZero_hasCorrectSummary() {
final long backgroundTimeZero = 0;
final long foregroundTimeAMinutes = 60000;
Bundle bundle = new Bundle(2);
@@ -538,11 +538,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("1 min total for past 24 hr");
.isEqualTo("1 min total from last full charge");
}
@Test
public void testInitHeader_foregroundTwoMinBackgroundFourMin_hasCorrectSummary() {
public void initHeader_foregroundTwoMinBackgroundFourMin_hasCorrectSummary() {
final long backgroundTimeFourMinute = 240000;
final long foregroundTimeTwoMinutes = 120000;
Bundle bundle = new Bundle(2);
@@ -555,11 +555,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("6 min total • 4 min background\nfor past 24 hr");
.isEqualTo("6 min total • 4 min background\nfrom last full charge");
}
@Test
public void testInitHeader_totalUsageLessThanAMinWithSlotTime_hasCorrectSummary() {
public void initHeader_totalUsageLessThanAMinWithSlotTime_hasCorrectSummary() {
final long backgroundTimeLessThanHalfMinute = 20000;
final long foregroundTimeLessThanHalfMinute = 20000;
Bundle bundle = new Bundle(3);
@@ -579,7 +579,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_TotalAMinBackgroundLessThanAMinWithSlotTime_hasCorrectSummary() {
public void initHeader_TotalAMinBackgroundLessThanAMinWithSlotTime_hasCorrectSummary() {
final long backgroundTimeZero = 59999;
final long foregroundTimeTwoMinutes = 1;
Bundle bundle = new Bundle(3);
@@ -597,7 +597,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_TotalAMinBackgroundZeroWithSlotTime_hasCorrectSummary() {
public void initHeader_TotalAMinBackgroundZeroWithSlotTime_hasCorrectSummary() {
final long backgroundTimeZero = 0;
final long foregroundTimeAMinutes = 60000;
Bundle bundle = new Bundle(3);
@@ -615,7 +615,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_foregroundTwoMinBackgroundFourMinWithSlotTime_hasCorrectSummary() {
public void initHeader_foregroundTwoMinBackgroundFourMinWithSlotTime_hasCorrectSummary() {
final long backgroundTimeFourMinute = 240000;
final long foregroundTimeTwoMinutes = 120000;
Bundle bundle = new Bundle(3);
@@ -633,7 +633,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_systemUidWithChartIsDisabled_nullSummary() {
public void initHeader_systemUidWithChartIsDisabled_nullSummary() {
Bundle bundle = new Bundle(3);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, 240000);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, 120000);
@@ -650,7 +650,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_systemUidWithChartIsEnabled_notNullSummary() {
public void initHeader_systemUidWithChartIsEnabled_notNullSummary() {
Bundle bundle = new Bundle(3);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, 240000);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, 120000);
@@ -665,7 +665,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_hasBasicData() {
public void startBatteryDetailPage_hasBasicData() {
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
mBatteryEntry, USAGE_PERCENT, /*isValidToShowSummary=*/ true);
@@ -679,7 +679,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_invalidToShowSummary_noFGBDData() {
public void startBatteryDetailPage_invalidToShowSummary_noFGBDData() {
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
mBatteryEntry, USAGE_PERCENT, /*isValidToShowSummary=*/ false);
@@ -693,7 +693,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_NormalApp() {
public void startBatteryDetailPage_NormalApp() {
when(mBatteryEntry.getDefaultPackageName()).thenReturn(PACKAGE_NAME[0]);
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
@@ -704,7 +704,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_SystemApp() {
public void startBatteryDetailPage_SystemApp() {
when(mBatteryEntry.getDefaultPackageName()).thenReturn(null);
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
@@ -716,7 +716,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_WorkApp() {
public void startBatteryDetailPage_WorkApp() {
final int appUid = 1010019;
doReturn(appUid).when(mBatteryEntry).getUid();
@@ -727,7 +727,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_typeUser_startByCurrentUser() {
public void startBatteryDetailPage_typeUser_startByCurrentUser() {
when(mBatteryEntry.isUserEntry()).thenReturn(true);
final int currentUser = 20;
@@ -739,7 +739,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_noBatteryUsage_hasBasicData() {
public void startBatteryDetailPage_noBatteryUsage_hasBasicData() {
final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment, PACKAGE_NAME[0]);
@@ -756,7 +756,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName() throws
public void startBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName() throws
PackageManager.NameNotFoundException {
doReturn(UID).when(mPackageManager).getPackageUid(PACKAGE_NAME[0], 0 /* no flag */);
@@ -796,7 +796,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitPreferenceForTriState_isSystemOrDefaultApp_hasCorrectString() {
public void initPreferenceForTriState_isSystemOrDefaultApp_hasCorrectString() {
when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true);
when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true);
@@ -807,7 +807,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitPreferenceForTriState_hasCorrectString() {
public void initPreferenceForTriState_hasCorrectString() {
when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true);
when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(false);
@@ -818,7 +818,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testOnRadioButtonClicked_clickOptimizePref_optimizePreferenceChecked() {
public void onRadioButtonClicked_clickOptimizePref_optimizePreferenceChecked() {
mOptimizePreference.setKey(KEY_PREF_OPTIMIZED);
mRestrictedPreference.setKey(KEY_PREF_RESTRICTED);
mUnrestrictedPreference.setKey(KEY_PREF_UNRESTRICTED);
@@ -830,7 +830,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testOnPause_optimizationModeChanged_logPreference() {
public void onPause_optimizationModeChanged_logPreference() {
final int mode = BatteryOptimizeUtils.MODE_RESTRICTED;
mFragment.mOptimizationMode = mode;
when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode);
@@ -849,7 +849,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testOnPause_optimizationModeIsNotChanged_notInvokeLogging() {
public void onPause_optimizationModeIsNotChanged_notInvokeLogging() {
final int mode = BatteryOptimizeUtils.MODE_OPTIMIZED;
mFragment.mOptimizationMode = mode;
when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode);

View File

@@ -18,23 +18,24 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.settings.SettingsEnums;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.LocaleList;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.LinearLayout;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -58,15 +59,15 @@ import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
@RunWith(RobolectricTestRunner.class)
public final class BatteryChartPreferenceControllerTest {
private static final String PREF_KEY = "pref_key";
private static final String PREF_SUMMARY = "fake preference summary";
private static final int DESIRED_HISTORY_SIZE =
BatteryChartPreferenceController.DESIRED_HISTORY_SIZE;
@Mock
private InstrumentedPreferenceFragment mFragment;
@@ -79,11 +80,15 @@ public final class BatteryChartPreferenceControllerTest {
@Mock
private BatteryHistEntry mBatteryHistEntry;
@Mock
private BatteryChartView mBatteryChartView;
private BatteryChartView mDailyChartView;
@Mock
private BatteryChartView mHourlyChartView;
@Mock
private PowerGaugePreference mPowerGaugePreference;
@Mock
private BatteryUtils mBatteryUtils;
@Mock
private LinearLayout.LayoutParams mLayoutParams;
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
@@ -96,6 +101,7 @@ public final class BatteryChartPreferenceControllerTest {
MockitoAnnotations.initMocks(this);
Locale.setDefault(new Locale("en_US"));
org.robolectric.shadows.ShadowSettings.set24HourTimeFormat(false);
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
mFeatureFactory = FakeFeatureFactory.setupForTest();
mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider;
mContext = spy(RuntimeEnvironment.application);
@@ -108,10 +114,12 @@ public final class BatteryChartPreferenceControllerTest {
doReturn(new String[]{"com.android.gms.persistent"})
.when(mFeatureFactory.powerUsageFeatureProvider)
.getHideApplicationEntries(mContext);
doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mPrefContext = mContext;
mBatteryChartPreferenceController.mAppListPrefGroup = mAppListGroup;
mBatteryChartPreferenceController.mBatteryChartView = mBatteryChartView;
mBatteryChartPreferenceController.mDailyChartView = mDailyChartView;
mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView;
mBatteryDiffEntry = new BatteryDiffEntry(
mContext,
/*foregroundUsageTimeInMs=*/ 1,
@@ -123,12 +131,10 @@ public final class BatteryChartPreferenceControllerTest {
BatteryDiffEntry.sResourceCache.put(
"fakeBatteryDiffEntryKey",
new BatteryEntry.NameAndIcon("fakeName", /*icon=*/ null, /*iconId=*/ 1));
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
}
@Test
public void testOnDestroy_activityIsChanging_clearBatteryEntryCache() {
public void onDestroy_activityIsChanging_clearBatteryEntryCache() {
doReturn(true).when(mSettingsActivity).isChangingConfigurations();
// Ensures the testing environment is correct.
assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
@@ -138,7 +144,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnDestroy_activityIsNotChanging_notClearBatteryEntryCache() {
public void onDestroy_activityIsNotChanging_notClearBatteryEntryCache() {
doReturn(false).when(mSettingsActivity).isChangingConfigurations();
// Ensures the testing environment is correct.
assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
@@ -148,7 +154,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnDestroy_clearPreferenceCache() {
public void onDestroy_clearPreferenceCache() {
// Ensures the testing environment is correct.
mBatteryChartPreferenceController.mPreferenceCache.put(
PREF_KEY, mPowerGaugePreference);
@@ -160,113 +166,135 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnDestroy_removeAllPreferenceFromPreferenceGroup() {
public void onDestroy_removeAllPreferenceFromPreferenceGroup() {
mBatteryChartPreferenceController.onDestroy();
verify(mAppListGroup).removeAll();
}
@Test
public void testSetBatteryHistoryMap_createExpectedKeysAndLevels() {
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
public void setBatteryChartViewModel_6Hours() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
// Verifies the created battery keys array.
for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
assertThat(mBatteryChartPreferenceController.mBatteryHistoryKeys[index])
// These values is are calculated by hand from createBatteryHistoryMap().
.isEqualTo(index + 1);
}
// Verifies the created battery levels array.
for (int index = 0; index < 13; index++) {
assertThat(mBatteryChartPreferenceController.mBatteryHistoryLevels[index])
// These values is are calculated by hand from createBatteryHistoryMap().
.isEqualTo(100 - index * 2);
}
assertThat(mBatteryChartPreferenceController.mBatteryIndexedMap).hasSize(13);
verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE);
verify(mHourlyChartView, atLeastOnce()).setVisibility(View.VISIBLE);
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 97, 95),
List.of("8 am", "10 am", "12 pm"),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
@Test
public void testSetBatteryHistoryMap_largeSize_createExpectedKeysAndLevels() {
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
public void setBatteryChartViewModel_60Hours() {
BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel(
List.of(100, 83, 59, 41),
List.of("Sat", "Sun", "Mon", "Mon"),
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
// Verifies the created battery keys array.
for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
assertThat(mBatteryChartPreferenceController.mBatteryHistoryKeys[index])
// These values is are calculated by hand from createBatteryHistoryMap().
.isEqualTo(index + 1);
}
// Verifies the created battery levels array.
for (int index = 0; index < 13; index++) {
assertThat(mBatteryChartPreferenceController.mBatteryHistoryLevels[index])
// These values is are calculated by hand from createBatteryHistoryMap().
.isEqualTo(100 - index * 2);
}
assertThat(mBatteryChartPreferenceController.mBatteryIndexedMap).hasSize(13);
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE);
verify(mHourlyChartView, atLeastOnce()).setVisibility(View.GONE);
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
reset(mDailyChartView);
reset(mHourlyChartView);
doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
mBatteryChartPreferenceController.mDailyChartIndex = 0;
mBatteryChartPreferenceController.refreshUi();
verify(mDailyChartView).setVisibility(View.VISIBLE);
verify(mHourlyChartView).setVisibility(View.VISIBLE);
expectedDailyViewModel.setSelectedIndex(0);
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
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.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
reset(mDailyChartView);
reset(mHourlyChartView);
doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
mBatteryChartPreferenceController.mDailyChartIndex = 1;
mBatteryChartPreferenceController.mHourlyChartIndex = 6;
mBatteryChartPreferenceController.refreshUi();
verify(mDailyChartView).setVisibility(View.VISIBLE);
verify(mHourlyChartView).setVisibility(View.VISIBLE);
expectedDailyViewModel.setSelectedIndex(1);
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
BatteryChartViewModel expectedHourlyViewModel = 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"),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS);
expectedHourlyViewModel.setSelectedIndex(6);
verify(mHourlyChartView).setViewModel(expectedHourlyViewModel);
reset(mDailyChartView);
reset(mHourlyChartView);
doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
mBatteryChartPreferenceController.mDailyChartIndex = 2;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.refreshUi();
verify(mDailyChartView).setVisibility(View.VISIBLE);
verify(mHourlyChartView).setVisibility(View.VISIBLE);
expectedDailyViewModel.setSelectedIndex(2);
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
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.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
@Test
public void testRefreshUi_batteryIndexedMapIsNull_ignoreRefresh() {
public void refreshUi_normalCase_returnTrue() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue();
}
@Test
public void refreshUi_batteryIndexedMapIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.setBatteryHistoryMap(null);
assertThat(mBatteryChartPreferenceController.refreshUi(
/*trapezoidIndex=*/ 1, /*isForce=*/ false)).isFalse();
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void testRefreshUi_batteryChartViewIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.mBatteryChartView = null;
assertThat(mBatteryChartPreferenceController.refreshUi(
/*trapezoidIndex=*/ 1, /*isForce=*/ false)).isFalse();
public void refreshUi_dailyChartViewIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.mDailyChartView = null;
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void testRefreshUi_trapezoidIndexIsNotChanged_ignoreRefresh() {
final int trapezoidIndex = 1;
mBatteryChartPreferenceController.mTrapezoidIndex = trapezoidIndex;
assertThat(mBatteryChartPreferenceController.refreshUi(
trapezoidIndex, /*isForce=*/ false)).isFalse();
public void refreshUi_hourlyChartViewIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.mHourlyChartView = null;
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void testRefreshUi_forceUpdate_refreshUi() {
final int trapezoidIndex = 1;
mBatteryChartPreferenceController.mTrapezoidIndex = trapezoidIndex;
assertThat(mBatteryChartPreferenceController.refreshUi(
trapezoidIndex, /*isForce=*/ true)).isTrue();
}
@Test
public void testForceRefreshUi_updateTrapezoidIndexIntoSelectAll() {
mBatteryChartPreferenceController.mTrapezoidIndex =
BatteryChartView.SELECTED_INDEX_INVALID;
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
assertThat(mBatteryChartPreferenceController.mTrapezoidIndex)
.isEqualTo(BatteryChartView.SELECTED_INDEX_ALL);
}
@Test
public void testRemoveAndCacheAllPrefs_emptyContent_ignoreRemoveAll() {
final int trapezoidIndex = 1;
public void removeAndCacheAllPrefs_emptyContent_ignoreRemoveAll() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
doReturn(0).when(mAppListGroup).getPreferenceCount();
mBatteryChartPreferenceController.refreshUi(
trapezoidIndex, /*isForce=*/ true);
mBatteryChartPreferenceController.refreshUi();
verify(mAppListGroup, never()).removeAll();
}
@Test
public void testRemoveAndCacheAllPrefs_buildCacheAndRemoveAllPreference() {
final int trapezoidIndex = 1;
public void removeAndCacheAllPrefs_buildCacheAndRemoveAllPreference() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
doReturn(1).when(mAppListGroup).getPreferenceCount();
doReturn(mPowerGaugePreference).when(mAppListGroup).getPreference(0);
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
doReturn(PREF_KEY).when(mPowerGaugePreference).getKey();
doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY);
// Ensures the testing data is correct.
assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty();
mBatteryChartPreferenceController.refreshUi(
trapezoidIndex, /*isForce=*/ true);
mBatteryChartPreferenceController.refreshUi();
assertThat(mBatteryChartPreferenceController.mPreferenceCache.get(PREF_KEY))
.isEqualTo(mPowerGaugePreference);
@@ -274,14 +302,14 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testAddPreferenceToScreen_emptyContent_ignoreAddPreference() {
public void addPreferenceToScreen_emptyContent_ignoreAddPreference() {
mBatteryChartPreferenceController.addPreferenceToScreen(
new ArrayList<BatteryDiffEntry>());
verify(mAppListGroup, never()).addPreference(any());
}
@Test
public void testAddPreferenceToScreen_addPreferenceIntoScreen() {
public void addPreferenceToScreen_addPreferenceIntoScreen() {
final String appLabel = "fake app label";
doReturn(1).when(mAppListGroup).getPreferenceCount();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
@@ -310,7 +338,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testAddPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() {
public void addPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() {
final String appLabel = "fake app label";
doReturn(1).when(mAppListGroup).getPreferenceCount();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
@@ -325,7 +353,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testHandlePreferenceTreeiClick_notPowerGaugePreference_returnFalse() {
public void handlePreferenceTreeClick_notPowerGaugePreference_returnFalse() {
assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick(mAppListGroup))
.isFalse();
@@ -336,7 +364,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testHandlePreferenceTreeClick_forAppEntry_returnTrue() {
public void handlePreferenceTreeClick_forAppEntry_returnTrue() {
doReturn(false).when(mBatteryHistEntry).isAppEntry();
doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
@@ -352,7 +380,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testHandlePreferenceTreeClick_forSystemEntry_returnTrue() {
public void handlePreferenceTreeClick_forSystemEntry_returnTrue() {
mBatteryChartPreferenceController.mBatteryUtils = mBatteryUtils;
doReturn(true).when(mBatteryHistEntry).isAppEntry();
doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
@@ -369,7 +397,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() {
public void setPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -381,7 +409,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setBackgroundUsageTimeOnly() {
public void setPreferenceSummary_setBackgroundUsageTimeOnly() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -393,7 +421,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setTotalUsageTimeLessThanAMinute() {
public void setPreferenceSummary_setTotalUsageTimeLessThanAMinute() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -405,7 +433,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() {
public void setPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -418,7 +446,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setTotalAndBackgroundUsageTime() {
public void setPreferenceSummary_setTotalAndBackgroundUsageTime() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -430,7 +458,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_notAllowShownPackage_setSummayAsNull() {
public void setPreferenceSummary_notAllowShownPackage_setSummayAsNull() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
final BatteryDiffEntry batteryDiffEntry =
@@ -445,36 +473,9 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testValidateUsageTime_returnTrueIfBatteryDiffEntryIsValid() {
assertThat(BatteryChartPreferenceController.validateUsageTime(
createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS,
/*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS)))
.isTrue();
}
@Test
public void testValidateUsageTime_foregroundTimeExceedThreshold_returnFalse() {
assertThat(BatteryChartPreferenceController.validateUsageTime(
createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ DateUtils.HOUR_IN_MILLIS * 3,
/*backgroundUsageTimeInMs=*/ 0)))
.isFalse();
}
@Test
public void testValidateUsageTime_backgroundTimeExceedThreshold_returnFalse() {
assertThat(BatteryChartPreferenceController.validateUsageTime(
createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ DateUtils.HOUR_IN_MILLIS * 3)))
.isFalse();
}
@Test
public void testOnExpand_expandedIsTrue_addSystemEntriesToPreferenceGroup() {
public void onExpand_expandedIsTrue_addSystemEntriesToPreferenceGroup() {
doReturn(1).when(mAppListGroup).getPreferenceCount();
mBatteryChartPreferenceController.mSystemEntries.add(mBatteryDiffEntry);
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
doReturn("label").when(mBatteryDiffEntry).getAppLabel();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
@@ -493,10 +494,10 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnExpand_expandedIsFalse_removeSystemEntriesFromPreferenceGroup() {
public void onExpand_expandedIsFalse_removeSystemEntriesFromPreferenceGroup() {
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY);
mBatteryChartPreferenceController.mSystemEntries.add(mBatteryDiffEntry);
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
// Verifies the cache is empty first.
assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty();
@@ -513,57 +514,17 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnSelect_selectSpecificTimeSlot_logMetric() {
mBatteryChartPreferenceController.onSelect(1 /*slot index*/);
verify(mMetricsFeatureProvider)
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
}
@Test
public void testOnSelect_selectAll_logMetric() {
mBatteryChartPreferenceController.onSelect(
BatteryChartView.SELECTED_INDEX_ALL /*slot index*/);
verify(mMetricsFeatureProvider)
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL);
}
@Test
public void testRefreshCategoryTitle_setHourIntoBothTitleTextView() {
mBatteryChartPreferenceController = createController();
setUpBatteryHistoryKeys();
mBatteryChartPreferenceController.mAppListPrefGroup =
spy(new PreferenceCategory(mContext));
mBatteryChartPreferenceController.mExpandDividerPreference =
spy(new ExpandDividerPreference(mContext));
// Simulates select the first slot.
mBatteryChartPreferenceController.mTrapezoidIndex = 0;
mBatteryChartPreferenceController.refreshCategoryTitle();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
// Verifies the title in the preference group.
verify(mBatteryChartPreferenceController.mAppListPrefGroup)
.setTitle(captor.capture());
assertThat(captor.getValue()).isNotEqualTo("App usage for past 24 hr");
// Verifies the title in the expandable divider.
captor = ArgumentCaptor.forClass(String.class);
verify(mBatteryChartPreferenceController.mExpandDividerPreference)
.setTitle(captor.capture());
assertThat(captor.getValue()).isNotEqualTo("System usage for past 24 hr");
}
@Test
public void testRefreshCategoryTitle_setLast24HrIntoBothTitleTextView() {
public void refreshCategoryTitle_setLastFullChargeIntoBothTitleTextView() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mAppListPrefGroup =
spy(new PreferenceCategory(mContext));
mBatteryChartPreferenceController.mExpandDividerPreference =
spy(new ExpandDividerPreference(mContext));
// Simulates select all condition.
mBatteryChartPreferenceController.mTrapezoidIndex =
BatteryChartView.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.mDailyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.refreshCategoryTitle();
@@ -572,76 +533,93 @@ public final class BatteryChartPreferenceControllerTest {
verify(mBatteryChartPreferenceController.mAppListPrefGroup)
.setTitle(captor.capture());
assertThat(captor.getValue())
.isEqualTo("App usage for past 24 hr");
.isEqualTo("App usage since last full charge");
// Verifies the title in the expandable divider.
captor = ArgumentCaptor.forClass(String.class);
verify(mBatteryChartPreferenceController.mExpandDividerPreference)
.setTitle(captor.capture());
assertThat(captor.getValue())
.isEqualTo("System usage for past 24 hr");
.isEqualTo("System usage since last full charge");
}
@Test
public void testSetTimestampLabel_nullBatteryHistoryKeys_ignore() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mBatteryHistoryKeys = null;
mBatteryChartPreferenceController.mBatteryChartView =
spy(new BatteryChartView(mContext));
mBatteryChartPreferenceController.setTimestampLabel();
public void selectedSlotText_selectAllDaysAllHours_returnNull() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
mBatteryChartPreferenceController.mDailyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
verify(mBatteryChartPreferenceController.mBatteryChartView, never())
.setLatestTimestamp(anyLong());
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
}
@Test
public void testSetTimestampLabel_setExpectedTimestampData() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mBatteryChartView =
spy(new BatteryChartView(mContext));
setUpBatteryHistoryKeys();
public void selectedSlotText_onlyOneDayDataSelectAllHours_returnNull() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mDailyChartIndex = 0;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.setTimestampLabel();
verify(mBatteryChartPreferenceController.mBatteryChartView)
.setLatestTimestamp(1619247636826L);
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
}
@Test
public void testSetTimestampLabel_withoutValidTimestamp_setExpectedTimestampData() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mBatteryChartView =
spy(new BatteryChartView(mContext));
mBatteryChartPreferenceController.mBatteryHistoryKeys = new long[]{0L};
public void selectedSlotText_selectADayAllHours_onlyDayText() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
mBatteryChartPreferenceController.mDailyChartIndex = 1;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.setTimestampLabel();
verify(mBatteryChartPreferenceController.mBatteryChartView)
.setLatestTimestamp(anyLong());
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo("Sunday");
}
@Test
public void testOnSaveInstanceState_restoreSelectedIndexAndExpandState() {
final int expectedIndex = 1;
public void selectedSlotText_onlyOneDayDataSelectAnHour_onlyHourText() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mDailyChartIndex = 0;
mBatteryChartPreferenceController.mHourlyChartIndex = 1;
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
"10 am - 12 pm");
}
@Test
public void selectedSlotText_SelectADayAnHour_dayAndHourText() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
mBatteryChartPreferenceController.mDailyChartIndex = 1;
mBatteryChartPreferenceController.mHourlyChartIndex = 8;
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
"Sunday 4 pm - 6 pm");
}
@Test
public void onSaveInstanceState_restoreSelectedIndexAndExpandState() {
final int expectedDailyIndex = 1;
final int expectedHourlyIndex = 2;
final boolean isExpanded = true;
final Bundle bundle = new Bundle();
mBatteryChartPreferenceController.mTrapezoidIndex = expectedIndex;
mBatteryChartPreferenceController.mDailyChartIndex = expectedDailyIndex;
mBatteryChartPreferenceController.mHourlyChartIndex = expectedHourlyIndex;
mBatteryChartPreferenceController.mIsExpanded = isExpanded;
mBatteryChartPreferenceController.onSaveInstanceState(bundle);
// Replaces the original controller with other values.
mBatteryChartPreferenceController.mTrapezoidIndex = -1;
mBatteryChartPreferenceController.mDailyChartIndex = -1;
mBatteryChartPreferenceController.mHourlyChartIndex = -1;
mBatteryChartPreferenceController.mIsExpanded = false;
mBatteryChartPreferenceController.onCreate(bundle);
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(25));
assertThat(mBatteryChartPreferenceController.mTrapezoidIndex)
.isEqualTo(expectedIndex);
assertThat(mBatteryChartPreferenceController.mDailyChartIndex)
.isEqualTo(expectedDailyIndex);
assertThat(mBatteryChartPreferenceController.mHourlyChartIndex)
.isEqualTo(expectedHourlyIndex);
assertThat(mBatteryChartPreferenceController.mIsExpanded).isTrue();
}
@Test
public void testIsValidToShowSummary_returnExpectedResult() {
public void isValidToShowSummary_returnExpectedResult() {
assertThat(mBatteryChartPreferenceController
.isValidToShowSummary("com.google.android.apps.scone"))
.isTrue();
@@ -652,31 +630,42 @@ public final class BatteryChartPreferenceControllerTest {
.isFalse();
}
@Test
public void testIsValidToShowEntry_returnExpectedResult() {
assertThat(mBatteryChartPreferenceController
.isValidToShowEntry("com.google.android.apps.scone"))
.isTrue();
// Verifies the items which are defined in the array list.
assertThat(mBatteryChartPreferenceController
.isValidToShowEntry("com.android.gms.persistent"))
.isFalse();
private static Long generateTimestamp(int index) {
// "2021-04-23 07:00:00 UTC" + index hours
return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS;
}
private static Map<Long, Map<String, BatteryHistEntry>> createBatteryHistoryMap() {
private static Map<Long, Map<String, BatteryHistEntry>> createBatteryHistoryMap(
int numOfHours) {
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
for (int index = 0; index < numOfHours; index++) {
final ContentValues values = new ContentValues();
values.put("batteryLevel", Integer.valueOf(100 - index));
values.put("consumePower", Integer.valueOf(100 - index));
final BatteryHistEntry entry = new BatteryHistEntry(values);
final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
entryMap.put("fake_entry_key" + index, entry);
batteryHistoryMap.put(Long.valueOf(index + 1), entryMap);
batteryHistoryMap.put(generateTimestamp(index), entryMap);
}
return batteryHistoryMap;
}
private Map<Integer, Map<Integer, BatteryDiffData>> createBatteryUsageMap() {
final int selectedAll = BatteryChartViewModel.SELECTED_INDEX_ALL;
return Map.of(
selectedAll, Map.of(
selectedAll, new BatteryDiffData(
Arrays.asList(mBatteryDiffEntry),
Arrays.asList(mBatteryDiffEntry))),
0, Map.of(
selectedAll, new BatteryDiffData(
Arrays.asList(mBatteryDiffEntry),
Arrays.asList(mBatteryDiffEntry)),
0, new BatteryDiffData(
Arrays.asList(mBatteryDiffEntry),
Arrays.asList(mBatteryDiffEntry))));
}
private BatteryDiffEntry createBatteryDiffEntry(
long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) {
return new BatteryDiffEntry(
@@ -684,13 +673,6 @@ public final class BatteryChartPreferenceControllerTest {
/*consumePower=*/ 0, mBatteryHistEntry);
}
private void setUpBatteryHistoryKeys() {
mBatteryChartPreferenceController.mBatteryHistoryKeys =
new long[]{1619196786769L, 0L, 1619247636826L};
ConvertUtils.utcToLocalTimeHour(
mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
}
private BatteryChartPreferenceController createController() {
final BatteryChartPreferenceController controller =
new BatteryChartPreferenceController(

View File

@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.os.LocaleList;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
@@ -41,6 +42,7 @@ import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@RunWith(RobolectricTestRunner.class)
@@ -55,6 +57,8 @@ public final class BatteryChartViewTest {
private AccessibilityServiceInfo mMockAccessibilityServiceInfo;
@Mock
private AccessibilityManager mMockAccessibilityManager;
@Mock
private View mMockView;
@Before
public void setUp() {
@@ -74,13 +78,13 @@ public final class BatteryChartViewTest {
}
@Test
public void testIsAccessibilityEnabled_disable_returnFalse() {
public void isAccessibilityEnabled_disable_returnFalse() {
doReturn(false).when(mMockAccessibilityManager).isEnabled();
assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse();
}
@Test
public void testIsAccessibilityEnabled_emptyInfo_returnFalse() {
public void isAccessibilityEnabled_emptyInfo_returnFalse() {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
doReturn(new ArrayList<AccessibilityServiceInfo>())
.when(mMockAccessibilityManager)
@@ -90,68 +94,70 @@ public final class BatteryChartViewTest {
}
@Test
public void testIsAccessibilityEnabled_validServiceId_returnTrue() {
public void isAccessibilityEnabled_validServiceId_returnTrue() {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isTrue();
}
@Test
public void testSetSelectedIndex_invokesCallback() {
public void onClick_invokesCallback() {
final int originalSelectedIndex = 2;
BatteryChartViewModel batteryChartViewModel = new BatteryChartViewModel(
List.of(90, 80, 70, 60), List.of("", "", "", ""),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS);
batteryChartViewModel.setSelectedIndex(originalSelectedIndex);
mBatteryChartView.setViewModel(batteryChartViewModel);
for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) {
mBatteryChartView.mTrapezoidSlots[i] = new BatteryChartView.TrapezoidSlot();
mBatteryChartView.mTrapezoidSlots[i].mLeft = i;
mBatteryChartView.mTrapezoidSlots[i].mRight = i + 0.5f;
}
final int[] selectedIndex = new int[1];
final int expectedIndex = 2;
mBatteryChartView.mSelectedIndex = 1;
mBatteryChartView.setOnSelectListener(
trapezoidIndex -> {
selectedIndex[0] = trapezoidIndex;
});
mBatteryChartView.setSelectedIndex(expectedIndex);
// Verify onClick() a different index 1.
mBatteryChartView.mTouchUpEventX = 1;
selectedIndex[0] = Integer.MIN_VALUE;
mBatteryChartView.onClick(mMockView);
assertThat(selectedIndex[0]).isEqualTo(1);
assertThat(mBatteryChartView.mSelectedIndex)
.isEqualTo(expectedIndex);
assertThat(selectedIndex[0]).isEqualTo(expectedIndex);
// Verify onClick() the same index 2.
mBatteryChartView.mTouchUpEventX = 2;
selectedIndex[0] = Integer.MIN_VALUE;
mBatteryChartView.onClick(mMockView);
assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewModel.SELECTED_INDEX_ALL);
}
@Test
public void testSetSelectedIndex_sameIndex_notInvokesCallback() {
final int[] selectedIndex = new int[1];
final int expectedIndex = 1;
mBatteryChartView.mSelectedIndex = expectedIndex;
mBatteryChartView.setOnSelectListener(
trapezoidIndex -> {
selectedIndex[0] = trapezoidIndex;
});
mBatteryChartView.setSelectedIndex(expectedIndex);
assertThat(selectedIndex[0]).isNotEqualTo(expectedIndex);
}
@Test
public void testClickable_isChartGraphSlotsEnabledIsFalse_notClickable() {
public void clickable_isChartGraphSlotsEnabledIsFalse_notClickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(false);
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isFalse();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
}
@Test
public void testClickable_accessibilityIsDisabled_clickable() {
public void clickable_accessibilityIsDisabled_clickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(false).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@Test
public void testClickable_accessibilityIsEnabledWithoutValidId_clickable() {
public void clickable_accessibilityIsEnabledWithoutValidId_clickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
@@ -161,30 +167,34 @@ public final class BatteryChartViewTest {
.getEnabledAccessibilityServiceList(anyInt());
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@Test
public void testClickable_accessibilityIsEnabledWithValidId_notClickable() {
public void clickable_accessibilityIsEnabledWithValidId_notClickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(true).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isFalse();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
}
@Test
public void testClickable_restoreFromNonClickableState() {
final int[] levels = new int[13];
for (int index = 0; index < levels.length; index++) {
levels[index] = index + 1;
public void clickable_restoreFromNonClickableState() {
final List<Integer> levels = new ArrayList<Integer>();
final List<String> texts = new ArrayList<String>();
for (int index = 0; index < 13; index++) {
levels.add(index + 1);
texts.add("");
}
mBatteryChartView.setTrapezoidCount(12);
mBatteryChartView.setLevels(levels);
mBatteryChartView.setViewModel(new BatteryChartViewModel(levels, texts,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
@@ -201,14 +211,14 @@ public final class BatteryChartViewTest {
}
@Test
public void testOnAttachedToWindow_addAccessibilityStateChangeListener() {
public void onAttachedToWindow_addAccessibilityStateChangeListener() {
mBatteryChartView.onAttachedToWindow();
verify(mMockAccessibilityManager)
.addAccessibilityStateChangeListener(mBatteryChartView);
}
@Test
public void testOnDetachedFromWindow_removeAccessibilityStateChangeListener() {
public void onDetachedFromWindow_removeAccessibilityStateChangeListener() {
mBatteryChartView.onAttachedToWindow();
mBatteryChartView.mHandler.postDelayed(
mBatteryChartView.mUpdateClickableStateRun, 1000);
@@ -223,7 +233,7 @@ public final class BatteryChartViewTest {
}
@Test
public void testOnAccessibilityStateChanged_postUpdateStateRunnable() {
public void onAccessibilityStateChanged_postUpdateStateRunnable() {
mBatteryChartView.mHandler = spy(mBatteryChartView.mHandler);
mBatteryChartView.onAccessibilityStateChanged(/*enabled=*/ true);

View File

@@ -53,7 +53,7 @@ public final class BatteryHistoryLoaderTest {
public void testLoadIBackground_returnsMapFromPowerFeatureProvider() {
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
doReturn(batteryHistoryMap).when(mFeatureFactory.powerUsageFeatureProvider)
.getBatteryHistory(mContext);
.getBatteryHistorySinceLastFullCharge(mContext);
assertThat(mBatteryHistoryLoader.loadInBackground())
.isSameInstanceAs(batteryHistoryMap);

View File

@@ -26,6 +26,7 @@ import android.os.BatteryManager;
import android.os.BatteryUsageStats;
import android.os.LocaleList;
import android.os.UserHandle;
import android.text.format.DateUtils;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
@@ -39,8 +40,8 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -173,7 +174,8 @@ public final class ConvertUtilsTest {
public void getIndexedUsageMap_returnsExpectedResult() {
// Creates the fake testing data.
final int timeSlotSize = 2;
final long[] batteryHistoryKeys = new long[]{101L, 102L, 103L, 104L, 105L};
final long[] batteryHistoryKeys = new long[]{generateTimestamp(0), generateTimestamp(1),
generateTimestamp(2), generateTimestamp(3), generateTimestamp(4)};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
new HashMap<>();
final BatteryHistEntry fakeEntry = createBatteryHistEntry(
@@ -270,11 +272,11 @@ public final class ConvertUtilsTest {
for (int index = 0; index < remainingSize; index++) {
batteryHistoryMap.put(105L + index + 1, new HashMap<>());
}
when(mPowerUsageFeatureProvider.getBatteryHistory(mContext))
when(mPowerUsageFeatureProvider.getBatteryHistorySinceLastFullCharge(mContext))
.thenReturn(batteryHistoryMap);
final List<BatteryDiffEntry> batteryDiffEntryList =
BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext);
BatteryChartPreferenceController.getAppBatteryUsageData(mContext);
assertThat(batteryDiffEntryList).isNotEmpty();
final BatteryDiffEntry resultEntry = batteryDiffEntryList.get(0);
@@ -472,4 +474,9 @@ public final class ConvertUtilsTest {
assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
}
private static Long generateTimestamp(int index) {
// "2021-04-23 07:00:00 UTC" + index hours
return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS;
}
}

View File

@@ -0,0 +1,950 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batteryusage;
import 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;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
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;
@RunWith(RobolectricTestRunner.class)
public class DataProcessorTest {
private static final String FAKE_ENTRY_KEY = "fake_entry_key";
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,
/*handler=*/ null,
/*batteryHistoryMap=*/ null,
/*asyncResponseDelegate=*/ null))
.isNull();
assertThat(DataProcessor.getBatteryLevelData(
mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null))
.isNull();
}
@Test
public void getBatteryLevelData_notEnoughData_returnNull() {
// The timestamps are within 1 hour.
final long[] timestamps = {1000000L, 2000000L, 3000000L};
final int[] levels = {100, 99, 98};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
assertThat(DataProcessor.getBatteryLevelData(
mContext, /*handler=*/ null, batteryHistoryMap, /*asyncResponseDelegate=*/ null))
.isNull();
}
@Test
public void getBatteryLevelData_returnExpectedResult() {
// Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00, 2022-01-01 02:00:00
final long[] timestamps = {1640966400000L, 1640970000000L, 1640973600000L};
final int[] levels = {100, 99, 98};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
final BatteryLevelData resultData =
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]);
final List<List<Long>> expectedHourlyTimestamps = List.of(expectedDailyTimestamps);
final List<List<Integer>> expectedHourlyLevels = List.of(expectedDailyLevels);
verifyExpectedBatteryLevelData(
resultData,
expectedDailyTimestamps,
expectedDailyLevels,
expectedHourlyTimestamps,
expectedHourlyLevels);
}
@Test
public void getHistoryMapWithExpectedTimestamps_emptyHistoryMap_returnEmptyMap() {
assertThat(DataProcessor
.getHistoryMapWithExpectedTimestamps(mContext, new HashMap<>()))
.isEmpty();
}
@Test
public void getHistoryMapWithExpectedTimestamps_returnExpectedMap() {
// Timezone GMT+8
final long[] timestamps = {
1640966700000L, // 2022-01-01 00:05:00
1640970180000L, // 2022-01-01 01:03:00
1640973840000L, // 2022-01-01 02:04:00
1640978100000L, // 2022-01-01 03:15:00
1640981400000L // 2022-01-01 04:10:00
};
final int[] levels = {100, 94, 90, 82, 50};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
final Map<Long, Map<String, BatteryHistEntry>> resultMap =
DataProcessor.getHistoryMapWithExpectedTimestamps(mContext, batteryHistoryMap);
// Timezone GMT+8
final long[] expectedTimestamps = {
1640966400000L, // 2022-01-01 00:00:00
1640970000000L, // 2022-01-01 01:00:00
1640973600000L, // 2022-01-01 02:00:00
1640977200000L, // 2022-01-01 03:00:00
1640980800000L // 2022-01-01 04:00:00
};
final int[] expectedLevels = {100, 94, 90, 84, 56};
assertThat(resultMap).hasSize(expectedLevels.length);
for (int index = 0; index < expectedLevels.length; index++) {
assertThat(resultMap.get(expectedTimestamps[index]).get(FAKE_ENTRY_KEY).mBatteryLevel)
.isEqualTo(expectedLevels[index]);
}
}
@Test
public void getLevelDataThroughProcessedHistoryMap_notEnoughData_returnNull() {
final long[] timestamps = {100L};
final int[] levels = {100};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
assertThat(
DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap))
.isNull();
}
@Test
public void getLevelDataThroughProcessedHistoryMap_OneDayData_returnExpectedResult() {
// Timezone GMT+8
final long[] timestamps = {
1640966400000L, // 2022-01-01 00:00:00
1640970000000L, // 2022-01-01 01:00:00
1640973600000L, // 2022-01-01 02:00:00
1640977200000L, // 2022-01-01 03:00:00
1640980800000L // 2022-01-01 04:00:00
};
final int[] levels = {100, 94, 90, 82, 50};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
final BatteryLevelData resultData =
DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap);
final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[4]);
final List<Integer> expectedDailyLevels = List.of(levels[0], levels[4]);
final List<List<Long>> expectedHourlyTimestamps = List.of(
List.of(timestamps[0], timestamps[2], timestamps[4])
);
final List<List<Integer>> expectedHourlyLevels = List.of(
List.of(levels[0], levels[2], levels[4])
);
verifyExpectedBatteryLevelData(
resultData,
expectedDailyTimestamps,
expectedDailyLevels,
expectedHourlyTimestamps,
expectedHourlyLevels);
}
@Test
public void getLevelDataThroughProcessedHistoryMap_MultipleDaysData_returnExpectedResult() {
// Timezone GMT+8
final long[] timestamps = {
1641038400000L, // 2022-01-01 20:00:00
1641060000000L, // 2022-01-02 02:00:00
1641067200000L, // 2022-01-02 04:00:00
1641081600000L, // 2022-01-02 08:00:00
};
final int[] levels = {100, 94, 90, 82};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
final BatteryLevelData resultData =
DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap);
final List<Long> expectedDailyTimestamps = List.of(
1641038400000L, // 2022-01-01 20:00:00
1641052800000L, // 2022-01-02 00:00:00
1641081600000L // 2022-01-02 08:00:00
);
final List<Integer> expectedDailyLevels = new ArrayList<>();
expectedDailyLevels.add(100);
expectedDailyLevels.add(null);
expectedDailyLevels.add(82);
final List<List<Long>> expectedHourlyTimestamps = List.of(
List.of(
1641038400000L, // 2022-01-01 20:00:00
1641045600000L, // 2022-01-01 22:00:00
1641052800000L // 2022-01-02 00:00:00
),
List.of(
1641052800000L, // 2022-01-02 00:00:00
1641060000000L, // 2022-01-02 02:00:00
1641067200000L, // 2022-01-02 04:00:00
1641074400000L, // 2022-01-02 06:00:00
1641081600000L // 2022-01-02 08:00:00
)
);
final List<Integer> expectedHourlyLevels1 = new ArrayList<>();
expectedHourlyLevels1.add(100);
expectedHourlyLevels1.add(null);
expectedHourlyLevels1.add(null);
final List<Integer> expectedHourlyLevels2 = new ArrayList<>();
expectedHourlyLevels2.add(null);
expectedHourlyLevels2.add(94);
expectedHourlyLevels2.add(90);
expectedHourlyLevels2.add(null);
expectedHourlyLevels2.add(82);
final List<List<Integer>> expectedHourlyLevels = List.of(
expectedHourlyLevels1,
expectedHourlyLevels2
);
verifyExpectedBatteryLevelData(
resultData,
expectedDailyTimestamps,
expectedDailyLevels,
expectedHourlyTimestamps,
expectedHourlyLevels);
}
@Test
public void getTimestampSlots_emptyRawList_returnEmptyList() {
final List<Long> resultList =
DataProcessor.getTimestampSlots(new ArrayList<>());
assertThat(resultList).isEmpty();
}
@Test
public void getTimestampSlots_startWithEvenHour_returnExpectedResult() {
final Calendar startCalendar = Calendar.getInstance();
startCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
final Calendar endCalendar = Calendar.getInstance();
endCalendar.set(2022, 6, 5, 22, 30, 50); // 2022-07-05 22:30:50
final Calendar expectedStartCalendar = Calendar.getInstance();
expectedStartCalendar.set(2022, 6, 5, 6, 0, 0); // 2022-07-05 06:00:00
final Calendar expectedEndCalendar = Calendar.getInstance();
expectedEndCalendar.set(2022, 6, 5, 22, 0, 0); // 2022-07-05 22:00:00
verifyExpectedTimestampSlots(
startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar);
}
@Test
public void getTimestampSlots_startWithOddHour_returnExpectedResult() {
final Calendar startCalendar = Calendar.getInstance();
startCalendar.set(2022, 6, 5, 5, 0, 50); // 2022-07-05 05:00:50
final Calendar endCalendar = Calendar.getInstance();
endCalendar.set(2022, 6, 6, 21, 00, 50); // 2022-07-06 21:00:50
final Calendar expectedStartCalendar = Calendar.getInstance();
expectedStartCalendar.set(2022, 6, 5, 6, 00, 00); // 2022-07-05 06:00:00
final Calendar expectedEndCalendar = Calendar.getInstance();
expectedEndCalendar.set(2022, 6, 6, 20, 00, 00); // 2022-07-06 20:00:00
verifyExpectedTimestampSlots(
startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar);
}
@Test
public void getDailyTimestamps_notEnoughData_returnEmptyList() {
assertThat(DataProcessor.getDailyTimestamps(new ArrayList<>())).isEmpty();
assertThat(DataProcessor.getDailyTimestamps(List.of(100L))).isEmpty();
}
@Test
public void getDailyTimestamps_OneDayData_returnExpectedList() {
// Timezone GMT+8
final List<Long> timestamps = List.of(
1640966400000L, // 2022-01-01 00:00:00
1640970000000L, // 2022-01-01 01:00:00
1640973600000L, // 2022-01-01 02:00:00
1640977200000L, // 2022-01-01 03:00:00
1640980800000L // 2022-01-01 04:00:00
);
final List<Long> expectedTimestamps = List.of(
1640966400000L, // 2022-01-01 00:00:00
1640980800000L // 2022-01-01 04:00:00
);
assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
}
@Test
public void getDailyTimestamps_MultipleDaysData_returnExpectedList() {
// Timezone GMT+8
final List<Long> timestamps = List.of(
1640988000000L, // 2022-01-01 06:00:00
1641060000000L, // 2022-01-02 02:00:00
1641160800000L, // 2022-01-03 06:00:00
1641254400000L // 2022-01-04 08:00:00
);
final List<Long> expectedTimestamps = List.of(
1640988000000L, // 2022-01-01 06:00:00
1641052800000L, // 2022-01-02 00:00:00
1641139200000L, // 2022-01-03 00:00:00
1641225600000L, // 2022-01-04 00:00:00
1641254400000L // 2022-01-04 08:00:00
);
assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
}
@Test
public void isFromFullCharge_emptyData_returnFalse() {
assertThat(DataProcessor.isFromFullCharge(null)).isFalse();
assertThat(DataProcessor.isFromFullCharge(new HashMap<>())).isFalse();
}
@Test
public void isFromFullCharge_notChargedData_returnFalse() {
final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
final ContentValues values = new ContentValues();
values.put("batteryLevel", 98);
final BatteryHistEntry entry = new BatteryHistEntry(values);
entryMap.put(FAKE_ENTRY_KEY, entry);
assertThat(DataProcessor.isFromFullCharge(entryMap)).isFalse();
}
@Test
public void isFromFullCharge_chargedData_returnTrue() {
final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
final ContentValues values = new ContentValues();
values.put("batteryLevel", 100);
final BatteryHistEntry entry = new BatteryHistEntry(values);
entryMap.put(FAKE_ENTRY_KEY, entry);
assertThat(DataProcessor.isFromFullCharge(entryMap)).isTrue();
}
@Test
public void findNearestTimestamp_returnExpectedResult() {
long[] results = DataProcessor.findNearestTimestamp(
Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 15L);
assertThat(results).isEqualTo(new long[] {10L, 20L});
results = DataProcessor.findNearestTimestamp(
Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 10L);
assertThat(results).isEqualTo(new long[] {10L, 10L});
results = DataProcessor.findNearestTimestamp(
Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 5L);
assertThat(results).isEqualTo(new long[] {0L, 10L});
results = DataProcessor.findNearestTimestamp(
Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 50L);
assertThat(results).isEqualTo(new long[] {40L, 0L});
}
@Test
public void getTimestampOfNextDay_returnExpectedResult() {
// 2021-02-28 06:00:00 => 2021-03-01 00:00:00
assertThat(DataProcessor.getTimestampOfNextDay(1614463200000L))
.isEqualTo(1614528000000L);
// 2021-12-31 16:00:00 => 2022-01-01 00:00:00
assertThat(DataProcessor.getTimestampOfNextDay(1640937600000L))
.isEqualTo(1640966400000L);
}
@Test
public void isForDailyChart_returnExpectedResult() {
assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ true, 0L)).isTrue();
// 2022-01-01 00:00:00
assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ false, 1640966400000L))
.isTrue();
// 2022-01-01 01:00:05
assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ false, 1640970005000L))
.isFalse();
}
@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_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<>();
for (int index = 0; index < timestamps.length; index++) {
final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
final ContentValues values = new ContentValues();
values.put(BatteryHistEntry.KEY_BATTERY_LEVEL, levels[index]);
final BatteryHistEntry entry = new BatteryHistEntry(values);
entryMap.put(FAKE_ENTRY_KEY, entry);
batteryHistoryMap.put(timestamps[index], entryMap);
}
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,
final List<Integer> expectedDailyLevels,
final List<List<Long>> expectedHourlyTimestamps,
final List<List<Integer>> expectedHourlyLevels) {
final BatteryLevelData.PeriodBatteryLevelData dailyResultData =
resultData.getDailyBatteryLevels();
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData =
resultData.getHourlyBatteryLevelsPerDay();
verifyExpectedDailyBatteryLevelData(
dailyResultData, expectedDailyTimestamps, expectedDailyLevels);
verifyExpectedHourlyBatteryLevelData(
hourlyResultData, expectedHourlyTimestamps, expectedHourlyLevels);
}
private static void verifyExpectedDailyBatteryLevelData(
final BatteryLevelData.PeriodBatteryLevelData dailyResultData,
final List<Long> expectedDailyTimestamps,
final List<Integer> expectedDailyLevels) {
assertThat(dailyResultData.getTimestamps()).isEqualTo(expectedDailyTimestamps);
assertThat(dailyResultData.getLevels()).isEqualTo(expectedDailyLevels);
}
private static void verifyExpectedHourlyBatteryLevelData(
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData,
final List<List<Long>> expectedHourlyTimestamps,
final List<List<Integer>> expectedHourlyLevels) {
final int expectedHourlySize = expectedHourlyTimestamps.size();
assertThat(hourlyResultData).hasSize(expectedHourlySize);
for (int dailyIndex = 0; dailyIndex < expectedHourlySize; dailyIndex++) {
assertThat(hourlyResultData.get(dailyIndex).getTimestamps())
.isEqualTo(expectedHourlyTimestamps.get(dailyIndex));
assertThat(hourlyResultData.get(dailyIndex).getLevels())
.isEqualTo(expectedHourlyLevels.get(dailyIndex));
}
}
private static void verifyExpectedTimestampSlots(
final Calendar start,
final Calendar end,
final Calendar expectedStart,
final Calendar expectedEnd) {
expectedStart.set(Calendar.MILLISECOND, 0);
expectedEnd.set(Calendar.MILLISECOND, 0);
final ArrayList<Long> timestampSlots = new ArrayList<>();
timestampSlots.add(start.getTimeInMillis());
timestampSlots.add(end.getTimeInMillis());
final List<Long> resultList =
DataProcessor.getTimestampSlots(timestampSlots);
for (int index = 0; index < resultList.size(); index++) {
final long expectedTimestamp =
expectedStart.getTimeInMillis() + index * DateUtils.HOUR_IN_MILLIS;
assertThat(resultList.get(index)).isEqualTo(expectedTimestamp);
}
assertThat(resultList.get(resultList.size() - 1))
.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);
}
}

View File

@@ -139,17 +139,7 @@ public class PowerUsageSummaryTest {
}
@Test
public void initPreference_chartGraphEnabled_hasCorrectSummary() {
mFragment.initPreference();
verify(mBatteryUsagePreference).setSummary("View usage for past 24 hours");
}
@Test
public void initPreference_chartGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mRealContext))
.thenReturn(false);
public void initPreference_hasCorrectSummary() {
mFragment.initPreference();
verify(mBatteryUsagePreference).setSummary("View usage from last full charge");