some items are not clickable to launch the restriction page in the battery usage list, we will apply the disabled visual in the preferrence item to improve the UX (avoid users click the item without any action) Bug: 188751551 Bug: 188663505 Test: make SettingsgRoboTests Change-Id: Ib8925b8e191117543bb1c74d6d01191e3043fc73
703 lines
29 KiB
Java
703 lines
29 KiB
Java
/*
|
|
* Copyright (C) 2021 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;
|
|
|
|
import android.app.settings.SettingsEnums;
|
|
import android.content.Context;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.text.TextUtils;
|
|
import android.text.format.DateFormat;
|
|
import android.text.format.DateUtils;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
|
|
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.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.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
/** Controls the update for chart graph and the list items. */
|
|
public class BatteryChartPreferenceController extends AbstractPreferenceController
|
|
implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy,
|
|
OnSaveInstanceState, BatteryChartView.OnSelectListener, OnResume,
|
|
ExpandDividerPreference.OnExpandListener {
|
|
private static final String TAG = "BatteryChartPreferenceController";
|
|
private static final String KEY_FOOTER_PREF = "battery_graph_footer";
|
|
|
|
/** Desired battery history size for timestamp slots. */
|
|
public static final int DESIRED_HISTORY_SIZE = 25;
|
|
private static final int CHART_LEVEL_ARRAY_SIZE = 13;
|
|
private static final int CHART_KEY_ARRAY_SIZE = DESIRED_HISTORY_SIZE;
|
|
private static final long VALID_USAGE_TIME_DURATION = DateUtils.HOUR_IN_MILLIS * 2;
|
|
private static final long VALID_DIFF_DURATION = DateUtils.MINUTE_IN_MILLIS * 3;
|
|
|
|
// Keys for bundle instance to restore configurations.
|
|
private static final String KEY_EXPAND_SYSTEM_INFO = "expand_system_info";
|
|
private static final String KEY_CURRENT_TIME_SLOT = "current_time_slot";
|
|
|
|
private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
|
|
|
|
@VisibleForTesting
|
|
Map<Integer, List<BatteryDiffEntry>> mBatteryIndexedMap;
|
|
|
|
@VisibleForTesting Context mPrefContext;
|
|
@VisibleForTesting BatteryUtils mBatteryUtils;
|
|
@VisibleForTesting PreferenceGroup mAppListPrefGroup;
|
|
@VisibleForTesting BatteryChartView mBatteryChartView;
|
|
@VisibleForTesting ExpandDividerPreference mExpandDividerPreference;
|
|
|
|
@VisibleForTesting boolean mIsExpanded = false;
|
|
@VisibleForTesting int[] mBatteryHistoryLevels;
|
|
@VisibleForTesting long[] mBatteryHistoryKeys;
|
|
@VisibleForTesting int mTrapezoidIndex = BatteryChartView.SELECTED_INDEX_INVALID;
|
|
|
|
private boolean mIs24HourFormat = false;
|
|
private boolean mIsFooterPrefAdded = false;
|
|
private PreferenceScreen mPreferenceScreen;
|
|
private FooterPreference mFooterPreference;
|
|
|
|
private final String mPreferenceKey;
|
|
private final SettingsActivity mActivity;
|
|
private final InstrumentedPreferenceFragment mFragment;
|
|
private final CharSequence[] mNotAllowShowEntryPackages;
|
|
private final CharSequence[] mNotAllowShowSummaryPackages;
|
|
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
|
|
|
// Preference cache to avoid create new instance each time.
|
|
@VisibleForTesting
|
|
final Map<String, Preference> mPreferenceCache = new HashMap<>();
|
|
@VisibleForTesting
|
|
final List<BatteryDiffEntry> mSystemEntries = new ArrayList<>();
|
|
|
|
public BatteryChartPreferenceController(
|
|
Context context, String preferenceKey,
|
|
Lifecycle lifecycle, SettingsActivity activity,
|
|
InstrumentedPreferenceFragment fragment) {
|
|
super(context);
|
|
mActivity = activity;
|
|
mFragment = fragment;
|
|
mPreferenceKey = preferenceKey;
|
|
mIs24HourFormat = DateFormat.is24HourFormat(context);
|
|
mNotAllowShowSummaryPackages = context.getResources()
|
|
.getTextArray(R.array.allowlist_hide_summary_in_battery_usage);
|
|
mNotAllowShowEntryPackages = context.getResources()
|
|
.getTextArray(R.array.allowlist_hide_entry_in_battery_usage);
|
|
mMetricsFeatureProvider =
|
|
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
|
|
if (lifecycle != null) {
|
|
lifecycle.addObserver(this);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
if (savedInstanceState == null) {
|
|
return;
|
|
}
|
|
mTrapezoidIndex =
|
|
savedInstanceState.getInt(KEY_CURRENT_TIME_SLOT, mTrapezoidIndex);
|
|
mIsExpanded =
|
|
savedInstanceState.getBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded);
|
|
Log.d(TAG, String.format("onCreate() slotIndex=%d isExpanded=%b",
|
|
mTrapezoidIndex, 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_CURRENT_TIME_SLOT, mTrapezoidIndex);
|
|
savedInstance.putBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded);
|
|
Log.d(TAG, String.format("onSaveInstanceState() slotIndex=%d isExpanded=%b",
|
|
mTrapezoidIndex, 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_for_past_24));
|
|
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(
|
|
mPrefContext,
|
|
isAppEntry
|
|
? SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM
|
|
: SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM,
|
|
new Pair(ConvertUtils.METRIC_KEY_PACKAGE, packageName),
|
|
new Pair(ConvertUtils.METRIC_KEY_BATTERY_LEVEL, histEntry.mBatteryLevel),
|
|
new Pair(ConvertUtils.METRIC_KEY_BATTERY_USAGE, powerPref.getPercent()));
|
|
Log.d(TAG, String.format("handleClick() label=%s key=%s enntry=\n%s",
|
|
diffEntry.getAppLabel(), histEntry.getKey(), histEntry));
|
|
AdvancedPowerUsageDetail.startBatteryDetailPage(
|
|
mActivity, mFragment, diffEntry, powerPref.getPercent(),
|
|
isValidToShowSummary(packageName), getSlotInformation());
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onSelect(int trapezoidIndex) {
|
|
Log.d(TAG, "onChartSelect:" + trapezoidIndex);
|
|
refreshUi(trapezoidIndex, /*isForce=*/ false);
|
|
mMetricsFeatureProvider.action(
|
|
mPrefContext,
|
|
trapezoidIndex == BatteryChartView.SELECTED_INDEX_ALL
|
|
? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
|
|
: SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
|
|
}
|
|
|
|
@Override
|
|
public void onExpand(boolean isExpanded) {
|
|
mIsExpanded = isExpanded;
|
|
mMetricsFeatureProvider.action(
|
|
mPrefContext,
|
|
SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM,
|
|
isExpanded);
|
|
refreshExpandUi();
|
|
}
|
|
|
|
void setBatteryHistoryMap(
|
|
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
|
|
// Resets all battery history data relative variables.
|
|
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
|
|
mBatteryIndexedMap = null;
|
|
mBatteryHistoryKeys = null;
|
|
mBatteryHistoryLevels = null;
|
|
addFooterPreferenceIfNeeded(false);
|
|
return;
|
|
}
|
|
mBatteryHistoryKeys = getBatteryHistoryKeys(batteryHistoryMap);
|
|
mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE];
|
|
for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) {
|
|
final long timestamp = mBatteryHistoryKeys[index * 2];
|
|
final Map<String, BatteryHistEntry> entryMap = batteryHistoryMap.get(timestamp);
|
|
if (entryMap == null || entryMap.isEmpty()) {
|
|
Log.e(TAG, "abnormal entry list in the timestamp:"
|
|
+ ConvertUtils.utcToLocalTime(timestamp));
|
|
continue;
|
|
}
|
|
// Averages the battery level in each time slot to avoid corner conditions.
|
|
float batteryLevelCounter = 0;
|
|
for (BatteryHistEntry entry : entryMap.values()) {
|
|
batteryLevelCounter += entry.mBatteryLevel;
|
|
}
|
|
mBatteryHistoryLevels[index] =
|
|
Math.round(batteryLevelCounter / entryMap.size());
|
|
}
|
|
forceRefreshUi();
|
|
Log.d(TAG, String.format(
|
|
"setBatteryHistoryMap() size=%d\nkeys=%s\nlevels=%s",
|
|
batteryHistoryMap.size(),
|
|
utcToLocalTime(mBatteryHistoryKeys),
|
|
Arrays.toString(mBatteryHistoryLevels)));
|
|
|
|
// 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
|
|
boolean refreshUi(int trapezoidIndex, boolean isForce) {
|
|
// Invalid refresh condition.
|
|
if (mBatteryIndexedMap == null
|
|
|| mBatteryChartView == null
|
|
|| (mTrapezoidIndex == trapezoidIndex && !isForce)) {
|
|
return false;
|
|
}
|
|
Log.d(TAG, String.format("refreshUi: index=%d size=%d isForce:%b",
|
|
trapezoidIndex, mBatteryIndexedMap.size(), isForce));
|
|
|
|
mTrapezoidIndex = trapezoidIndex;
|
|
mHandler.post(() -> {
|
|
removeAndCacheAllPrefs();
|
|
addAllPreferences();
|
|
refreshCategoryTitle();
|
|
});
|
|
return true;
|
|
}
|
|
|
|
private void addAllPreferences() {
|
|
final List<BatteryDiffEntry> entries =
|
|
mBatteryIndexedMap.get(Integer.valueOf(mTrapezoidIndex));
|
|
addFooterPreferenceIfNeeded(!entries.isEmpty());
|
|
if (entries == null) {
|
|
Log.w(TAG, "cannot find BatteryDiffEntry for:" + mTrapezoidIndex);
|
|
return;
|
|
}
|
|
// Separates data into two groups and sort them individually.
|
|
final List<BatteryDiffEntry> appEntries = new ArrayList<>();
|
|
mSystemEntries.clear();
|
|
entries.forEach(entry -> {
|
|
final String packageName = entry.getPackageName();
|
|
if (!isValidToShowEntry(packageName)) {
|
|
Log.w(TAG, "ignore showing item:" + packageName);
|
|
return;
|
|
}
|
|
if (entry.isSystemEntry()) {
|
|
mSystemEntries.add(entry);
|
|
} else {
|
|
appEntries.add(entry);
|
|
}
|
|
// Validates the usage time if users click a specific slot.
|
|
if (mTrapezoidIndex >= 0) {
|
|
validateUsageTime(entry);
|
|
}
|
|
});
|
|
Collections.sort(appEntries, BatteryDiffEntry.COMPARATOR);
|
|
Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
|
|
Log.d(TAG, String.format("addAllPreferences() app=%d system=%d",
|
|
appEntries.size(), mSystemEntries.size()));
|
|
|
|
// Adds app entries to the list if it is not empty.
|
|
if (!appEntries.isEmpty()) {
|
|
addPreferenceToScreen(appEntries);
|
|
}
|
|
// Adds the expabable divider if we have system entries data.
|
|
if (!mSystemEntries.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\n" + entry);
|
|
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\n" + entry);
|
|
} 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() {
|
|
if (mIsExpanded) {
|
|
addPreferenceToScreen(mSystemEntries);
|
|
} else {
|
|
// Removes and recycles all system entries to hide all of them.
|
|
for (BatteryDiffEntry entry : mSystemEntries) {
|
|
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) {
|
|
// Null means we show all information without a specific time slot.
|
|
if (slotInformation == null) {
|
|
return isApp
|
|
? mPrefContext.getString(R.string.battery_app_usage_for_past_24)
|
|
: mPrefContext.getString(R.string.battery_system_usage_for_past_24);
|
|
} else {
|
|
return isApp
|
|
? mPrefContext.getString(R.string.battery_app_usage_for, slotInformation)
|
|
: mPrefContext.getString(R.string.battery_system_usage_for ,slotInformation);
|
|
}
|
|
}
|
|
|
|
private String getSlotInformation() {
|
|
if (mTrapezoidIndex < 0) {
|
|
return null;
|
|
}
|
|
final String fromHour = ConvertUtils.utcToLocalTimeHour(
|
|
mBatteryHistoryKeys[mTrapezoidIndex * 2], mIs24HourFormat);
|
|
final String toHour = ConvertUtils.utcToLocalTimeHour(
|
|
mBatteryHistoryKeys[(mTrapezoidIndex + 1) * 2], mIs24HourFormat);
|
|
return String.format("%s - %s", fromHour, toHour);
|
|
}
|
|
|
|
@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 !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) {
|
|
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 static String utcToLocalTime(long[] timestamps) {
|
|
final StringBuilder builder = new StringBuilder();
|
|
for (int index = 0; index < timestamps.length; index++) {
|
|
builder.append(String.format("%s| ",
|
|
ConvertUtils.utcToLocalTime(timestamps[index])));
|
|
}
|
|
return builder.toString();
|
|
}
|
|
|
|
private static boolean contains(String target, CharSequence[] packageNames) {
|
|
if (target != null && packageNames != null) {
|
|
for (CharSequence packageName : packageNames) {
|
|
if (TextUtils.equals(target, packageName)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static boolean validateUsageTime(BatteryDiffEntry entry) {
|
|
final long foregroundUsageTimeInMs = entry.mForegroundUsageTimeInMs;
|
|
final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs;
|
|
final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs;
|
|
if (foregroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
|
|
|| backgroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
|
|
|| totalUsageTimeInMs > VALID_USAGE_TIME_DURATION) {
|
|
Log.e(TAG, "validateUsageTime() fail for\n" + entry);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static List<BatteryDiffEntry> getBatteryLast24HrUsageData(Context context) {
|
|
final long start = System.currentTimeMillis();
|
|
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
|
|
FeatureFactory.getFactory(context)
|
|
.getPowerUsageFeatureProvider(context)
|
|
.getBatteryHistory(context);
|
|
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
|
|
return null;
|
|
}
|
|
Log.d(TAG, String.format("getBatteryLast24HrData() size=%d time=&d/ms",
|
|
batteryHistoryMap.size(), (System.currentTimeMillis() - start)));
|
|
final Map<Integer, List<BatteryDiffEntry>> batteryIndexedMap =
|
|
ConvertUtils.getIndexedUsageMap(
|
|
context,
|
|
/*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1,
|
|
getBatteryHistoryKeys(batteryHistoryMap),
|
|
batteryHistoryMap,
|
|
/*purgeLowPercentageAndFakeData=*/ true);
|
|
return batteryIndexedMap.get(BatteryChartView.SELECTED_INDEX_ALL);
|
|
}
|
|
|
|
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();
|
|
});
|
|
}
|
|
}
|
|
}
|