Switch Battery Usage Chart from V1 to V2.

Test: manual
Bug: 236101166
Change-Id: I9142c0d4e00dea3771777ba9aedeab07b635fa1a
This commit is contained in:
Zaiyue Xue
2022-07-15 15:54:47 +08:00
parent 6b806e31f9
commit 284e049cc1
17 changed files with 720 additions and 3018 deletions

View File

@@ -32,10 +32,20 @@
android:text="@string/battery_usage_chart_graph_hint_last_full_charge" /> android:text="@string/battery_usage_chart_graph_hint_last_full_charge" />
<com.android.settings.fuelgauge.batteryusage.BatteryChartView <com.android.settings.fuelgauge.batteryusage.BatteryChartView
android:id="@+id/battery_chart" android:id="@+id/daily_battery_chart"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="170dp" android:layout_height="170dp"
android:layout_marginBottom="6dp" android:layout_marginBottom="16dp"
android:visibility="invisible"
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="invisible" android:visibility="invisible"
android:contentDescription="@string/battery_usage_chart" android:contentDescription="@string/battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"

View File

@@ -473,7 +473,6 @@
<dimen name="chartview_trapezoid_radius">5dp</dimen> <dimen name="chartview_trapezoid_radius">5dp</dimen>
<dimen name="chartview_trapezoid_margin_start">1dp</dimen> <dimen name="chartview_trapezoid_margin_start">1dp</dimen>
<dimen name="chartview_trapezoid_margin_bottom">2dp</dimen> <dimen name="chartview_trapezoid_margin_bottom">2dp</dimen>
<dimen name="chartview_two_charts_margin">16dp</dimen>
<!-- Dimensions for Dream settings cards --> <!-- Dimensions for Dream settings cards -->
<dimen name="dream_item_min_column_width">174dp</dimen> <dimen name="dream_item_min_column_width">174dp</dimen>

View File

@@ -40,7 +40,7 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.fuelgauge.batteryusage.BatteryChartPreferenceControllerV2; import com.android.settings.fuelgauge.batteryusage.BatteryChartPreferenceController;
import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry; import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryEntry; import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryUsageStatsLoader; import com.android.settings.fuelgauge.batteryusage.BatteryUsageStatsLoader;
@@ -179,7 +179,7 @@ public class AppBatteryPreferenceController extends BasePreferenceController
return null; return null;
} }
final BatteryDiffEntry entry = final BatteryDiffEntry entry =
BatteryChartPreferenceControllerV2.getAppBatteryUsageData( BatteryChartPreferenceController.getAppBatteryUsageData(
mContext, mPackageName, mUserId); mContext, mPackageName, mUserId);
Log.d(TAG, "loadBatteryDiffEntries():\n" + entry); Log.d(TAG, "loadBatteryDiffEntries():\n" + entry);
return entry; return entry;

View File

@@ -20,7 +20,6 @@ import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@@ -28,7 +27,9 @@ import android.text.TextUtils;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.Log; import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceGroup;
@@ -53,8 +54,6 @@ import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.FooterPreference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -62,27 +61,23 @@ import java.util.Map;
/** Controls the update for chart graph and the list items. */ /** Controls the update for chart graph and the list items. */
public class BatteryChartPreferenceController extends AbstractPreferenceController public class BatteryChartPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy, implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy,
OnSaveInstanceState, BatteryChartView.OnSelectListener, OnResume, OnSaveInstanceState, OnResume, ExpandDividerPreference.OnExpandListener {
ExpandDividerPreference.OnExpandListener {
private static final String TAG = "BatteryChartPreferenceController"; private static final String TAG = "BatteryChartPreferenceController";
private static final String KEY_FOOTER_PREF = "battery_graph_footer"; private static final String KEY_FOOTER_PREF = "battery_graph_footer";
private static final String PACKAGE_NAME_NONE = "none"; 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_USAGE_TIME_DURATION = DateUtils.HOUR_IN_MILLIS * 2;
private static final long VALID_DIFF_DURATION = DateUtils.MINUTE_IN_MILLIS * 3; private static final long VALID_DIFF_DURATION = DateUtils.MINUTE_IN_MILLIS * 3;
// Keys for bundle instance to restore configurations. // Keys for bundle instance to restore configurations.
private static final String KEY_EXPAND_SYSTEM_INFO = "expand_system_info"; 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; private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
@VisibleForTesting @VisibleForTesting
Map<Integer, List<BatteryDiffEntry>> mBatteryIndexedMap; Map<Integer, Map<Integer, BatteryDiffData>> mBatteryUsageMap;
@VisibleForTesting @VisibleForTesting
Context mPrefContext; Context mPrefContext;
@@ -91,28 +86,34 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@VisibleForTesting @VisibleForTesting
PreferenceGroup mAppListPrefGroup; PreferenceGroup mAppListPrefGroup;
@VisibleForTesting @VisibleForTesting
BatteryChartView mBatteryChartView;
@VisibleForTesting
ExpandDividerPreference mExpandDividerPreference; ExpandDividerPreference mExpandDividerPreference;
@VisibleForTesting @VisibleForTesting
boolean mIsExpanded = false; 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 boolean mIsFooterPrefAdded = false;
private PreferenceScreen mPreferenceScreen; private PreferenceScreen mPreferenceScreen;
private FooterPreference mFooterPreference; 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 String mPreferenceKey;
private final SettingsActivity mActivity; private final SettingsActivity mActivity;
private final InstrumentedPreferenceFragment mFragment; private final InstrumentedPreferenceFragment mFragment;
private final CharSequence[] mNotAllowShowEntryPackages;
private final CharSequence[] mNotAllowShowSummaryPackages; private final CharSequence[] mNotAllowShowSummaryPackages;
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
private final Handler mHandler = new Handler(Looper.getMainLooper()); 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. // Preference cache to avoid create new instance each time.
@VisibleForTesting @VisibleForTesting
final Map<String, Preference> mPreferenceCache = new HashMap<>(); final Map<String, Preference> mPreferenceCache = new HashMap<>();
@VisibleForTesting
final List<BatteryDiffEntry> mSystemEntries = new ArrayList<>();
public BatteryChartPreferenceController( public BatteryChartPreferenceController(
Context context, String preferenceKey, Context context, String preferenceKey,
@@ -134,10 +133,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mIs24HourFormat = DateFormat.is24HourFormat(context); mIs24HourFormat = DateFormat.is24HourFormat(context);
mMetricsFeatureProvider = mMetricsFeatureProvider =
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(); FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
mNotAllowShowEntryPackages =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getHideApplicationEntries(context);
mNotAllowShowSummaryPackages = mNotAllowShowSummaryPackages =
FeatureFactory.getFactory(context) FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context) .getPowerUsageFeatureProvider(context)
@@ -152,12 +147,14 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
if (savedInstanceState == null) { if (savedInstanceState == null) {
return; return;
} }
mTrapezoidIndex = mDailyChartIndex =
savedInstanceState.getInt(KEY_CURRENT_TIME_SLOT, mTrapezoidIndex); savedInstanceState.getInt(KEY_DAILY_CHART_INDEX, mDailyChartIndex);
mHourlyChartIndex =
savedInstanceState.getInt(KEY_HOURLY_CHART_INDEX, mHourlyChartIndex);
mIsExpanded = mIsExpanded =
savedInstanceState.getBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded); savedInstanceState.getBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded);
Log.d(TAG, String.format("onCreate() slotIndex=%d isExpanded=%b", Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
mTrapezoidIndex, mIsExpanded)); mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
} }
@Override @Override
@@ -179,10 +176,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
if (savedInstance == null) { if (savedInstance == null) {
return; 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); savedInstance.putBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded);
Log.d(TAG, String.format("onSaveInstanceState() slotIndex=%d isExpanded=%b", Log.d(TAG, String.format("onSaveInstanceState() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
mTrapezoidIndex, mIsExpanded)); mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
} }
@Override @Override
@@ -204,8 +202,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mPrefContext = screen.getContext(); mPrefContext = screen.getContext();
mAppListPrefGroup = screen.findPreference(mPreferenceKey); mAppListPrefGroup = screen.findPreference(mPreferenceKey);
mAppListPrefGroup.setOrderingAsAdded(false); mAppListPrefGroup.setOrderingAsAdded(false);
mAppListPrefGroup.setTitle( mAppListPrefGroup.setTitle(mPrefContext.getString(R.string.battery_app_usage));
mPrefContext.getString(R.string.battery_app_usage_for_past_24));
mFooterPreference = screen.findPreference(KEY_FOOTER_PREF); mFooterPreference = screen.findPreference(KEY_FOOTER_PREF);
// Removes footer first until usage data is loaded to avoid flashing. // Removes footer first until usage data is loaded to avoid flashing.
if (mFooterPreference != null) { if (mFooterPreference != null) {
@@ -249,17 +246,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
return true; 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 @Override
public void onExpand(boolean isExpanded) { public void onExpand(boolean isExpanded) {
mIsExpanded = isExpanded; mIsExpanded = isExpanded;
@@ -272,81 +258,119 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
void setBatteryHistoryMap( void setBatteryHistoryMap(
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) { final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
// Resets all battery history data relative variables. Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null"
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { : ("size=" + batteryHistoryMap.size())));
mBatteryIndexedMap = null; final BatteryLevelData batteryLevelData =
mBatteryHistoryKeys = null; DataProcessor.getBatteryLevelData(mContext, mHandler, batteryHistoryMap,
mBatteryHistoryLevels = null; batteryUsageMap -> {
addFooterPreferenceIfNeeded(false); mBatteryUsageMap = batteryUsageMap;
refreshUi();
});
Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
if (batteryLevelData == null) {
mDailyTimestampFullTexts = null;
mDailyViewModel = null;
mHourlyViewModels = null;
refreshUi();
return; return;
} }
mBatteryHistoryKeys = getBatteryHistoryKeys(batteryHistoryMap); mDailyTimestampFullTexts = generateTimestampDayOfWeekTexts(
mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE]; mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) { /* isAbbreviation= */ false);
final long timestamp = mBatteryHistoryKeys[index * 2]; mDailyViewModel = new BatteryChartViewModel(
final Map<String, BatteryHistEntry> entryMap = batteryHistoryMap.get(timestamp); batteryLevelData.getDailyBatteryLevels().getLevels(),
if (entryMap == null || entryMap.isEmpty()) { generateTimestampDayOfWeekTexts(
Log.e(TAG, "abnormal entry list in the timestamp:" mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
+ ConvertUtils.utcToLocalTime(mPrefContext, timestamp)); /* isAbbreviation= */ true),
continue; 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));
}
refreshUi();
}
void setBatteryChartView(@NonNull final BatteryChartView dailyChartView,
@NonNull final BatteryChartView hourlyChartView) {
if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) {
mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView));
}
}
private void setBatteryChartViewInner(@NonNull final BatteryChartView dailyChartView,
@NonNull final BatteryChartView hourlyChartView) {
mDailyChartView = dailyChartView;
mDailyChartView.setOnSelectListener(trapezoidIndex -> {
if (mDailyChartIndex == trapezoidIndex) {
return;
} }
// Averages the battery level in each time slot to avoid corner conditions. Log.d(TAG, "onDailyChartSelect:" + trapezoidIndex);
float batteryLevelCounter = 0; mDailyChartIndex = trapezoidIndex;
for (BatteryHistEntry entry : entryMap.values()) { mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
batteryLevelCounter += entry.mBatteryLevel; refreshUi();
// TODO: Change to log daily data.
});
mHourlyChartView = hourlyChartView;
mHourlyChartView.setOnSelectListener(trapezoidIndex -> {
if (mHourlyChartIndex == trapezoidIndex) {
return;
} }
mBatteryHistoryLevels[index] = Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex);
Math.round(batteryLevelCounter / entryMap.size()); mHourlyChartIndex = trapezoidIndex;
} refreshUi();
forceRefreshUi(); mMetricsFeatureProvider.action(
Log.d(TAG, String.format( mPrefContext,
"setBatteryHistoryMap() size=%d key=%s\nlevels=%s", trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
batteryHistoryMap.size(), ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
ConvertUtils.utcToLocalTime(mPrefContext, : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1]), });
Arrays.toString(mBatteryHistoryLevels))); refreshUi();
// Loads item icon and label in the background.
new LoadAllItemsInfoTask(batteryHistoryMap).execute();
}
void setBatteryChartView(final BatteryChartView batteryChartView) {
if (mBatteryChartView != batteryChartView) {
mHandler.post(() -> setBatteryChartViewInner(batteryChartView));
}
}
private void setBatteryChartViewInner(final BatteryChartView batteryChartView) {
mBatteryChartView = batteryChartView;
mBatteryChartView.setOnSelectListener(this);
forceRefreshUi();
}
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();
}
refreshUi(refreshIndex, /*isForce=*/ true);
} }
@VisibleForTesting @VisibleForTesting
boolean refreshUi(int trapezoidIndex, boolean isForce) { boolean refreshUi() {
// Invalid refresh condition. if (mDailyChartView == null || mHourlyChartView == null) {
if (mBatteryIndexedMap == null // Chart views are not initialized.
|| mBatteryChartView == null return false;
|| (mTrapezoidIndex == trapezoidIndex && !isForce)) { }
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);
addFooterPreferenceIfNeeded(false);
return false;
}
if (mBatteryUsageMap == null) {
// Battery usage data is not ready, wait for data ready to refresh UI.
return false; 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(() -> { mHandler.post(() -> {
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
removeAndCacheAllPrefs(); removeAndCacheAllPrefs();
@@ -359,43 +383,22 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
} }
private void addAllPreferences() { private void addAllPreferences() {
final List<BatteryDiffEntry> entries = final BatteryDiffData batteryDiffData =
mBatteryIndexedMap.get(Integer.valueOf(mTrapezoidIndex)); mBatteryUsageMap.get(mDailyChartIndex).get(mHourlyChartIndex);
addFooterPreferenceIfNeeded(entries != null && !entries.isEmpty()); addFooterPreferenceIfNeeded(batteryDiffData != null
if (entries == null) { && (!batteryDiffData.getAppDiffEntryList().isEmpty()
Log.w(TAG, "cannot find BatteryDiffEntry for:" + mTrapezoidIndex); || !batteryDiffData.getSystemDiffEntryList().isEmpty()));
if (batteryDiffData == null) {
Log.w(TAG, "cannot find BatteryDiffEntry for daily_index: " + mDailyChartIndex
+ " hourly_index: " + mHourlyChartIndex);
return; 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. // Adds app entries to the list if it is not empty.
if (!appEntries.isEmpty()) { if (!batteryDiffData.getAppDiffEntryList().isEmpty()) {
addPreferenceToScreen(appEntries); addPreferenceToScreen(batteryDiffData.getAppDiffEntryList());
} }
// Adds the expabable divider if we have system entries data. // Adds the expabable divider if we have system entries data.
if (!mSystemEntries.isEmpty()) { if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) {
if (mExpandDividerPreference == null) { if (mExpandDividerPreference == null) {
mExpandDividerPreference = new ExpandDividerPreference(mPrefContext); mExpandDividerPreference = new ExpandDividerPreference(mPrefContext);
mExpandDividerPreference.setOnExpandListener(this); mExpandDividerPreference.setOnExpandListener(this);
@@ -469,11 +472,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
} }
private void refreshExpandUi() { private void refreshExpandUi() {
final List<BatteryDiffEntry> systemEntries = mBatteryUsageMap.get(mDailyChartIndex).get(
mHourlyChartIndex).getSystemDiffEntryList();
if (mIsExpanded) { if (mIsExpanded) {
addPreferenceToScreen(mSystemEntries); addPreferenceToScreen(systemEntries);
} else { } else {
// Removes and recycles all system entries to hide all of them. // 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 String prefKey = entry.mBatteryHistEntry.getKey();
final Preference pref = mAppListPrefGroup.findPreference(prefKey); final Preference pref = mAppListPrefGroup.findPreference(prefKey);
if (pref != null) { if (pref != null) {
@@ -499,11 +504,12 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
} }
private String getSlotInformation(boolean isApp, String slotInformation) { 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. // Null means we show all information without a specific time slot.
if (slotInformation == null) { if (slotInformation == null) {
return isApp return isApp
? mPrefContext.getString(R.string.battery_app_usage_for_past_24) ? mPrefContext.getString(R.string.battery_app_usage)
: mPrefContext.getString(R.string.battery_system_usage_for_past_24); : mPrefContext.getString(R.string.battery_system_usage);
} else { } else {
return isApp return isApp
? mPrefContext.getString(R.string.battery_app_usage_for, slotInformation) ? mPrefContext.getString(R.string.battery_app_usage_for, slotInformation)
@@ -511,17 +517,33 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
} }
} }
private String getSlotInformation() { @VisibleForTesting
if (mTrapezoidIndex < 0) { String getSlotInformation() {
if (mDailyTimestampFullTexts == null || mDailyViewModel == null
|| mHourlyViewModels == null) {
// No data
return null; return null;
} }
final String fromHour = ConvertUtils.utcToLocalTimeHour(mPrefContext, if (isAllSelected()) {
mBatteryHistoryKeys[mTrapezoidIndex * 2], mIs24HourFormat); return null;
final String toHour = ConvertUtils.utcToLocalTimeHour(mPrefContext, }
mBatteryHistoryKeys[(mTrapezoidIndex + 1) * 2], mIs24HourFormat);
return mIs24HourFormat final String selectedDayText = mDailyTimestampFullTexts.get(mDailyChartIndex);
? String.format("%s%s", fromHour, toHour) if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
: String.format("%s %s", fromHour, toHour); 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 @VisibleForTesting
@@ -575,22 +597,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@VisibleForTesting @VisibleForTesting
boolean isValidToShowSummary(String packageName) { boolean isValidToShowSummary(String packageName) {
return !contains(packageName, mNotAllowShowSummaryPackages); return !DataProcessor.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);
} }
private void addFooterPreferenceIfNeeded(boolean containAppItems) { private void addFooterPreferenceIfNeeded(boolean containAppItems) {
@@ -605,60 +612,65 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference)); mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference));
} }
private static boolean contains(String target, CharSequence[] packageNames) { private boolean isBatteryLevelDataInOneDay() {
if (target != null && packageNames != null) { return mHourlyViewModels != null && mHourlyViewModels.size() == 1;
for (CharSequence packageName : packageNames) {
if (TextUtils.equals(target, packageName)) {
return true;
}
}
}
return false;
} }
@VisibleForTesting private boolean isAllSelected() {
static boolean validateUsageTime(BatteryDiffEntry entry) { return (isBatteryLevelDataInOneDay()
final long foregroundUsageTimeInMs = entry.mForegroundUsageTimeInMs; || mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL)
final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs; && mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL;
final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs; }
if (foregroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
|| backgroundUsageTimeInMs > VALID_USAGE_TIME_DURATION private static List<String> generateTimestampDayOfWeekTexts(@NonNull final Context context,
|| totalUsageTimeInMs > VALID_USAGE_TIME_DURATION) { @NonNull final List<Long> timestamps, final boolean isAbbreviation) {
Log.e(TAG, "validateUsageTime() fail for\n" + entry); final ArrayList<String> texts = new ArrayList<>();
return false; for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeDayOfWeek(context, timestamp, isAbbreviation));
} }
return true; 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}. */ /** Used for {@link AppBatteryPreferenceController}. */
public static List<BatteryDiffEntry> getBatteryLast24HrUsageData(Context context) { public static List<BatteryDiffEntry> getAppBatteryUsageData(Context context) {
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
FeatureFactory.getFactory(context) FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context) .getPowerUsageFeatureProvider(context)
.getBatteryHistory(context); .getBatteryHistorySinceLastFullCharge(context);
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
return null; 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))); batteryHistoryMap.size(), (System.currentTimeMillis() - start)));
final Map<Integer, List<BatteryDiffEntry>> batteryIndexedMap =
ConvertUtils.getIndexedUsageMap( final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageData =
context, DataProcessor.getBatteryUsageData(context, batteryHistoryMap);
/*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1, return batteryUsageData == null
getBatteryHistoryKeys(batteryHistoryMap), ? null
batteryHistoryMap, : batteryUsageData
/*purgeLowPercentageAndFakeData=*/ true); .get(BatteryChartViewModel.SELECTED_INDEX_ALL)
return batteryIndexedMap.get(BatteryChartView.SELECTED_INDEX_ALL); .get(BatteryChartViewModel.SELECTED_INDEX_ALL)
.getAppDiffEntryList();
} }
/** Used for {@link AppBatteryPreferenceController}. */ /** Used for {@link AppBatteryPreferenceController}. */
public static BatteryDiffEntry getBatteryLast24HrUsageData( public static BatteryDiffEntry getAppBatteryUsageData(
Context context, String packageName, int userId) { Context context, String packageName, int userId) {
if (packageName == null) { if (packageName == null) {
return null; return null;
} }
final List<BatteryDiffEntry> entries = getBatteryLast24HrUsageData(context); final List<BatteryDiffEntry> entries = getAppBatteryUsageData(context);
if (entries == null) { if (entries == null) {
return null; return null;
} }
@@ -673,65 +685,4 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
} }
return null; 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

@@ -1,701 +0,0 @@
/*
* 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 android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreate;
import com.android.settingslib.core.lifecycle.events.OnDestroy;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.FooterPreference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Controls the update for chart graph and the list items. */
public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceController
implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy,
OnSaveInstanceState, OnResume, ExpandDividerPreference.OnExpandListener {
private static final String TAG = "BatteryChartPreferenceControllerV2";
private static final String KEY_FOOTER_PREF = "battery_graph_footer";
private static final String PACKAGE_NAME_NONE = "none";
private static final int TWENTY_FOUR_HOURS_TIME_SLOT_SIZE = 12;
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_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, Map<Integer, BatteryDiffData>> mBatteryUsageMap;
@VisibleForTesting
Context mPrefContext;
@VisibleForTesting
BatteryUtils mBatteryUtils;
@VisibleForTesting
PreferenceGroup mAppListPrefGroup;
@VisibleForTesting
ExpandDividerPreference mExpandDividerPreference;
@VisibleForTesting
boolean mIsExpanded = false;
@VisibleForTesting
BatteryChartViewV2 mDailyChartView;
@VisibleForTesting
BatteryChartViewV2 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[] mNotAllowShowSummaryPackages;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final Handler mHandler = new Handler(Looper.getMainLooper());
// Preference cache to avoid create new instance each time.
@VisibleForTesting
final Map<String, Preference> mPreferenceCache = new HashMap<>();
public BatteryChartPreferenceControllerV2(
Context context, String preferenceKey,
Lifecycle lifecycle, SettingsActivity activity,
InstrumentedPreferenceFragment fragment) {
super(context);
mActivity = activity;
mFragment = fragment;
mPreferenceKey = preferenceKey;
mIs24HourFormat = DateFormat.is24HourFormat(context);
mMetricsFeatureProvider =
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
mNotAllowShowSummaryPackages =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getHideApplicationSummary(context);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
return;
}
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() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
}
@Override
public void onResume() {
final int currentUiMode =
mContext.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK;
if (sUiMode != currentUiMode) {
sUiMode = currentUiMode;
BatteryDiffEntry.clearCache();
Log.d(TAG, "clear icon and label cache since uiMode is changed");
}
mIs24HourFormat = DateFormat.is24HourFormat(mContext);
mMetricsFeatureProvider.action(mPrefContext, SettingsEnums.OPEN_BATTERY_USAGE);
}
@Override
public void onSaveInstanceState(Bundle savedInstance) {
if (savedInstance == null) {
return;
}
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() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
}
@Override
public void onDestroy() {
if (mActivity.isChangingConfigurations()) {
BatteryDiffEntry.clearCache();
}
mHandler.removeCallbacksAndMessages(/*token=*/ null);
mPreferenceCache.clear();
if (mAppListPrefGroup != null) {
mAppListPrefGroup.removeAll();
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreferenceScreen = screen;
mPrefContext = screen.getContext();
mAppListPrefGroup = screen.findPreference(mPreferenceKey);
mAppListPrefGroup.setOrderingAsAdded(false);
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) {
screen.removePreference(mFooterPreference);
}
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return mPreferenceKey;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (!(preference instanceof PowerGaugePreference)) {
return false;
}
final PowerGaugePreference powerPref = (PowerGaugePreference) preference;
final BatteryDiffEntry diffEntry = powerPref.getBatteryDiffEntry();
final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry;
final String packageName = histEntry.mPackageName;
final boolean isAppEntry = histEntry.isAppEntry();
mMetricsFeatureProvider.action(
/* attribution */ SettingsEnums.OPEN_BATTERY_USAGE,
/* action */ isAppEntry
? SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM
: SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM,
/* pageId */ SettingsEnums.OPEN_BATTERY_USAGE,
TextUtils.isEmpty(packageName) ? PACKAGE_NAME_NONE : packageName,
(int) Math.round(diffEntry.getPercentOfTotal()));
Log.d(TAG, String.format("handleClick() label=%s key=%s package=%s",
diffEntry.getAppLabel(), histEntry.getKey(), histEntry.mPackageName));
AdvancedPowerUsageDetail.startBatteryDetailPage(
mActivity, mFragment, diffEntry, powerPref.getPercent(),
isValidToShowSummary(packageName), getSlotInformation());
return true;
}
@Override
public void onExpand(boolean isExpanded) {
mIsExpanded = isExpanded;
mMetricsFeatureProvider.action(
mPrefContext,
SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM,
isExpanded);
refreshExpandUi();
}
void setBatteryHistoryMap(
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
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;
}
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));
}
refreshUi();
}
void setBatteryChartView(@NonNull final BatteryChartViewV2 dailyChartView,
@NonNull final BatteryChartViewV2 hourlyChartView) {
if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) {
mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView));
}
}
private void setBatteryChartViewInner(@NonNull final BatteryChartViewV2 dailyChartView,
@NonNull final BatteryChartViewV2 hourlyChartView) {
mDailyChartView = dailyChartView;
mDailyChartView.setOnSelectListener(trapezoidIndex -> {
if (mDailyChartIndex == trapezoidIndex) {
return;
}
Log.d(TAG, "onDailyChartSelect:" + trapezoidIndex);
mDailyChartIndex = trapezoidIndex;
mHourlyChartIndex = BatteryChartView.SELECTED_INDEX_ALL;
refreshUi();
// TODO: Change to log daily data.
});
mHourlyChartView = hourlyChartView;
mHourlyChartView.setOnSelectListener(trapezoidIndex -> {
if (mHourlyChartIndex == trapezoidIndex) {
return;
}
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() {
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);
addFooterPreferenceIfNeeded(false);
return false;
}
if (mBatteryUsageMap == null) {
// Battery usage data is not ready, wait for data ready to refresh UI.
return false;
}
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);
updateMarginBetweenCharts(false);
} else {
mHourlyChartView.setVisibility(View.VISIBLE);
final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex);
hourlyViewModel.setSelectedIndex(mHourlyChartIndex);
mHourlyChartView.setViewModel(hourlyViewModel);
updateMarginBetweenCharts(true);
}
mHandler.post(() -> {
final long start = System.currentTimeMillis();
removeAndCacheAllPrefs();
addAllPreferences();
refreshCategoryTitle();
Log.d(TAG, String.format("refreshUi is finished in %d/ms",
(System.currentTimeMillis() - start)));
});
return true;
}
private void updateMarginBetweenCharts(boolean addMargin) {
final LinearLayout.LayoutParams layoutParams =
(LinearLayout.LayoutParams) mDailyChartView.getLayoutParams();
layoutParams.bottomMargin = addMargin ? Math.round(
mContext.getResources().getDimension(R.dimen.chartview_two_charts_margin)) : 0;
mDailyChartView.setLayoutParams(layoutParams);
}
private void addAllPreferences() {
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;
}
// Adds app entries to the list if it is not empty.
if (!batteryDiffData.getAppDiffEntryList().isEmpty()) {
addPreferenceToScreen(batteryDiffData.getAppDiffEntryList());
}
// Adds the expabable divider if we have system entries data.
if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) {
if (mExpandDividerPreference == null) {
mExpandDividerPreference = new ExpandDividerPreference(mPrefContext);
mExpandDividerPreference.setOnExpandListener(this);
mExpandDividerPreference.setIsExpanded(mIsExpanded);
}
mExpandDividerPreference.setOrder(
mAppListPrefGroup.getPreferenceCount());
mAppListPrefGroup.addPreference(mExpandDividerPreference);
}
refreshExpandUi();
}
@VisibleForTesting
void addPreferenceToScreen(List<BatteryDiffEntry> entries) {
if (mAppListPrefGroup == null || entries.isEmpty()) {
return;
}
int prefIndex = mAppListPrefGroup.getPreferenceCount();
for (BatteryDiffEntry entry : entries) {
boolean isAdded = false;
final String appLabel = entry.getAppLabel();
final Drawable appIcon = entry.getAppIcon();
if (TextUtils.isEmpty(appLabel) || appIcon == null) {
Log.w(TAG, "cannot find app resource for:" + entry.getPackageName());
continue;
}
final String prefKey = entry.mBatteryHistEntry.getKey();
PowerGaugePreference pref = mAppListPrefGroup.findPreference(prefKey);
if (pref != null) {
isAdded = true;
Log.w(TAG, "preference should be removed for:" + entry.getPackageName());
} else {
pref = (PowerGaugePreference) mPreferenceCache.get(prefKey);
}
// Creates new innstance if cached preference is not found.
if (pref == null) {
pref = new PowerGaugePreference(mPrefContext);
pref.setKey(prefKey);
mPreferenceCache.put(prefKey, pref);
}
pref.setIcon(appIcon);
pref.setTitle(appLabel);
pref.setOrder(prefIndex);
pref.setPercent(entry.getPercentOfTotal());
pref.setSingleLineTitle(true);
// Sets the BatteryDiffEntry to preference for launching detailed page.
pref.setBatteryDiffEntry(entry);
pref.setEnabled(entry.validForRestriction());
setPreferenceSummary(pref, entry);
if (!isAdded) {
mAppListPrefGroup.addPreference(pref);
}
prefIndex++;
}
}
private void removeAndCacheAllPrefs() {
if (mAppListPrefGroup == null
|| mAppListPrefGroup.getPreferenceCount() == 0) {
return;
}
final int prefsCount = mAppListPrefGroup.getPreferenceCount();
for (int index = 0; index < prefsCount; index++) {
final Preference pref = mAppListPrefGroup.getPreference(index);
if (TextUtils.isEmpty(pref.getKey())) {
continue;
}
mPreferenceCache.put(pref.getKey(), pref);
}
mAppListPrefGroup.removeAll();
}
private void refreshExpandUi() {
final List<BatteryDiffEntry> systemEntries = mBatteryUsageMap.get(mDailyChartIndex).get(
mHourlyChartIndex).getSystemDiffEntryList();
if (mIsExpanded) {
addPreferenceToScreen(systemEntries);
} else {
// Removes and recycles all system entries to hide all of them.
for (BatteryDiffEntry entry : systemEntries) {
final String prefKey = entry.mBatteryHistEntry.getKey();
final Preference pref = mAppListPrefGroup.findPreference(prefKey);
if (pref != null) {
mAppListPrefGroup.removePreference(pref);
mPreferenceCache.put(pref.getKey(), pref);
}
}
}
}
@VisibleForTesting
void refreshCategoryTitle() {
final String slotInformation = getSlotInformation();
Log.d(TAG, String.format("refreshCategoryTitle:%s", slotInformation));
if (mAppListPrefGroup != null) {
mAppListPrefGroup.setTitle(
getSlotInformation(/*isApp=*/ true, slotInformation));
}
if (mExpandDividerPreference != null) {
mExpandDividerPreference.setTitle(
getSlotInformation(/*isApp=*/ false, slotInformation));
}
}
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)
: mPrefContext.getString(R.string.battery_system_usage);
} else {
return isApp
? mPrefContext.getString(R.string.battery_app_usage_for, slotInformation)
: mPrefContext.getString(R.string.battery_system_usage_for, slotInformation);
}
}
@VisibleForTesting
String getSlotInformation() {
if (mDailyTimestampFullTexts == null || mDailyViewModel == null
|| mHourlyViewModels == null) {
// No data
return null;
}
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
void setPreferenceSummary(
PowerGaugePreference preference, BatteryDiffEntry entry) {
final long foregroundUsageTimeInMs = entry.mForegroundUsageTimeInMs;
final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs;
final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs;
// Checks whether the package is allowed to show summary or not.
if (!isValidToShowSummary(entry.getPackageName())) {
preference.setSummary(null);
return;
}
String usageTimeSummary = null;
// Not shows summary for some system components without usage time.
if (totalUsageTimeInMs == 0) {
preference.setSummary(null);
// Shows background summary only if we don't have foreground usage time.
} else if (foregroundUsageTimeInMs == 0 && backgroundUsageTimeInMs != 0) {
usageTimeSummary = buildUsageTimeInfo(backgroundUsageTimeInMs, true);
// Shows total usage summary only if total usage time is small.
} else if (totalUsageTimeInMs < DateUtils.MINUTE_IN_MILLIS) {
usageTimeSummary = buildUsageTimeInfo(totalUsageTimeInMs, false);
} else {
usageTimeSummary = buildUsageTimeInfo(totalUsageTimeInMs, false);
// Shows background usage time if it is larger than a minute.
if (backgroundUsageTimeInMs > 0) {
usageTimeSummary +=
"\n" + buildUsageTimeInfo(backgroundUsageTimeInMs, true);
}
}
preference.setSummary(usageTimeSummary);
}
private String buildUsageTimeInfo(long usageTimeInMs, boolean isBackground) {
if (usageTimeInMs < DateUtils.MINUTE_IN_MILLIS) {
return mPrefContext.getString(
isBackground
? R.string.battery_usage_background_less_than_one_minute
: R.string.battery_usage_total_less_than_one_minute);
}
final CharSequence timeSequence =
StringUtil.formatElapsedTime(mPrefContext, usageTimeInMs,
/*withSeconds=*/ false, /*collapseTimeUnit=*/ false);
final int resourceId =
isBackground
? R.string.battery_usage_for_background_time
: R.string.battery_usage_for_total_time;
return mPrefContext.getString(resourceId, timeSequence);
}
@VisibleForTesting
boolean isValidToShowSummary(String packageName) {
return !DataProcessor.contains(packageName, mNotAllowShowSummaryPackages);
}
private void addFooterPreferenceIfNeeded(boolean containAppItems) {
if (mIsFooterPrefAdded || mFooterPreference == null) {
return;
}
mIsFooterPrefAdded = true;
mFooterPreference.setTitle(mPrefContext.getString(
containAppItems
? R.string.battery_usage_screen_footer
: R.string.battery_usage_screen_footer_empty));
mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference));
}
private boolean isBatteryLevelDataInOneDay() {
return mHourlyViewModels != null && mHourlyViewModels.size() == 1;
}
private boolean isAllSelected() {
return (isBatteryLevelDataInOneDay()
|| mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL)
&& mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL;
}
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> getAppBatteryUsageData(Context context) {
final long start = System.currentTimeMillis();
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getBatteryHistorySinceLastFullCharge(context);
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
return null;
}
Log.d(TAG, String.format("getBatterySinceLastFullChargeUsageData() size=%d time=%d/ms",
batteryHistoryMap.size(), (System.currentTimeMillis() - start)));
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 getAppBatteryUsageData(
Context context, String packageName, int userId) {
if (packageName == null) {
return null;
}
final List<BatteryDiffEntry> entries = getAppBatteryUsageData(context);
if (entries == null) {
return null;
}
for (BatteryDiffEntry entry : entries) {
final BatteryHistEntry batteryHistEntry = entry.mBatteryHistEntry;
if (batteryHistEntry != null
&& batteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY
&& batteryHistEntry.mUserId == userId
&& packageName.equals(entry.getPackageName())) {
return entry;
}
}
return null;
}
}

View File

@@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.Utils.formatPercentage; import static com.android.settings.Utils.formatPercentage;
import static java.lang.Math.round; import static java.lang.Math.round;
import static java.util.Objects.requireNonNull;
import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context; import android.content.Context;
@@ -29,8 +30,6 @@ import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Handler; import android.os.Handler;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
@@ -39,6 +38,7 @@ import android.view.View;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
@@ -46,7 +46,7 @@ import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils; import com.android.settingslib.Utils;
import java.time.Clock; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -58,36 +58,26 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private static final List<String> ACCESSIBILITY_SERVICE_NAMES = private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService"); 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 int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
private static final long UPDATE_STATE_DELAYED_TIME = 500L; 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. */ /** A callback listener for selected group index is updated. */
public interface OnSelectListener { public interface OnSelectListener {
/** The callback function for selected group index is updated. */ /** The callback function for selected group index is updated. */
void onSelect(int trapezoidIndex); void onSelect(int trapezoidIndex);
} }
private BatteryChartViewModel mViewModel;
private int mDividerWidth; private int mDividerWidth;
private int mDividerHeight; private int mDividerHeight;
private int mTrapezoidCount;
private float mTrapezoidVOffset; private float mTrapezoidVOffset;
private float mTrapezoidHOffset; private float mTrapezoidHOffset;
private boolean mIsSlotsClickabled; private boolean mIsSlotsClickabled;
private String[] mPercentages = getPercentages(); private String[] mPercentages = getPercentages();
@VisibleForTesting @VisibleForTesting
int mHoveredIndex = SELECTED_INDEX_INVALID; int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
@VisibleForTesting
int mSelectedIndex = SELECTED_INDEX_INVALID;
@VisibleForTesting
String[] mTimestamps;
// Colors for drawing the trapezoid shape and dividers. // Colors for drawing the trapezoid shape and dividers.
private int mTrapezoidColor; private int mTrapezoidColor;
@@ -98,25 +88,26 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private final Rect mIndent = new Rect(); private final Rect mIndent = new Rect();
private final Rect[] mPercentageBounds = private final Rect[] mPercentageBounds =
new Rect[]{new Rect(), new Rect(), new Rect()}; new Rect[]{new Rect(), new Rect(), new Rect()};
// For drawing the timestamp information. // For drawing the axis label information.
private final Rect[] mTimestampsBounds = private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
new Rect[]{new Rect(), new Rect(), new Rect(), new Rect()};
@VisibleForTesting @VisibleForTesting
Handler mHandler = new Handler(); Handler mHandler = new Handler();
@VisibleForTesting @VisibleForTesting
final Runnable mUpdateClickableStateRun = () -> updateClickableState(); final Runnable mUpdateClickableStateRun = () -> updateClickableState();
private int[] mLevels;
private Paint mTextPaint; private Paint mTextPaint;
private Paint mDividerPaint; private Paint mDividerPaint;
private Paint mTrapezoidPaint; private Paint mTrapezoidPaint;
@VisibleForTesting @VisibleForTesting
Paint mTrapezoidCurvePaint = null; Paint mTrapezoidCurvePaint = null;
private TrapezoidSlot[] mTrapezoidSlots; @VisibleForTesting
TrapezoidSlot[] mTrapezoidSlots;
// Records the location to calculate selected index. // Records the location to calculate selected index.
private float mTouchUpEventX = Float.MIN_VALUE; @VisibleForTesting
float mTouchUpEventX = Float.MIN_VALUE;
private BatteryChartView.OnSelectListener mOnSelectListener; private BatteryChartView.OnSelectListener mOnSelectListener;
public BatteryChartView(Context context) { public BatteryChartView(Context context) {
@@ -128,57 +119,25 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
initializeColors(context); initializeColors(context);
// Registers the click event listener. // Registers the click event listener.
setOnClickListener(this); setOnClickListener(this);
setSelectedIndex(SELECTED_INDEX_ALL);
setTrapezoidCount(DEFAULT_TRAPEZOID_COUNT);
setClickable(false); setClickable(false);
setLatestTimestamp(0); requestLayout();
} }
/** Sets the total trapezoid count for drawing. */ /** Sets the data model of this view. */
public void setTrapezoidCount(int trapezoidCount) { public void setViewModel(BatteryChartViewModel viewModel) {
Log.i(TAG, "trapezoidCount:" + trapezoidCount); if (viewModel == null) {
mTrapezoidCount = trapezoidCount; mViewModel = null;
mTrapezoidSlots = new TrapezoidSlot[trapezoidCount];
// Allocates the trapezoid slot array.
for (int index = 0; index < trapezoidCount; index++) {
mTrapezoidSlots[index] = new TrapezoidSlot();
}
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(); invalidate();
// Callbacks to the listener if we have. return;
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. */ /** Sets the callback to monitor the selected group index. */
@@ -195,29 +154,6 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
} else { } else {
mTextPaint = null; 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(); requestLayout();
} }
@@ -226,6 +162,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Measures text bounds and updates indent configuration. // Measures text bounds and updates indent configuration.
if (mTextPaint != null) { if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.LEFT);
for (int index = 0; index < mPercentages.length; index++) { for (int index = 0; index < mPercentages.length; index++) {
mTextPaint.getTextBounds( mTextPaint.getTextBounds(
mPercentages[index], 0, mPercentages[index].length(), mPercentages[index], 0, mPercentages[index].length(),
@@ -235,15 +172,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mIndent.top = mPercentageBounds[0].height(); mIndent.top = mPercentageBounds[0].height();
mIndent.right = mPercentageBounds[0].width() + mTextPadding; mIndent.right = mPercentageBounds[0].width() + mTextPadding;
if (mTimestamps != null) { if (mViewModel != null) {
int maxHeight = 0; int maxTop = 0;
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) { for (int index = 0; index < mViewModel.size(); index++) {
mTextPaint.getTextBounds( final String text = mViewModel.texts().get(index);
mTimestamps[index], 0, mTimestamps[index].length(), mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index));
mTimestampsBounds[index]); maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top);
maxHeight = Math.max(maxHeight, mTimestampsBounds[index].height());
} }
mIndent.bottom = maxHeight + round(mTextPadding * 1.5f); mIndent.bottom = maxTop + round(mTextPadding * 2f);
} }
Log.d(TAG, "setIndent:" + mPercentageBounds[0]); Log.d(TAG, "setIndent:" + mPercentageBounds[0]);
} else { } else {
@@ -254,7 +190,12 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
@Override @Override
public void draw(Canvas canvas) { public void draw(Canvas canvas) {
super.draw(canvas); super.draw(canvas);
// Before mLevels initialized, the count of trapezoids is unknown. Only draws the
// horizontal percentages and dividers.
drawHorizontalDividers(canvas); drawHorizontalDividers(canvas);
if (mViewModel == null) {
return;
}
drawVerticalDividers(canvas); drawVerticalDividers(canvas);
drawTrapezoids(canvas); drawTrapezoids(canvas);
} }
@@ -294,7 +235,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
public void onHoverChanged(boolean hovered) { public void onHoverChanged(boolean hovered) {
super.onHoverChanged(hovered); super.onHoverChanged(hovered);
if (!hovered) { if (!hovered) {
mHoveredIndex = SELECTED_INDEX_INVALID; // reset mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
invalidate(); invalidate();
} }
} }
@@ -307,15 +248,15 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
} }
final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX); final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX);
// Ignores the click event if the level is zero. // Ignores the click event if the level is zero.
if (trapezoidIndex == SELECTED_INDEX_INVALID if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
|| !isValidToDraw(trapezoidIndex)) { || !isValidToDraw(mViewModel, trapezoidIndex)) {
return; return;
} }
// Selects all if users click the same trapezoid item two times. if (mOnSelectListener != null) {
if (trapezoidIndex == mSelectedIndex) { // Selects all if users click the same trapezoid item two times.
setSelectedIndex(SELECTED_INDEX_ALL); mOnSelectListener.onSelect(
} else { trapezoidIndex == mViewModel.selectedIndex()
setSelectedIndex(trapezoidIndex); ? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex);
} }
view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK); view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
} }
@@ -364,8 +305,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2); mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2);
} else if (mIsSlotsClickabled) { } else if (mIsSlotsClickabled) {
mTrapezoidCurvePaint = null; mTrapezoidCurvePaint = null;
// Sets levels again to force update the click state. // Sets view model again to force update the click state.
setLevels(mLevels); setViewModel(mViewModel);
} }
invalidate(); invalidate();
} }
@@ -380,6 +321,13 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
super.setClickable(clickable); 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) { private void initializeColors(Context context) {
setBackgroundColor(Color.TRANSPARENT); setBackgroundColor(Color.TRANSPARENT);
mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context); 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) { private void drawPercentage(Canvas canvas, int index, float offsetY) {
if (mTextPaint != null) { if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText( canvas.drawText(
mPercentages[index], mPercentages[index],
getWidth() - mPercentageBounds[index].width() getWidth(),
- mPercentageBounds[index].left,
offsetY + mPercentageBounds[index].height() * .5f, offsetY + mPercentageBounds[index].height() * .5f,
mTextPaint); mTextPaint);
} }
@@ -445,9 +393,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private void drawVerticalDividers(Canvas canvas) { private void drawVerticalDividers(Canvas canvas) {
final int width = getWidth() - mIndent.right; final int width = getWidth() - mIndent.right;
final int dividerCount = mTrapezoidCount + 1; final int dividerCount = mTrapezoidSlots.length + 1;
final float dividerSpace = dividerCount * mDividerWidth; 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 bottomY = getHeight() - mIndent.bottom;
final float startY = bottomY - mDividerHeight; final float startY = bottomY - mDividerHeight;
final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * .5f; final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * .5f;
@@ -463,53 +411,119 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
} }
startX = nextX; startX = nextX;
} }
// Draws the timestamp slot information. // Draws the axis label slot information.
if (mTimestamps != null) { if (mViewModel != null) {
final float[] xOffsets = new float[DEFAULT_TIMESTAMP_COUNT]; final float baselineY = getHeight() - mTextPadding * 1.5f;
final float baselineX = mDividerWidth * .5f; Rect[] axisLabelDisplayAreas;
final float offsetX = mDividerWidth + unitWidth; switch (mViewModel.axisLabelPosition()) {
final int slotBarOffset = (/*total 12 bars*/ 12) / TIMESTAMP_GAPS_COUNT; case CENTER_OF_TRAPEZOIDS:
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) { axisLabelDisplayAreas = getAxisLabelDisplayAreas(
xOffsets[index] = baselineX + index * offsetX * slotBarOffset; /* 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) { /** Gets all the axis label texts displaying area positions if they are shown. */
// Draws the 1st timestamp info. private Rect[] getAxisLabelDisplayAreas(final int size, final float baselineX,
canvas.drawText( final float offsetX, final float baselineY, final boolean shiftFirstAndLast) {
mTimestamps[0], final Rect[] result = new Rect[size];
xOffsets[0] - mTimestampsBounds[0].left, for (int index = 0; index < result.length; index++) {
getTimestampY(0), mTextPaint); final float width = mAxisLabelsBounds.get(index).width();
final int latestIndex = DEFAULT_TIMESTAMP_COUNT - 1; float middle = baselineX + index * offsetX;
// Draws the last timestamp info. if (shiftFirstAndLast) {
canvas.drawText( if (index == 0) {
mTimestamps[latestIndex], middle += width * .5f;
xOffsets[latestIndex] - mTimestampsBounds[latestIndex].width() }
- mTimestampsBounds[latestIndex].left, if (index == size - 1) {
getTimestampY(latestIndex), mTextPaint); middle -= width * .5f;
// 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( final float left = middle - width * .5f;
mTimestamps[index], final float right = left + width;
xOffsets[index] final float top = baselineY + mAxisLabelsBounds.get(index).top;
- (mTimestampsBounds[index].width() - mTimestampsBounds[index].left) final float bottom = top + mAxisLabelsBounds.get(index).height();
* .5f, result[index] = new Rect(round(left), round(top), round(right), round(bottom));
getTimestampY(index), mTextPaint); }
return result;
}
/**
* Pairly draws axis labels from left and right side to middle. If the pair of labels have
* any overlap, skips that pair of labels.
*/
private void drawAxisLabels(Canvas canvas, final Rect[] displayAreas, final float baselineY) {
int forwardCheckLine = Integer.MIN_VALUE;
int backwardCheckLine = Integer.MAX_VALUE;
Rect middleDisplayArea = null;
for (int forwardIndex = 0, backwordIndex = displayAreas.length - 1;
forwardIndex <= backwordIndex; forwardIndex++, backwordIndex--) {
final Rect forwardDisplayArea = displayAreas[forwardIndex];
final Rect backwardDisplayArea = displayAreas[backwordIndex];
if (forwardDisplayArea.left < forwardCheckLine
|| backwardDisplayArea.right > backwardCheckLine) {
// Overlapped at left or right, skip the pair of labels
continue;
}
if (middleDisplayArea != null && (
forwardDisplayArea.right + mTextPadding > middleDisplayArea.left
|| backwardDisplayArea.left - mTextPadding < middleDisplayArea.right)) {
// Overlapped with the middle label.
continue;
}
if (forwardIndex != backwordIndex
&& forwardDisplayArea.right + mTextPadding > backwardDisplayArea.left) {
// Overlapped in the middle, skip the pair of labels
continue;
}
drawAxisLabelText(canvas, forwardIndex, forwardDisplayArea, baselineY);
drawAxisLabelText(canvas, backwordIndex, backwardDisplayArea, baselineY);
forwardCheckLine = forwardDisplayArea.right + mTextPadding;
backwardCheckLine = backwardDisplayArea.left - mTextPadding;
// If the number of labels is odd, draw the middle label first
if (forwardIndex == 0 && backwordIndex % 2 == 0) {
final int middleIndex = backwordIndex / 2;
middleDisplayArea = displayAreas[middleIndex];
if (middleDisplayArea.left < forwardCheckLine
|| middleDisplayArea.right > backwardCheckLine) {
// Overlapped at left or right, skip the pair of labels
continue;
}
drawAxisLabelText(canvas, middleIndex, middleDisplayArea, baselineY);
}
} }
} }
private int getTimestampY(int index) { private void drawAxisLabelText(
return getHeight() - mTimestampsBounds[index].height() Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
+ (mTimestampsBounds[index].height() + mTimestampsBounds[index].top) mTextPaint.setTextAlign(Paint.Align.CENTER);
+ round(mTextPadding * 1.5f); canvas.drawText(
mViewModel.texts().get(index),
displayArea.centerX(),
baselineY,
mTextPaint);
} }
private void drawTrapezoids(Canvas canvas) { private void drawTrapezoids(Canvas canvas) {
// Ignores invalid trapezoid data. // Ignores invalid trapezoid data.
if (mLevels == null) { if (mViewModel == null) {
return; return;
} }
final float trapezoidBottom = final float trapezoidBottom =
@@ -520,9 +534,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
// Draws all trapezoid shapes into the canvas. // Draws all trapezoid shapes into the canvas.
final Path trapezoidPath = new Path(); final Path trapezoidPath = new Path();
Path trapezoidCurvePath = null; 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. // Not draws the trapezoid for corner or not initialization cases.
if (!isValidToDraw(index)) { if (!isValidToDraw(mViewModel, index)) {
if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) { if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint); canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
trapezoidCurvePath = null; trapezoidCurvePath = null;
@@ -530,17 +544,18 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
continue; continue;
} }
// Configures the trapezoid paint color. // Configures the trapezoid paint color.
final int trapezoidColor = final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index
!mIsSlotsClickabled || mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL)
? mTrapezoidColor ? mTrapezoidSolidColor : mTrapezoidColor;
: mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL
? mTrapezoidSolidColor : mTrapezoidColor;
final boolean isHoverState = final boolean isHoverState =
mIsSlotsClickabled && mHoveredIndex == index && isValidToDraw(mHoveredIndex); mIsSlotsClickabled && mHoveredIndex == index
&& isValidToDraw(mViewModel, mHoveredIndex);
mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor); mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor);
final float leftTop = round(trapezoidBottom - mLevels[index] * unitHeight); final float leftTop = round(
final float rightTop = round(trapezoidBottom - mLevels[index + 1] * unitHeight); trapezoidBottom - requireNonNull(mViewModel.levels().get(index)) * unitHeight);
final float rightTop = round(trapezoidBottom
- requireNonNull(mViewModel.levels().get(index + 1)) * unitHeight);
trapezoidPath.reset(); trapezoidPath.reset();
trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom); trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop); trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
@@ -579,15 +594,37 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
return index; return index;
} }
} }
return SELECTED_INDEX_INVALID; return BatteryChartViewModel.SELECTED_INDEX_INVALID;
} }
private boolean isValidToDraw(int trapezoidIndex) { private void initializeAxisLabelsBounds() {
return mLevels != null 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 >= 0
&& trapezoidIndex < mLevels.length - 1 && trapezoidIndex < viewModel.size() - 1
&& mLevels[trapezoidIndex] != 0 && isTrapezoidValid(viewModel, trapezoidIndex);
&& mLevels[trapezoidIndex + 1] != 0; }
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() { private static String[] getPercentages() {
@@ -621,7 +658,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
} }
// A container class for each trapezoid left and right location. // 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 mLeft;
public float mRight; public float mRight;

View File

@@ -23,7 +23,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
/** The view model of {@code BatteryChartViewV2} */ /** The view model of {@code BatteryChartView} */
class BatteryChartViewModel { class BatteryChartViewModel {
private static final String TAG = "BatteryChartViewModel"; private static final String TAG = "BatteryChartViewModel";

View File

@@ -1,671 +0,0 @@
/*
* 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.android.settings.Utils.formatPercentage;
import static java.lang.Math.round;
import static java.util.Objects.requireNonNull;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
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;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/** A widget component to draw chart graph. */
public class BatteryChartViewV2 extends AppCompatImageView implements View.OnClickListener,
AccessibilityManager.AccessibilityStateChangeListener {
private static final String TAG = "BatteryChartViewV2";
private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
private static final long UPDATE_STATE_DELAYED_TIME = 500L;
/** 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 float mTrapezoidVOffset;
private float mTrapezoidHOffset;
private boolean mIsSlotsClickabled;
private String[] mPercentages = getPercentages();
@VisibleForTesting
int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
// Colors for drawing the trapezoid shape and dividers.
private int mTrapezoidColor;
private int mTrapezoidSolidColor;
private int mTrapezoidHoverColor;
// For drawing the percentage information.
private int mTextPadding;
private final Rect mIndent = new Rect();
private final Rect[] mPercentageBounds =
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 Paint mTextPaint;
private Paint mDividerPaint;
private Paint mTrapezoidPaint;
@VisibleForTesting
Paint mTrapezoidCurvePaint = null;
@VisibleForTesting
TrapezoidSlot[] mTrapezoidSlots;
// Records the location to calculate selected index.
@VisibleForTesting
float mTouchUpEventX = Float.MIN_VALUE;
private BatteryChartViewV2.OnSelectListener mOnSelectListener;
public BatteryChartViewV2(Context context) {
super(context, null);
}
public BatteryChartViewV2(Context context, AttributeSet attrs) {
super(context, attrs);
initializeColors(context);
// Registers the click event listener.
setOnClickListener(this);
setClickable(false);
requestLayout();
}
/** Sets the data model of this view. */
public void setViewModel(BatteryChartViewModel viewModel) {
if (viewModel == null) {
mViewModel = null;
invalidate();
return;
}
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. */
public void setOnSelectListener(BatteryChartViewV2.OnSelectListener listener) {
mOnSelectListener = listener;
}
/** Sets the companion {@link TextView} for percentage information. */
public void setCompanionTextView(TextView textView) {
if (textView != null) {
// Pre-draws the view first to load style atttributions into paint.
textView.draw(new Canvas());
mTextPaint = textView.getPaint();
} else {
mTextPaint = null;
}
requestLayout();
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
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(),
mPercentageBounds[index]);
}
// Updates the indent configurations.
mIndent.top = mPercentageBounds[0].height();
mIndent.right = mPercentageBounds[0].width() + mTextPadding;
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 = maxTop + round(mTextPadding * 2f);
}
Log.d(TAG, "setIndent:" + mPercentageBounds[0]);
} else {
mIndent.set(0, 0, 0, 0);
}
}
@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);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Caches the location to calculate selected trapezoid index.
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
mTouchUpEventX = event.getX();
break;
case MotionEvent.ACTION_CANCEL:
mTouchUpEventX = Float.MIN_VALUE; // reset
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean onHoverEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE:
final int trapezoidIndex = getTrapezoidIndex(event.getX());
if (mHoveredIndex != trapezoidIndex) {
mHoveredIndex = trapezoidIndex;
invalidate();
}
break;
}
return super.onHoverEvent(event);
}
@Override
public void onHoverChanged(boolean hovered) {
super.onHoverChanged(hovered);
if (!hovered) {
mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
invalidate();
}
}
@Override
public void onClick(View view) {
if (mTouchUpEventX == Float.MIN_VALUE) {
Log.w(TAG, "invalid motion event for onClick() callback");
return;
}
final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX);
// Ignores the click event if the level is zero.
if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
|| !isValidToDraw(mViewModel, trapezoidIndex)) {
return;
}
if (mOnSelectListener != null) {
// Selects all if users click the same trapezoid item two times.
mOnSelectListener.onSelect(
trapezoidIndex == mViewModel.selectedIndex()
? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex);
}
view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
updateClickableState();
mContext.getSystemService(AccessibilityManager.class)
.addAccessibilityStateChangeListener(/*listener=*/ this);
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mContext.getSystemService(AccessibilityManager.class)
.removeAccessibilityStateChangeListener(/*listener=*/ this);
mHandler.removeCallbacks(mUpdateClickableStateRun);
}
@Override
public void onAccessibilityStateChanged(boolean enabled) {
Log.d(TAG, "onAccessibilityStateChanged:" + enabled);
mHandler.removeCallbacks(mUpdateClickableStateRun);
// We should delay it a while since accessibility manager will spend
// some times to bind with new enabled accessibility services.
mHandler.postDelayed(
mUpdateClickableStateRun, UPDATE_STATE_DELAYED_TIME);
}
private void updateClickableState() {
final Context context = mContext;
mIsSlotsClickabled =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.isChartGraphSlotsEnabled(context)
&& !isAccessibilityEnabled(context);
Log.d(TAG, "isChartGraphSlotsEnabled:" + mIsSlotsClickabled);
setClickable(isClickable());
// Initializes the trapezoid curve paint for non-clickable case.
if (!mIsSlotsClickabled && mTrapezoidCurvePaint == null) {
mTrapezoidCurvePaint = new Paint();
mTrapezoidCurvePaint.setAntiAlias(true);
mTrapezoidCurvePaint.setColor(mTrapezoidSolidColor);
mTrapezoidCurvePaint.setStyle(Paint.Style.STROKE);
mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2);
} else if (mIsSlotsClickabled) {
mTrapezoidCurvePaint = null;
// Sets view model again to force update the click state.
setViewModel(mViewModel);
}
invalidate();
}
@Override
public void setClickable(boolean clickable) {
super.setClickable(mIsSlotsClickabled && clickable);
}
@VisibleForTesting
void setClickableForce(boolean clickable) {
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);
mTrapezoidColor = Utils.getDisabled(context, mTrapezoidSolidColor);
mTrapezoidHoverColor = Utils.getColorAttrDefaultColor(context,
com.android.internal.R.attr.colorAccentSecondaryVariant);
// Initializes the divider line paint.
final Resources resources = getContext().getResources();
mDividerWidth = resources.getDimensionPixelSize(R.dimen.chartview_divider_width);
mDividerHeight = resources.getDimensionPixelSize(R.dimen.chartview_divider_height);
mDividerPaint = new Paint();
mDividerPaint.setAntiAlias(true);
mDividerPaint.setColor(DIVIDER_COLOR);
mDividerPaint.setStyle(Paint.Style.STROKE);
mDividerPaint.setStrokeWidth(mDividerWidth);
Log.i(TAG, "mDividerWidth:" + mDividerWidth);
Log.i(TAG, "mDividerHeight:" + mDividerHeight);
// Initializes the trapezoid paint.
mTrapezoidHOffset = resources.getDimension(R.dimen.chartview_trapezoid_margin_start);
mTrapezoidVOffset = resources.getDimension(R.dimen.chartview_trapezoid_margin_bottom);
mTrapezoidPaint = new Paint();
mTrapezoidPaint.setAntiAlias(true);
mTrapezoidPaint.setColor(mTrapezoidSolidColor);
mTrapezoidPaint.setStyle(Paint.Style.FILL);
mTrapezoidPaint.setPathEffect(
new CornerPathEffect(
resources.getDimensionPixelSize(R.dimen.chartview_trapezoid_radius)));
// Initializes for drawing text information.
mTextPadding = resources.getDimensionPixelSize(R.dimen.chartview_text_padding);
}
private void drawHorizontalDividers(Canvas canvas) {
final int width = getWidth() - mIndent.right;
final int height = getHeight() - mIndent.top - mIndent.bottom;
// Draws the top divider line for 100% curve.
float offsetY = mIndent.top + mDividerWidth * .5f;
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
drawPercentage(canvas, /*index=*/ 0, offsetY);
// Draws the center divider line for 50% curve.
final float availableSpace =
height - mDividerWidth * 2 - mTrapezoidVOffset - mDividerHeight;
offsetY = mIndent.top + mDividerWidth + availableSpace * .5f;
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
drawPercentage(canvas, /*index=*/ 1, offsetY);
// Draws the bottom divider line for 0% curve.
offsetY = mIndent.top + (height - mDividerHeight - mDividerWidth * .5f);
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
drawPercentage(canvas, /*index=*/ 2, offsetY);
}
private void drawPercentage(Canvas canvas, int index, float offsetY) {
if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(
mPercentages[index],
getWidth(),
offsetY + mPercentageBounds[index].height() * .5f,
mTextPaint);
}
}
private void drawVerticalDividers(Canvas canvas) {
final int width = getWidth() - mIndent.right;
final int dividerCount = mTrapezoidSlots.length + 1;
final float dividerSpace = dividerCount * mDividerWidth;
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;
// Draws each vertical dividers.
float startX = mDividerWidth * .5f;
for (int index = 0; index < dividerCount; index++) {
canvas.drawLine(startX, startY, startX, bottomY, mDividerPaint);
final float nextX = startX + mDividerWidth + unitWidth;
// Updates the trapezoid slots for drawing.
if (index < mTrapezoidSlots.length) {
mTrapezoidSlots[index].mLeft = round(startX + trapezoidSlotOffset);
mTrapezoidSlots[index].mRight = round(nextX - trapezoidSlotOffset);
}
startX = nextX;
}
// 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;
}
drawAxisLabels(canvas, axisLabelDisplayAreas, baselineY);
}
}
/** 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;
}
/**
* Pairly draws axis labels from left and right side to middle. If the pair of labels have
* any overlap, skips that pair of labels.
*/
private void drawAxisLabels(Canvas canvas, final Rect[] displayAreas, final float baselineY) {
int forwardCheckLine = Integer.MIN_VALUE;
int backwardCheckLine = Integer.MAX_VALUE;
Rect middleDisplayArea = null;
for (int forwardIndex = 0, backwordIndex = displayAreas.length - 1;
forwardIndex <= backwordIndex; forwardIndex++, backwordIndex--) {
final Rect forwardDisplayArea = displayAreas[forwardIndex];
final Rect backwardDisplayArea = displayAreas[backwordIndex];
if (forwardDisplayArea.left < forwardCheckLine
|| backwardDisplayArea.right > backwardCheckLine) {
// Overlapped at left or right, skip the pair of labels
continue;
}
if (middleDisplayArea != null && (
forwardDisplayArea.right + mTextPadding > middleDisplayArea.left
|| backwardDisplayArea.left - mTextPadding < middleDisplayArea.right)) {
// Overlapped with the middle label.
continue;
}
if (forwardIndex != backwordIndex
&& forwardDisplayArea.right + mTextPadding > backwardDisplayArea.left) {
// Overlapped in the middle, skip the pair of labels
continue;
}
drawAxisLabelText(canvas, forwardIndex, forwardDisplayArea, baselineY);
drawAxisLabelText(canvas, backwordIndex, backwardDisplayArea, baselineY);
forwardCheckLine = forwardDisplayArea.right + mTextPadding;
backwardCheckLine = backwardDisplayArea.left - mTextPadding;
// If the number of labels is odd, draw the middle label first
if (forwardIndex == 0 && backwordIndex % 2 == 0) {
final int middleIndex = backwordIndex / 2;
middleDisplayArea = displayAreas[middleIndex];
if (middleDisplayArea.left < forwardCheckLine
|| middleDisplayArea.right > backwardCheckLine) {
// Overlapped at left or right, skip the pair of labels
continue;
}
drawAxisLabelText(canvas, middleIndex, middleDisplayArea, baselineY);
}
}
}
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 (mViewModel == null) {
return;
}
final float trapezoidBottom =
getHeight() - mIndent.bottom - mDividerHeight - mDividerWidth
- mTrapezoidVOffset;
final float availableSpace = trapezoidBottom - mDividerWidth * .5f - mIndent.top;
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 < mTrapezoidSlots.length; index++) {
// Not draws the trapezoid for corner or not initialization cases.
if (!isValidToDraw(mViewModel, index)) {
if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
trapezoidCurvePath = null;
}
continue;
}
// Configures the trapezoid paint color.
final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index
|| mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL)
? mTrapezoidSolidColor : mTrapezoidColor;
final boolean isHoverState =
mIsSlotsClickabled && mHoveredIndex == index
&& isValidToDraw(mViewModel, mHoveredIndex);
mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor);
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);
trapezoidPath.lineTo(mTrapezoidSlots[index].mRight, rightTop);
trapezoidPath.lineTo(mTrapezoidSlots[index].mRight, trapezoidBottom);
// A tricky way to make the trapezoid shape drawing the rounded corner.
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
// Draws the trapezoid shape into canvas.
canvas.drawPath(trapezoidPath, mTrapezoidPaint);
// Generates path for non-clickable trapezoid curve.
if (mTrapezoidCurvePaint != null) {
if (trapezoidCurvePath == null) {
trapezoidCurvePath = new Path();
trapezoidCurvePath.moveTo(mTrapezoidSlots[index].mLeft, leftTop);
} else {
trapezoidCurvePath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
}
trapezoidCurvePath.lineTo(mTrapezoidSlots[index].mRight, rightTop);
}
}
// Draws the trapezoid curve for non-clickable case.
if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
trapezoidCurvePath = null;
}
}
// Searches the corresponding trapezoid index from x location.
private int getTrapezoidIndex(float x) {
for (int index = 0; index < mTrapezoidSlots.length; index++) {
final TrapezoidSlot slot = mTrapezoidSlots[index];
if (x >= slot.mLeft - mTrapezoidHOffset
&& x <= slot.mRight + mTrapezoidHOffset) {
return index;
}
}
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
}
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 < 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() {
return new String[]{
formatPercentage(/*percentage=*/ 100, /*round=*/ true),
formatPercentage(/*percentage=*/ 50, /*round=*/ true),
formatPercentage(/*percentage=*/ 0, /*round=*/ true)};
}
@VisibleForTesting
static boolean isAccessibilityEnabled(Context context) {
final AccessibilityManager accessibilityManager =
context.getSystemService(AccessibilityManager.class);
if (!accessibilityManager.isEnabled()) {
return false;
}
final List<AccessibilityServiceInfo> serviceInfoList =
accessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_SPOKEN
| AccessibilityServiceInfo.FEEDBACK_GENERIC);
for (AccessibilityServiceInfo info : serviceInfoList) {
for (String serviceName : ACCESSIBILITY_SERVICE_NAMES) {
final String serviceId = info.getId();
if (serviceId != null && serviceId.contains(serviceName)) {
Log.d(TAG, "acccessibilityEnabled:" + serviceId);
return true;
}
}
}
return false;
}
// A container class for each trapezoid left and right location.
@VisibleForTesting
static final class TrapezoidSlot {
public float mLeft;
public float mRight;
@Override
public String toString() {
return String.format(Locale.US, "TrapezoidSlot[%f,%f]", mLeft, mRight);
}
}
}

View File

@@ -43,6 +43,6 @@ public class BatteryHistoryLoader
public Map<Long, Map<String, BatteryHistEntry>> loadInBackground() { public Map<Long, Map<String, BatteryHistEntry>> loadInBackground() {
final PowerUsageFeatureProvider powerUsageFeatureProvider = final PowerUsageFeatureProvider powerUsageFeatureProvider =
FeatureFactory.getFactory(mContext).getPowerUsageFeatureProvider(mContext); 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 TextView mSummaryView;
private CharSequence mSummaryContent; private CharSequence mSummaryContent;
private BatteryChartView mBatteryChartView; private BatteryChartView mDailyChartView;
private BatteryChartView mHourlyChartView;
private BatteryChartPreferenceController mChartPreferenceController; private BatteryChartPreferenceController mChartPreferenceController;
public BatteryHistoryPreference(Context context, AttributeSet attrs) { public BatteryHistoryPreference(Context context, AttributeSet attrs) {
@@ -92,8 +93,8 @@ public class BatteryHistoryPreference extends Preference {
void setChartPreferenceController(BatteryChartPreferenceController controller) { void setChartPreferenceController(BatteryChartPreferenceController controller) {
mChartPreferenceController = controller; mChartPreferenceController = controller;
if (mBatteryChartView != null) { if (mDailyChartView != null && mHourlyChartView != null) {
mChartPreferenceController.setBatteryChartView(mBatteryChartView); mChartPreferenceController.setBatteryChartView(mDailyChartView, mHourlyChartView);
} }
} }
@@ -105,11 +106,14 @@ public class BatteryHistoryPreference extends Preference {
return; return;
} }
if (mIsChartGraphEnabled) { if (mIsChartGraphEnabled) {
mBatteryChartView = (BatteryChartView) view.findViewById(R.id.battery_chart); mDailyChartView = (BatteryChartView) view.findViewById(R.id.daily_battery_chart);
mBatteryChartView.setCompanionTextView( 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)); (TextView) view.findViewById(R.id.companion_text));
if (mChartPreferenceController != null) { if (mChartPreferenceController != null) {
mChartPreferenceController.setBatteryChartView(mBatteryChartView); mChartPreferenceController.setBatteryChartView(mDailyChartView, mHourlyChartView);
} }
} else { } else {
final TextView chargeView = (TextView) view.findViewById(R.id.charge); final TextView chargeView = (TextView) view.findViewById(R.id.charge);

View File

@@ -276,7 +276,7 @@ public final class ConvertUtils {
diffEntry.setTotalConsumePower(totalConsumePower); diffEntry.setTotalConsumePower(totalConsumePower);
} }
} }
insert24HoursData(BatteryChartView.SELECTED_INDEX_ALL, resultMap); insert24HoursData(BatteryChartViewModel.SELECTED_INDEX_ALL, resultMap);
resolveMultiUsersData(context, resultMap); resolveMultiUsersData(context, resultMap);
if (purgeLowPercentageAndFakeData) { if (purgeLowPercentageAndFakeData) {
purgeLowPercentageAndFakeData(context, resultMap); purgeLowPercentageAndFakeData(context, resultMap);

View File

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

View File

@@ -1,684 +0,0 @@
/*
* 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.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.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;
import androidx.preference.PreferenceGroup;
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
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 BatteryChartPreferenceControllerV2Test {
private static final String PREF_KEY = "pref_key";
private static final String PREF_SUMMARY = "fake preference summary";
@Mock
private InstrumentedPreferenceFragment mFragment;
@Mock
private SettingsActivity mSettingsActivity;
@Mock
private PreferenceGroup mAppListGroup;
@Mock
private Drawable mDrawable;
@Mock
private BatteryHistEntry mBatteryHistEntry;
@Mock
private BatteryChartViewV2 mDailyChartView;
@Mock
private BatteryChartViewV2 mHourlyChartView;
@Mock
private PowerGaugePreference mPowerGaugePreference;
@Mock
private BatteryUtils mBatteryUtils;
@Mock
private LinearLayout.LayoutParams mLayoutParams;
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
private BatteryDiffEntry mBatteryDiffEntry;
private MetricsFeatureProvider mMetricsFeatureProvider;
private BatteryChartPreferenceControllerV2 mBatteryChartPreferenceController;
@Before
public void setUp() {
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);
final Resources resources = spy(mContext.getResources());
resources.getConfiguration().setLocales(new LocaleList(new Locale("en_US")));
doReturn(resources).when(mContext).getResources();
doReturn(new String[]{"com.android.googlequicksearchbox"})
.when(mFeatureFactory.powerUsageFeatureProvider)
.getHideApplicationSummary(mContext);
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.mDailyChartView = mDailyChartView;
mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView;
mBatteryDiffEntry = new BatteryDiffEntry(
mContext,
/*foregroundUsageTimeInMs=*/ 1,
/*backgroundUsageTimeInMs=*/ 2,
/*consumePower=*/ 3,
mBatteryHistEntry);
mBatteryDiffEntry = spy(mBatteryDiffEntry);
// Adds fake testing data.
BatteryDiffEntry.sResourceCache.put(
"fakeBatteryDiffEntryKey",
new BatteryEntry.NameAndIcon("fakeName", /*icon=*/ null, /*iconId=*/ 1));
}
@Test
public void onDestroy_activityIsChanging_clearBatteryEntryCache() {
doReturn(true).when(mSettingsActivity).isChangingConfigurations();
// Ensures the testing environment is correct.
assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
mBatteryChartPreferenceController.onDestroy();
assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
}
@Test
public void onDestroy_activityIsNotChanging_notClearBatteryEntryCache() {
doReturn(false).when(mSettingsActivity).isChangingConfigurations();
// Ensures the testing environment is correct.
assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
mBatteryChartPreferenceController.onDestroy();
assertThat(BatteryDiffEntry.sResourceCache).isNotEmpty();
}
@Test
public void onDestroy_clearPreferenceCache() {
// Ensures the testing environment is correct.
mBatteryChartPreferenceController.mPreferenceCache.put(
PREF_KEY, mPowerGaugePreference);
assertThat(mBatteryChartPreferenceController.mPreferenceCache).hasSize(1);
mBatteryChartPreferenceController.onDestroy();
// Verifies the result after onDestroy.
assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty();
}
@Test
public void onDestroy_removeAllPreferenceFromPreferenceGroup() {
mBatteryChartPreferenceController.onDestroy();
verify(mAppListGroup).removeAll();
}
@Test
public void setBatteryChartViewModel_6Hours() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
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 setBatteryChartViewModel_60Hours() {
BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel(
List.of(100, 83, 59, 41),
List.of("Sat", "Sun", "Mon", "Mon"),
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
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 refreshUi_normalCase_returnTrue() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue();
}
@Test
public void refreshUi_batteryIndexedMapIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.setBatteryHistoryMap(null);
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void refreshUi_dailyChartViewIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.mDailyChartView = null;
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void refreshUi_hourlyChartViewIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.mHourlyChartView = null;
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void removeAndCacheAllPrefs_emptyContent_ignoreRemoveAll() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
doReturn(0).when(mAppListGroup).getPreferenceCount();
mBatteryChartPreferenceController.refreshUi();
verify(mAppListGroup, never()).removeAll();
}
@Test
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();
assertThat(mBatteryChartPreferenceController.mPreferenceCache.get(PREF_KEY))
.isEqualTo(mPowerGaugePreference);
verify(mAppListGroup).removeAll();
}
@Test
public void addPreferenceToScreen_emptyContent_ignoreAddPreference() {
mBatteryChartPreferenceController.addPreferenceToScreen(
new ArrayList<BatteryDiffEntry>());
verify(mAppListGroup, never()).addPreference(any());
}
@Test
public void addPreferenceToScreen_addPreferenceIntoScreen() {
final String appLabel = "fake app label";
doReturn(1).when(mAppListGroup).getPreferenceCount();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel();
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
doReturn(null).when(mAppListGroup).findPreference(PREF_KEY);
doReturn(false).when(mBatteryDiffEntry).validForRestriction();
mBatteryChartPreferenceController.addPreferenceToScreen(
Arrays.asList(mBatteryDiffEntry));
// Verifies the preference cache.
final PowerGaugePreference pref =
(PowerGaugePreference) mBatteryChartPreferenceController.mPreferenceCache
.get(PREF_KEY);
assertThat(pref).isNotNull();
// Verifies the added preference configuration.
verify(mAppListGroup).addPreference(pref);
assertThat(pref.getKey()).isEqualTo(PREF_KEY);
assertThat(pref.getTitle()).isEqualTo(appLabel);
assertThat(pref.getIcon()).isEqualTo(mDrawable);
assertThat(pref.getOrder()).isEqualTo(1);
assertThat(pref.getBatteryDiffEntry()).isSameInstanceAs(mBatteryDiffEntry);
assertThat(pref.isSingleLineTitle()).isTrue();
assertThat(pref.isEnabled()).isFalse();
}
@Test
public void addPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() {
final String appLabel = "fake app label";
doReturn(1).when(mAppListGroup).getPreferenceCount();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel();
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY);
mBatteryChartPreferenceController.addPreferenceToScreen(
Arrays.asList(mBatteryDiffEntry));
verify(mAppListGroup, never()).addPreference(any());
}
@Test
public void handlePreferenceTreeClick_notPowerGaugePreference_returnFalse() {
assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick(mAppListGroup))
.isFalse();
verify(mMetricsFeatureProvider, never())
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM);
verify(mMetricsFeatureProvider, never())
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM);
}
@Test
public void handlePreferenceTreeClick_forAppEntry_returnTrue() {
doReturn(false).when(mBatteryHistEntry).isAppEntry();
doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick(
mPowerGaugePreference)).isTrue();
verify(mMetricsFeatureProvider)
.action(
SettingsEnums.OPEN_BATTERY_USAGE,
SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM,
SettingsEnums.OPEN_BATTERY_USAGE,
/* package name */ "none",
/* percentage of total */ 0);
}
@Test
public void handlePreferenceTreeClick_forSystemEntry_returnTrue() {
mBatteryChartPreferenceController.mBatteryUtils = mBatteryUtils;
doReturn(true).when(mBatteryHistEntry).isAppEntry();
doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick(
mPowerGaugePreference)).isTrue();
verify(mMetricsFeatureProvider)
.action(
SettingsEnums.OPEN_BATTERY_USAGE,
SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM,
SettingsEnums.OPEN_BATTERY_USAGE,
/* package name */ "none",
/* percentage of total */ 0);
}
@Test
public void setPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
mBatteryChartPreferenceController.setPreferenceSummary(
pref, createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ 0));
assertThat(pref.getSummary()).isNull();
}
@Test
public void setPreferenceSummary_setBackgroundUsageTimeOnly() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
mBatteryChartPreferenceController.setPreferenceSummary(
pref, createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS));
assertThat(pref.getSummary()).isEqualTo("Background: 1 min");
}
@Test
public void setPreferenceSummary_setTotalUsageTimeLessThanAMinute() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
mBatteryChartPreferenceController.setPreferenceSummary(
pref, createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ 100,
/*backgroundUsageTimeInMs=*/ 200));
assertThat(pref.getSummary()).isEqualTo("Total: less than a min");
}
@Test
public void setPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
mBatteryChartPreferenceController.setPreferenceSummary(
pref, createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS,
/*backgroundUsageTimeInMs=*/ 200));
assertThat(pref.getSummary())
.isEqualTo("Total: 1 min\nBackground: less than a min");
}
@Test
public void setPreferenceSummary_setTotalAndBackgroundUsageTime() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
mBatteryChartPreferenceController.setPreferenceSummary(
pref, createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS,
/*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS));
assertThat(pref.getSummary()).isEqualTo("Total: 2 min\nBackground: 1 min");
}
@Test
public void setPreferenceSummary_notAllowShownPackage_setSummayAsNull() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
final BatteryDiffEntry batteryDiffEntry =
spy(createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS,
/*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS));
doReturn("com.android.googlequicksearchbox").when(batteryDiffEntry)
.getPackageName();
mBatteryChartPreferenceController.setPreferenceSummary(pref, batteryDiffEntry);
assertThat(pref.getSummary()).isNull();
}
@Test
public void onExpand_expandedIsTrue_addSystemEntriesToPreferenceGroup() {
doReturn(1).when(mAppListGroup).getPreferenceCount();
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
doReturn("label").when(mBatteryDiffEntry).getAppLabel();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
mBatteryChartPreferenceController.onExpand(/*isExpanded=*/ true);
final ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
verify(mAppListGroup).addPreference(captor.capture());
// Verifies the added preference.
assertThat(captor.getValue().getKey()).isEqualTo(PREF_KEY);
verify(mMetricsFeatureProvider)
.action(
mContext,
SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM,
true /*isExpanded*/);
}
@Test
public void onExpand_expandedIsFalse_removeSystemEntriesFromPreferenceGroup() {
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY);
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
// Verifies the cache is empty first.
assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty();
mBatteryChartPreferenceController.onExpand(/*isExpanded=*/ false);
verify(mAppListGroup).findPreference(PREF_KEY);
verify(mAppListGroup).removePreference(mPowerGaugePreference);
assertThat(mBatteryChartPreferenceController.mPreferenceCache).hasSize(1);
verify(mMetricsFeatureProvider)
.action(
mContext,
SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM,
false /*isExpanded*/);
}
@Test
public void refreshCategoryTitle_setLastFullChargeIntoBothTitleTextView() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mAppListPrefGroup =
spy(new PreferenceCategory(mContext));
mBatteryChartPreferenceController.mExpandDividerPreference =
spy(new ExpandDividerPreference(mContext));
// Simulates select all condition.
mBatteryChartPreferenceController.mDailyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
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())
.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 since last full charge");
}
@Test
public void selectedSlotText_selectAllDaysAllHours_returnNull() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
mBatteryChartPreferenceController.mDailyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
}
@Test
public void selectedSlotText_onlyOneDayDataSelectAllHours_returnNull() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mDailyChartIndex = 0;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
}
@Test
public void selectedSlotText_selectADayAllHours_onlyDayText() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
mBatteryChartPreferenceController.mDailyChartIndex = 1;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo("Sunday");
}
@Test
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.mDailyChartIndex = expectedDailyIndex;
mBatteryChartPreferenceController.mHourlyChartIndex = expectedHourlyIndex;
mBatteryChartPreferenceController.mIsExpanded = isExpanded;
mBatteryChartPreferenceController.onSaveInstanceState(bundle);
// Replaces the original controller with other values.
mBatteryChartPreferenceController.mDailyChartIndex = -1;
mBatteryChartPreferenceController.mHourlyChartIndex = -1;
mBatteryChartPreferenceController.mIsExpanded = false;
mBatteryChartPreferenceController.onCreate(bundle);
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(25));
assertThat(mBatteryChartPreferenceController.mDailyChartIndex)
.isEqualTo(expectedDailyIndex);
assertThat(mBatteryChartPreferenceController.mHourlyChartIndex)
.isEqualTo(expectedHourlyIndex);
assertThat(mBatteryChartPreferenceController.mIsExpanded).isTrue();
}
@Test
public void isValidToShowSummary_returnExpectedResult() {
assertThat(mBatteryChartPreferenceController
.isValidToShowSummary("com.google.android.apps.scone"))
.isTrue();
// Verifies the item which is defined in the array list.
assertThat(mBatteryChartPreferenceController
.isValidToShowSummary("com.android.googlequicksearchbox"))
.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(
int numOfHours) {
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
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(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(
mContext, foregroundUsageTimeInMs, backgroundUsageTimeInMs,
/*consumePower=*/ 0, mBatteryHistEntry);
}
private BatteryChartPreferenceControllerV2 createController() {
final BatteryChartPreferenceControllerV2 controller =
new BatteryChartPreferenceControllerV2(
mContext, "app_list", /*lifecycle=*/ null,
mSettingsActivity, mFragment);
controller.mPrefContext = mContext;
return controller;
}
}

View File

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

View File

@@ -1,245 +0,0 @@
/*
* 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.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
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;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@RunWith(RobolectricTestRunner.class)
public final class BatteryChartViewV2Test {
private Context mContext;
private BatteryChartViewV2 mBatteryChartView;
private FakeFeatureFactory mFeatureFactory;
private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
@Mock
private AccessibilityServiceInfo mMockAccessibilityServiceInfo;
@Mock
private AccessibilityManager mMockAccessibilityManager;
@Mock
private View mMockView;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mFeatureFactory = FakeFeatureFactory.setupForTest();
mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
mContext = spy(RuntimeEnvironment.application);
mContext.getResources().getConfiguration().setLocales(
new LocaleList(new Locale("en_US")));
mBatteryChartView = new BatteryChartViewV2(mContext);
doReturn(mMockAccessibilityManager).when(mContext)
.getSystemService(AccessibilityManager.class);
doReturn("TalkBackService").when(mMockAccessibilityServiceInfo).getId();
doReturn(Arrays.asList(mMockAccessibilityServiceInfo))
.when(mMockAccessibilityManager)
.getEnabledAccessibilityServiceList(anyInt());
}
@Test
public void isAccessibilityEnabled_disable_returnFalse() {
doReturn(false).when(mMockAccessibilityManager).isEnabled();
assertThat(BatteryChartViewV2.isAccessibilityEnabled(mContext)).isFalse();
}
@Test
public void isAccessibilityEnabled_emptyInfo_returnFalse() {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
doReturn(new ArrayList<AccessibilityServiceInfo>())
.when(mMockAccessibilityManager)
.getEnabledAccessibilityServiceList(anyInt());
assertThat(BatteryChartViewV2.isAccessibilityEnabled(mContext)).isFalse();
}
@Test
public void isAccessibilityEnabled_validServiceId_returnTrue() {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
assertThat(BatteryChartViewV2.isAccessibilityEnabled(mContext)).isTrue();
}
@Test
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 BatteryChartViewV2.TrapezoidSlot();
mBatteryChartView.mTrapezoidSlots[i].mLeft = i;
mBatteryChartView.mTrapezoidSlots[i].mRight = i + 0.5f;
}
final int[] selectedIndex = new int[1];
mBatteryChartView.setOnSelectListener(
trapezoidIndex -> {
selectedIndex[0] = trapezoidIndex;
});
// Verify onClick() a different index 1.
mBatteryChartView.mTouchUpEventX = 1;
selectedIndex[0] = Integer.MIN_VALUE;
mBatteryChartView.onClick(mMockView);
assertThat(selectedIndex[0]).isEqualTo(1);
// 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 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 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 clickable_accessibilityIsEnabledWithoutValidId_clickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(true).when(mMockAccessibilityManager).isEnabled();
doReturn(new ArrayList<AccessibilityServiceInfo>())
.when(mMockAccessibilityManager)
.getEnabledAccessibilityServiceList(anyInt());
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@Test
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 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.setViewModel(new BatteryChartViewModel(levels, texts,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(true).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
// Ensures the testing environment is correct.
assertThat(mBatteryChartView.isClickable()).isFalse();
// Turns off accessibility service.
doReturn(false).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
}
@Test
public void onAttachedToWindow_addAccessibilityStateChangeListener() {
mBatteryChartView.onAttachedToWindow();
verify(mMockAccessibilityManager)
.addAccessibilityStateChangeListener(mBatteryChartView);
}
@Test
public void onDetachedFromWindow_removeAccessibilityStateChangeListener() {
mBatteryChartView.onAttachedToWindow();
mBatteryChartView.mHandler.postDelayed(
mBatteryChartView.mUpdateClickableStateRun, 1000);
mBatteryChartView.onDetachedFromWindow();
verify(mMockAccessibilityManager)
.removeAccessibilityStateChangeListener(mBatteryChartView);
assertThat(mBatteryChartView.mHandler.hasCallbacks(
mBatteryChartView.mUpdateClickableStateRun))
.isFalse();
}
@Test
public void onAccessibilityStateChanged_postUpdateStateRunnable() {
mBatteryChartView.mHandler = spy(mBatteryChartView.mHandler);
mBatteryChartView.onAccessibilityStateChanged(/*enabled=*/ true);
verify(mBatteryChartView.mHandler)
.removeCallbacks(mBatteryChartView.mUpdateClickableStateRun);
verify(mBatteryChartView.mHandler)
.postDelayed(mBatteryChartView.mUpdateClickableStateRun, 500L);
}
}

View File

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

View File

@@ -26,6 +26,7 @@ import android.os.BatteryManager;
import android.os.BatteryUsageStats; import android.os.BatteryUsageStats;
import android.os.LocaleList; import android.os.LocaleList;
import android.os.UserHandle; import android.os.UserHandle;
import android.text.format.DateUtils;
import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
@@ -39,8 +40,8 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -173,7 +174,8 @@ public final class ConvertUtilsTest {
public void getIndexedUsageMap_returnsExpectedResult() { public void getIndexedUsageMap_returnsExpectedResult() {
// Creates the fake testing data. // Creates the fake testing data.
final int timeSlotSize = 2; 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 = final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
new HashMap<>(); new HashMap<>();
final BatteryHistEntry fakeEntry = createBatteryHistEntry( final BatteryHistEntry fakeEntry = createBatteryHistEntry(
@@ -270,11 +272,11 @@ public final class ConvertUtilsTest {
for (int index = 0; index < remainingSize; index++) { for (int index = 0; index < remainingSize; index++) {
batteryHistoryMap.put(105L + index + 1, new HashMap<>()); batteryHistoryMap.put(105L + index + 1, new HashMap<>());
} }
when(mPowerUsageFeatureProvider.getBatteryHistory(mContext)) when(mPowerUsageFeatureProvider.getBatteryHistorySinceLastFullCharge(mContext))
.thenReturn(batteryHistoryMap); .thenReturn(batteryHistoryMap);
final List<BatteryDiffEntry> batteryDiffEntryList = final List<BatteryDiffEntry> batteryDiffEntryList =
BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext); BatteryChartPreferenceController.getAppBatteryUsageData(mContext);
assertThat(batteryDiffEntryList).isNotEmpty(); assertThat(batteryDiffEntryList).isNotEmpty();
final BatteryDiffEntry resultEntry = batteryDiffEntryList.get(0); final BatteryDiffEntry resultEntry = batteryDiffEntryList.get(0);
@@ -472,4 +474,9 @@ public final class ConvertUtilsTest {
assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs); assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs); 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;
}
} }