Battery usage page latency improvement (1-8)

Save battery slot diff data into database in hourly job. Then read the
saved diff data and only calculate the remaining data. This could speed
up the battery usage loading.

Bug: 261163071
Fix: 261163071
Test: manual
Change-Id: Icd4868ca9326b64b17ddbccdb0311e755dc68026
This commit is contained in:
Zaiyue Xue
2023-07-14 20:57:49 +08:00
parent 83c8f47ddd
commit 50da7feeb9
49 changed files with 2713 additions and 1443 deletions

View File

@@ -16,6 +16,8 @@
package com.android.settings.fuelgauge;
import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUserConsumer;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.backup.BackupManager;
@@ -41,7 +43,6 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryHistEntry;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.HelpUtils;
@@ -149,14 +150,13 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
Context context, int sourceMetricsCategory,
BatteryDiffEntry diffEntry, String usagePercent, String slotInformation,
boolean showTimeInformation) {
final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry;
final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
// configure the launch argument.
launchArgs.mUsagePercent = usagePercent;
launchArgs.mPackageName = diffEntry.getPackageName();
launchArgs.mAppLabel = diffEntry.getAppLabel();
launchArgs.mSlotInformation = slotInformation;
launchArgs.mUid = (int) histEntry.mUid;
launchArgs.mUid = (int) diffEntry.mUid;
launchArgs.mIconId = diffEntry.getAppIconId();
launchArgs.mConsumedPower = (int) diffEntry.mConsumePower;
if (showTimeInformation) {
@@ -164,7 +164,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
launchArgs.mBackgroundTimeMs = diffEntry.mBackgroundUsageTimeInMs;
launchArgs.mScreenOnTimeMs = diffEntry.mScreenOnTimeInMs;
}
launchArgs.mIsUserEntry = histEntry.isUserEntry();
launchArgs.mIsUserEntry = isUserConsumer(diffEntry.mConsumerType);
startBatteryDetailPage(context, sourceMetricsCategory, launchArgs);
}

View File

@@ -1,83 +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.usage.UsageEvents;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
/** Load app usage events data in the background. */
public final class AppUsageDataLoader {
private static final String TAG = "AppUsageDataLoader";
// For testing only.
@VisibleForTesting
static Supplier<Map<Long, UsageEvents>> sFakeAppUsageEventsSupplier;
@VisibleForTesting
static Supplier<List<AppUsageEvent>> sFakeUsageEventsListSupplier;
private AppUsageDataLoader() {}
static void enqueueWork(final Context context) {
AsyncTask.execute(() -> {
Log.d(TAG, "loadAppUsageDataSafely() in the AsyncTask");
loadAppUsageDataSafely(context.getApplicationContext());
});
}
@VisibleForTesting
static void loadAppUsageData(final Context context) {
final long start = System.currentTimeMillis();
final Map<Long, UsageEvents> appUsageEvents =
sFakeAppUsageEventsSupplier != null
? sFakeAppUsageEventsSupplier.get()
: DataProcessor.getAppUsageEvents(context);
if (appUsageEvents == null) {
Log.w(TAG, "loadAppUsageData() returns null");
return;
}
final List<AppUsageEvent> appUsageEventList =
sFakeUsageEventsListSupplier != null
? sFakeUsageEventsListSupplier.get()
: DataProcessor.generateAppUsageEventListFromUsageEvents(
context, appUsageEvents);
if (appUsageEventList == null || appUsageEventList.isEmpty()) {
Log.w(TAG, "loadAppUsageData() returns null or empty content");
return;
}
final long elapsedTime = System.currentTimeMillis() - start;
Log.d(TAG, String.format("loadAppUsageData() size=%d in %d/ms", appUsageEventList.size(),
elapsedTime));
// Uploads the AppUsageEvent data into database.
DatabaseUtils.sendAppUsageEventData(context, appUsageEventList);
}
private static void loadAppUsageDataSafely(final Context context) {
try {
loadAppUsageData(context);
} catch (RuntimeException e) {
Log.e(TAG, "loadAppUsageData:" + e);
}
}
}

View File

@@ -125,7 +125,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
Map<Integer, Map<Integer, BatteryDiffData>> mBatteryUsageMap;
private boolean mIs24HourFormat;
private boolean mHourlyChartVisible = true;
private View mBatteryChartViewGroup;
private TextView mChartSummaryTextView;
private BatteryChartViewModel mDailyViewModel;
@@ -227,20 +226,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mOnBatteryTipsUpdatedListener = listener;
}
void setBatteryHistoryMap(
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null"
: ("size=" + batteryHistoryMap.size())));
// Ensure the battery chart group is visible for users.
animateBatteryChartViewGroup();
final BatteryLevelData batteryLevelData =
DataProcessManager.getBatteryLevelData(mContext, mHandler, batteryHistoryMap,
batteryUsageMap -> {
mBatteryUsageMap = batteryUsageMap;
logScreenUsageTime();
refreshUi();
});
Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
void onBatteryLevelDataUpdate(final BatteryLevelData batteryLevelData) {
Log.d(TAG, "onBatteryLevelDataUpdate: " + batteryLevelData);
mMetricsFeatureProvider.action(
mPrefContext,
SettingsEnums.ACTION_BATTERY_HISTORY_LOADED,
@@ -271,6 +258,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
refreshUi();
}
void onBatteryUsageMapUpdate(Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
Log.d(TAG, "onBatteryUsageMapUpdate: " + batteryUsageMap);
mBatteryUsageMap = batteryUsageMap;
logScreenUsageTime();
refreshUi();
}
void setBatteryChartView(@NonNull final BatteryChartView dailyChartView,
@NonNull final BatteryChartView hourlyChartView) {
final View parentView = (View) dailyChartView.getParent();
@@ -472,10 +466,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
private void animateBatteryHourlyChartView(final boolean visible) {
if (mHourlyChartView == null || mHourlyChartVisible == visible) {
if (mHourlyChartView == null
|| (mHourlyChartView.getVisibility() == View.VISIBLE) == visible) {
return;
}
mHourlyChartVisible = visible;
if (visible) {
mHourlyChartView.setVisibility(View.VISIBLE);
@@ -632,10 +626,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
return null;
}
for (BatteryDiffEntry entry : entries) {
final BatteryHistEntry batteryHistEntry = entry.mBatteryHistEntry;
if (batteryHistEntry != null
&& batteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY
&& batteryHistEntry.mUserId == userId
if (!entry.isSystemEntry()
&& entry.mUserId == userId
&& packageName.equals(entry.getPackageName())) {
return entry;
}

View File

@@ -17,6 +17,7 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.Utils.formatPercentage;
import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS;
import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
import static java.lang.Math.abs;
import static java.lang.Math.round;
@@ -615,8 +616,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private static boolean isTrapezoidValid(
@NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel.getLevel(trapezoidIndex) != null
&& viewModel.getLevel(trapezoidIndex + 1) != null;
return viewModel.getLevel(trapezoidIndex) != BATTERY_LEVEL_UNKNOWN
&& viewModel.getLevel(trapezoidIndex + 1) != BATTERY_LEVEL_UNKNOWN;
}
private static boolean isTrapezoidIndexValid(

View File

@@ -16,6 +16,8 @@
package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging;
import android.content.Context;
import android.os.BatteryConsumer;
@@ -34,6 +36,10 @@ import java.util.Set;
public class BatteryDiffData {
static final double SMALL_PERCENTAGE_THRESHOLD = 1f;
private final long mStartTimestamp;
private final long mEndTimestamp;
private final int mStartBatteryLevel;
private final int mEndBatteryLevel;
private final long mScreenOnTime;
private final List<BatteryDiffEntry> mAppEntries;
private final List<BatteryDiffEntry> mSystemEntries;
@@ -41,12 +47,20 @@ public class BatteryDiffData {
/** Constructor for the diff entries. */
public BatteryDiffData(
final Context context,
final long startTimestamp,
final long endTimestamp,
final int startBatteryLevel,
final int endBatteryLevel,
final long screenOnTime,
final @NonNull List<BatteryDiffEntry> appDiffEntries,
final @NonNull List<BatteryDiffEntry> systemDiffEntries,
final @NonNull Set<String> systemAppsPackageNames,
final @NonNull Set<Integer> systemAppsUids,
final boolean isAccumulated) {
mStartTimestamp = startTimestamp;
mEndTimestamp = endTimestamp;
mStartBatteryLevel = startBatteryLevel;
mEndBatteryLevel = endBatteryLevel;
mScreenOnTime = screenOnTime;
mAppEntries = appDiffEntries;
mSystemEntries = systemDiffEntries;
@@ -63,18 +77,48 @@ public class BatteryDiffData {
processAndSortEntries(mSystemEntries);
}
public long getScreenOnTime() {
long getStartTimestamp() {
return mStartTimestamp;
}
long getEndTimestamp() {
return mEndTimestamp;
}
int getStartBatteryLevel() {
return mStartBatteryLevel;
}
int getEndBatteryLevel() {
return mEndBatteryLevel;
}
long getScreenOnTime() {
return mScreenOnTime;
}
public List<BatteryDiffEntry> getAppDiffEntryList() {
List<BatteryDiffEntry> getAppDiffEntryList() {
return mAppEntries;
}
public List<BatteryDiffEntry> getSystemDiffEntryList() {
List<BatteryDiffEntry> getSystemDiffEntryList() {
return mSystemEntries;
}
@Override
public String toString() {
return new StringBuilder("BatteryDiffData{")
.append("startTimestamp:" + utcToLocalTimeForLogging(mStartTimestamp))
.append("|endTimestamp:" + utcToLocalTimeForLogging(mEndTimestamp))
.append("|startLevel:" + mStartBatteryLevel)
.append("|endLevel:" + mEndBatteryLevel)
.append("|screenOnTime:" + mScreenOnTime)
.append("|appEntries.size:" + mAppEntries.size())
.append("|systemEntries.size:" + mSystemEntries.size())
.append("}")
.toString();
}
/** Removes fake usage data and hidden packages. */
private void purgeBatteryDiffData(final PowerUsageFeatureProvider featureProvider) {
purgeBatteryDiffData(featureProvider, mAppEntries);
@@ -109,7 +153,7 @@ public class BatteryDiffData {
final long screenOnTimeInMs = entry.mScreenOnTimeInMs;
final double comsumePower = entry.mConsumePower;
final String packageName = entry.getPackageName();
final Integer componentId = entry.mBatteryHistEntry.mDrainType;
final Integer componentId = entry.mComponentId;
if ((screenOnTimeInMs < screenOnTimeThresholdInMs
&& comsumePower < consumePowerThreshold)
|| ConvertUtils.FAKE_PACKAGE_NAME.equals(packageName)
@@ -130,14 +174,16 @@ public class BatteryDiffData {
final @NonNull Set<Integer> systemAppsUids,
final @NonNull List<BatteryDiffEntry> appEntries) {
final List<String> systemAppsAllowlist = featureProvider.getSystemAppsAllowlist();
BatteryDiffEntry.SystemAppsBatteryDiffEntry systemAppsDiffEntry = null;
BatteryDiffEntry systemAppsDiffEntry = null;
final Iterator<BatteryDiffEntry> appListIterator = appEntries.iterator();
while (appListIterator.hasNext()) {
final BatteryDiffEntry batteryDiffEntry = appListIterator.next();
if (needsCombineInSystemApp(batteryDiffEntry, systemAppsAllowlist,
systemAppsPackageNames, systemAppsUids)) {
if (systemAppsDiffEntry == null) {
systemAppsDiffEntry = new BatteryDiffEntry.SystemAppsBatteryDiffEntry(context);
systemAppsDiffEntry = new BatteryDiffEntry(context,
BatteryDiffEntry.SYSTEM_APPS_KEY, BatteryDiffEntry.SYSTEM_APPS_KEY,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
}
systemAppsDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
systemAppsDiffEntry.mForegroundUsageTimeInMs +=
@@ -159,17 +205,18 @@ public class BatteryDiffData {
final Set<Integer> othersSystemComponentSet = featureProvider.getOthersSystemComponentSet();
final Set<String> othersCustomComponentNameSet =
featureProvider.getOthersCustomComponentNameSet();
BatteryDiffEntry.OthersBatteryDiffEntry othersDiffEntry = null;
BatteryDiffEntry othersDiffEntry = null;
final Iterator<BatteryDiffEntry> systemListIterator = systemEntries.iterator();
while (systemListIterator.hasNext()) {
final BatteryDiffEntry batteryDiffEntry = systemListIterator.next();
final int componentId = batteryDiffEntry.mBatteryHistEntry.mDrainType;
final int componentId = batteryDiffEntry.mComponentId;
if (othersSystemComponentSet.contains(componentId) || (
componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
&& othersCustomComponentNameSet.contains(
batteryDiffEntry.getAppLabel()))) {
if (othersDiffEntry == null) {
othersDiffEntry = new BatteryDiffEntry.OthersBatteryDiffEntry(context);
othersDiffEntry = new BatteryDiffEntry(context, BatteryDiffEntry.OTHERS_KEY,
BatteryDiffEntry.OTHERS_KEY, ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
}
othersDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
othersDiffEntry.setTotalConsumePower(
@@ -188,7 +235,7 @@ public class BatteryDiffData {
final @NonNull List<String> systemAppsAllowlist,
final @NonNull Set<String> systemAppsPackageNames,
final @NonNull Set<Integer> systemAppsUids) {
if (batteryDiffEntry.mBatteryHistEntry.mIsHidden) {
if (batteryDiffEntry.mIsHidden) {
return true;
}
@@ -201,7 +248,7 @@ public class BatteryDiffData {
return true;
}
int uid = (int) batteryDiffEntry.mBatteryHistEntry.mUid;
int uid = (int) batteryDiffEntry.mUid;
return systemAppsPackageNames.contains(packageName) || systemAppsUids.contains(uid);
}

View File

@@ -15,7 +15,6 @@
*/
package com.android.settings.fuelgauge.batteryusage;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -24,6 +23,7 @@ import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.VisibleForTesting;
@@ -45,12 +45,29 @@ public class BatteryDiffEntry {
static final Map<String, BatteryEntry.NameAndIcon> sResourceCache = new HashMap<>();
// Whether a specific item is valid to launch restriction page?
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
/** A comparator for {@link BatteryDiffEntry} based on the sorting key. */
public static final Comparator<BatteryDiffEntry> COMPARATOR =
static final Comparator<BatteryDiffEntry> COMPARATOR =
(a, b) -> Double.compare(b.getSortingKey(), a.getSortingKey());
static final String SYSTEM_APPS_KEY = "A|SystemApps";
static final String OTHERS_KEY = "S|Others";
// key -> (label_id, icon_id)
private static final Map<String, Pair<Integer, Integer>> SPECIAL_ENTRY_MAP = Map.of(
SYSTEM_APPS_KEY,
Pair.create(R.string.battery_usage_system_apps, R.drawable.ic_power_system),
OTHERS_KEY,
Pair.create(R.string.battery_usage_others,
R.drawable.ic_settings_battery_usage_others));
public long mUid;
public long mUserId;
public String mKey;
public boolean mIsHidden;
public int mComponentId;
public String mLegacyPackageName;
public String mLegacyLabel;
public int mConsumerType;
public long mForegroundUsageTimeInMs;
public long mBackgroundUsageTimeInMs;
public long mScreenOnTimeInMs;
@@ -59,8 +76,6 @@ public class BatteryDiffEntry {
public double mForegroundServiceUsageConsumePower;
public double mBackgroundUsageConsumePower;
public double mCachedUsageConsumePower;
// A BatteryHistEntry corresponding to this diff usage data.
public final BatteryHistEntry mBatteryHistEntry;
protected Context mContext;
@@ -83,6 +98,14 @@ public class BatteryDiffEntry {
public BatteryDiffEntry(
Context context,
long uid,
long userId,
String key,
boolean isHidden,
int componentId,
String legacyPackageName,
String legacyLabel,
int consumerType,
long foregroundUsageTimeInMs,
long backgroundUsageTimeInMs,
long screenOnTimeInMs,
@@ -90,21 +113,36 @@ public class BatteryDiffEntry {
double foregroundUsageConsumePower,
double foregroundServiceUsageConsumePower,
double backgroundUsageConsumePower,
double cachedUsageConsumePower,
BatteryHistEntry batteryHistEntry) {
double cachedUsageConsumePower) {
mContext = context;
mUid = uid;
mUserId = userId;
mKey = key;
mIsHidden = isHidden;
mComponentId = componentId;
mLegacyPackageName = legacyPackageName;
mLegacyLabel = legacyLabel;
mConsumerType = consumerType;
mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
mScreenOnTimeInMs = screenOnTimeInMs;
mConsumePower = consumePower;
mForegroundUsageConsumePower = foregroundUsageConsumePower;
mForegroundServiceUsageConsumePower = foregroundServiceUsageConsumePower;
mBackgroundUsageConsumePower = backgroundUsageConsumePower;
mCachedUsageConsumePower = cachedUsageConsumePower;
mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
mScreenOnTimeInMs = screenOnTimeInMs;
mBatteryHistEntry = batteryHistEntry;
mUserManager = context.getSystemService(UserManager.class);
}
public BatteryDiffEntry(Context context, String key, String legacyLabel, int consumerType) {
this(context, /*uid=*/ 0, /*userId=*/ 0, key, /*isHidden=*/ false, /*componentId=*/ -1,
/*legacyPackageName=*/ null, legacyLabel, consumerType,
/*foregroundUsageTimeInMs=*/ 0, /*backgroundUsageTimeInMs=*/ 0,
/*screenOnTimeInMs=*/ 0, /*consumePower=*/ 0, /*foregroundUsageConsumePower=*/ 0,
/*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0,
/*cachedUsageConsumePower=*/ 0);
}
/** Sets the total consumed power in a specific time slot. */
public void setTotalConsumePower(double totalConsumePower) {
mTotalConsumePower = totalConsumePower;
@@ -135,13 +173,22 @@ public class BatteryDiffEntry {
/** Gets the key for sorting */
public double getSortingKey() {
return getPercentage() + getAdjustPercentageOffset();
return getKey() != null && SPECIAL_ENTRY_MAP.containsKey(getKey())
? -1 : getPercentage() + getAdjustPercentageOffset();
}
/** Clones a new instance. */
public BatteryDiffEntry clone() {
return new BatteryDiffEntry(
this.mContext,
this.mUid,
this.mUserId,
this.mKey,
this.mIsHidden,
this.mComponentId,
this.mLegacyPackageName,
this.mLegacyLabel,
this.mConsumerType,
this.mForegroundUsageTimeInMs,
this.mBackgroundUsageTimeInMs,
this.mScreenOnTimeInMs,
@@ -149,17 +196,14 @@ public class BatteryDiffEntry {
this.mForegroundUsageConsumePower,
this.mForegroundServiceUsageConsumePower,
this.mBackgroundUsageConsumePower,
this.mCachedUsageConsumePower,
this.mBatteryHistEntry /*same instance*/);
this.mCachedUsageConsumePower);
}
/** Gets the app label name for this entry. */
public String getAppLabel() {
loadLabelAndIcon();
// Returns default applicationn label if we cannot find it.
return mAppLabel == null || mAppLabel.length() == 0
? mBatteryHistEntry.mAppLabel
: mAppLabel;
// Returns default application label if we cannot find it.
return mAppLabel == null || mAppLabel.length() == 0 ? mLegacyLabel : mAppLabel;
}
/** Gets the app icon {@link Drawable} for this entry. */
@@ -179,7 +223,7 @@ public class BatteryDiffEntry {
/** Gets the searching package name for UID battery type. */
public String getPackageName() {
final String packageName = mDefaultPackageName != null
? mDefaultPackageName : mBatteryHistEntry.mPackageName;
? mDefaultPackageName : mLegacyPackageName;
if (packageName == null) {
return packageName;
}
@@ -198,10 +242,10 @@ public class BatteryDiffEntry {
/** Whether the current BatteryDiffEntry is system component or not. */
public boolean isSystemEntry() {
if (mBatteryHistEntry.mIsHidden) {
if (mIsHidden) {
return false;
}
switch (mBatteryHistEntry.mConsumerType) {
switch (mConsumerType) {
case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
return true;
@@ -236,12 +280,22 @@ public class BatteryDiffEntry {
updateRestrictionFlagState();
sValidForRestriction.put(getKey(), Boolean.valueOf(mValidForRestriction));
if (getKey() != null && SPECIAL_ENTRY_MAP.containsKey(getKey())) {
Pair<Integer, Integer> pair = SPECIAL_ENTRY_MAP.get(getKey());
mAppLabel = mContext.getString(pair.first);
mAppIconId = pair.second;
mAppIcon = mContext.getDrawable(mAppIconId);
sResourceCache.put(
getKey(),
new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
return;
}
// Loads application icon and label based on consumer type.
switch (mBatteryHistEntry.mConsumerType) {
switch (mConsumerType) {
case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
final BatteryEntry.NameAndIcon nameAndIconForUser =
BatteryEntry.getNameAndIconFromUserId(
mContext, (int) mBatteryHistEntry.mUserId);
BatteryEntry.getNameAndIconFromUserId(mContext, (int) mUserId);
if (nameAndIconForUser != null) {
mAppIcon = nameAndIconForUser.mIcon;
mAppLabel = nameAndIconForUser.mName;
@@ -252,8 +306,7 @@ public class BatteryDiffEntry {
break;
case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
final BatteryEntry.NameAndIcon nameAndIconForSystem =
BatteryEntry.getNameAndIconFromPowerComponent(
mContext, mBatteryHistEntry.mDrainType);
BatteryEntry.getNameAndIconFromPowerComponent(mContext, mComponentId);
if (nameAndIconForSystem != null) {
mAppLabel = nameAndIconForSystem.mName;
if (nameAndIconForSystem.mIconId != 0) {
@@ -283,12 +336,12 @@ public class BatteryDiffEntry {
}
String getKey() {
return mBatteryHistEntry.getKey();
return mKey;
}
@VisibleForTesting
void updateRestrictionFlagState() {
if (!mBatteryHistEntry.isAppEntry()) {
if (isSystemEntry()) {
mValidForRestriction = false;
return;
}
@@ -348,7 +401,7 @@ public class BatteryDiffEntry {
return;
}
final int uid = (int) mBatteryHistEntry.mUid;
final int uid = (int) mUid;
final String[] packages = packageManager.getPackagesForUid(uid);
// Loads special defined application label and icon if available.
if (packages == null || packages.length == 0) {
@@ -394,8 +447,7 @@ public class BatteryDiffEntry {
StringUtil.formatElapsedTime(mContext, (double) mScreenOnTimeInMs,
/*withSeconds=*/ true, /*collapseTimeUnit=*/ false)))
.append(String.format("\n\tpackage:%s|%s uid:%d userId:%d",
mBatteryHistEntry.mPackageName, getPackageName(),
mBatteryHistEntry.mUid, mBatteryHistEntry.mUserId));
mLegacyPackageName, getPackageName(), mUid, mUserId));
return builder.toString();
}
@@ -406,130 +458,8 @@ public class BatteryDiffEntry {
}
private Drawable getBadgeIconForUser(Drawable icon) {
final int userId = UserHandle.getUserId((int) mBatteryHistEntry.mUid);
final int userId = UserHandle.getUserId((int) mUid);
return userId == UserHandle.USER_OWNER ? icon :
mUserManager.getBadgedIconForUser(icon, new UserHandle(userId));
}
/** Specific battery diff entry for system apps. */
static class SystemAppsBatteryDiffEntry extends BatteryDiffEntry {
SystemAppsBatteryDiffEntry(Context context) {
super(context,
/*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ 0,
/*screenOnTimeInMs=*/ 0,
/*consumePower=*/ 0,
/*foregroundUsageConsumePower=*/ 0,
/*foregroundServiceUsageConsumePower=*/ 0,
/*backgroundUsageConsumePower=*/ 0,
/*cachedUsageConsumePower=*/ 0,
new BatteryHistEntry(new ContentValues()));
}
@Override
public String getKey() {
return "A|SystemApps";
}
@Override
public String getAppLabel() {
return mContext.getString(R.string.battery_usage_system_apps);
}
@Override
public Drawable getAppIcon() {
return mContext.getDrawable(R.drawable.ic_power_system);
}
@Override
public boolean validForRestriction() {
return false;
}
@Override
public boolean isSystemEntry() {
return false;
}
@Override
public double getSortingKey() {
// Always on the bottom of the app list.
return -1;
}
@Override
public BatteryDiffEntry clone() {
SystemAppsBatteryDiffEntry newEntry = new SystemAppsBatteryDiffEntry(this.mContext);
newEntry.mForegroundUsageTimeInMs = this.mForegroundUsageTimeInMs;
newEntry.mBackgroundUsageTimeInMs = this.mBackgroundUsageTimeInMs;
newEntry.mScreenOnTimeInMs = this.mScreenOnTimeInMs;
newEntry.mConsumePower = this.mConsumePower;
newEntry.mForegroundUsageConsumePower = this.mForegroundUsageConsumePower;
newEntry.mForegroundServiceUsageConsumePower = this.mForegroundServiceUsageConsumePower;
newEntry.mBackgroundUsageConsumePower = this.mBackgroundUsageConsumePower;
newEntry.mCachedUsageConsumePower = this.mCachedUsageConsumePower;
return newEntry;
}
}
/** Specific battery diff entry for others. */
static class OthersBatteryDiffEntry extends BatteryDiffEntry {
OthersBatteryDiffEntry(Context context) {
super(context,
/*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ 0,
/*screenOnTimeInMs=*/ 0,
/*consumePower=*/ 0,
/*foregroundUsageConsumePower=*/ 0,
/*foregroundServiceUsageConsumePower=*/ 0,
/*backgroundUsageConsumePower=*/ 0,
/*cachedUsageConsumePower=*/ 0,
new BatteryHistEntry(new ContentValues()));
}
@Override
public String getKey() {
return "S|Others";
}
@Override
public String getAppLabel() {
return mContext.getString(R.string.battery_usage_others);
}
@Override
public Drawable getAppIcon() {
return mContext.getDrawable(R.drawable.ic_settings_battery_usage_others);
}
@Override
public boolean validForRestriction() {
return false;
}
@Override
public boolean isSystemEntry() {
return true;
}
@Override
public double getSortingKey() {
// Always on the bottom of the system list.
return -1;
}
@Override
public BatteryDiffEntry clone() {
OthersBatteryDiffEntry newEntry = new OthersBatteryDiffEntry(this.mContext);
newEntry.mForegroundUsageTimeInMs = this.mForegroundUsageTimeInMs;
newEntry.mBackgroundUsageTimeInMs = this.mBackgroundUsageTimeInMs;
newEntry.mScreenOnTimeInMs = this.mScreenOnTimeInMs;
newEntry.mConsumePower = this.mConsumePower;
newEntry.mForegroundUsageConsumePower = this.mForegroundUsageConsumePower;
newEntry.mForegroundServiceUsageConsumePower = this.mForegroundServiceUsageConsumePower;
newEntry.mBackgroundUsageConsumePower = this.mBackgroundUsageConsumePower;
newEntry.mCachedUsageConsumePower = this.mCachedUsageConsumePower;
return newEntry;
}
}
}

View File

@@ -169,21 +169,6 @@ public class BatteryHistEntry {
return mIsValidEntry;
}
/** Whether this {@link BatteryHistEntry} is user consumer or not. */
public boolean isUserEntry() {
return mConsumerType == ConvertUtils.CONSUMER_TYPE_USER_BATTERY;
}
/** Whether this {@link BatteryHistEntry} is app consumer or not. */
public boolean isAppEntry() {
return mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY;
}
/** Whether this {@link BatteryHistEntry} is system consumer or not. */
public boolean isSystemEntry() {
return mConsumerType == ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY;
}
/** Gets an identifier to represent this {@link BatteryHistEntry}. */
public String getKey() {
if (mKey == null) {

View File

@@ -1,45 +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.content.Context;
import com.android.settingslib.utils.AsyncLoaderCompat;
import java.util.Calendar;
import java.util.Map;
/** Loader that can be used to load battery history information. */
public class BatteryHistoryLoader
extends AsyncLoaderCompat<Map<Long, Map<String, BatteryHistEntry>>> {
private static final String TAG = "BatteryHistoryLoader";
private final Context mContext;
public BatteryHistoryLoader(Context context) {
super(context);
mContext = context;
}
@Override
protected void onDiscardResult(Map<Long, Map<String, BatteryHistEntry>> result) {
}
@Override
public Map<Long, Map<String, BatteryHistEntry>> loadInBackground() {
return DatabaseUtils.getHistoryMapSinceLastFullCharge(mContext, Calendar.getInstance());
}
}

View File

@@ -17,17 +17,13 @@
package com.android.settings.fuelgauge.batteryusage;
import android.content.Context;
import android.os.BatteryUsageStats;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
/**
@@ -36,9 +32,6 @@ import com.android.settings.fuelgauge.BatteryUtils;
public class BatteryHistoryPreference extends Preference {
private static final String TAG = "BatteryHistoryPreference";
@VisibleForTesting
BatteryInfo mBatteryInfo;
private BatteryChartView mDailyChartView;
private BatteryChartView mHourlyChartView;
private BatteryChartPreferenceController mChartPreferenceController;
@@ -49,13 +42,6 @@ public class BatteryHistoryPreference extends Preference {
setSelectable(false);
}
void setBatteryUsageStats(@NonNull BatteryUsageStats batteryUsageStats) {
BatteryInfo.getBatteryInfo(getContext(), info -> {
mBatteryInfo = info;
notifyChanged();
}, batteryUsageStats, false);
}
void setChartPreferenceController(BatteryChartPreferenceController controller) {
mChartPreferenceController = controller;
if (mDailyChartView != null && mHourlyChartView != null) {
@@ -67,9 +53,6 @@ public class BatteryHistoryPreference extends Preference {
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
final long startTime = System.currentTimeMillis();
if (mBatteryInfo == null) {
return;
}
final TextView companionTextView = (TextView) view.findViewById(R.id.companion_text);
mDailyChartView = (BatteryChartView) view.findViewById(R.id.daily_battery_chart);
mDailyChartView.setCompanionTextView(companionTextView);

View File

@@ -16,15 +16,28 @@
package com.android.settings.fuelgauge.batteryusage;
import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.util.Preconditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/** Wraps the battery timestamp and level data used for battery usage chart. */
public final class BatteryLevelData {
private static final long MIN_SIZE = 2;
private static final long TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2;
/** A container for the battery timestamp and level data. */
public static final class PeriodBatteryLevelData {
// The length of mTimestamps and mLevels must be the same. mLevels[index] might be null when
@@ -33,12 +46,14 @@ public final class BatteryLevelData {
private final List<Integer> mLevels;
public PeriodBatteryLevelData(
@NonNull List<Long> timestamps, @NonNull List<Integer> levels) {
Preconditions.checkArgument(timestamps.size() == levels.size(),
/* errorMessage= */ "Timestamp: " + timestamps.size() + ", Level: "
+ levels.size());
@NonNull Map<Long, Integer> batteryLevelMap,
@NonNull List<Long> timestamps) {
mTimestamps = timestamps;
mLevels = levels;
mLevels = new ArrayList<>(timestamps.size());
for (Long timestamp : timestamps) {
mLevels.add(batteryLevelMap.containsKey(timestamp)
? batteryLevelMap.get(timestamp) : BATTERY_LEVEL_UNKNOWN);
}
}
public List<Long> getTimestamps() {
@@ -68,15 +83,21 @@ public final class BatteryLevelData {
// The size of hourly data must be the size of daily data - 1.
private final List<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
public BatteryLevelData(
@NonNull PeriodBatteryLevelData dailyBatteryLevels,
@NonNull List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) {
final long dailySize = dailyBatteryLevels.getTimestamps().size();
final long hourlySize = hourlyBatteryLevelsPerDay.size();
Preconditions.checkArgument(hourlySize == dailySize - 1,
/* errorMessage= */ "DailySize: " + dailySize + ", HourlySize: " + hourlySize);
mDailyBatteryLevels = dailyBatteryLevels;
mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
public BatteryLevelData(@NonNull Map<Long, Integer> batteryLevelMap) {
final int mapSize = batteryLevelMap.size();
Preconditions.checkArgument(mapSize >= MIN_SIZE, "batteryLevelMap size:" + mapSize);
final List<Long> timestampList = new ArrayList<>(batteryLevelMap.keySet());
Collections.sort(timestampList);
final List<Long> dailyTimestamps = getDailyTimestamps(timestampList);
final List<List<Long>> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps);
mDailyBatteryLevels = new PeriodBatteryLevelData(batteryLevelMap, dailyTimestamps);
mHourlyBatteryLevelsPerDay = new ArrayList<>(hourlyTimestamps.size());
for (List<Long> hourlyTimestampsPerDay : hourlyTimestamps) {
mHourlyBatteryLevelsPerDay.add(
new PeriodBatteryLevelData(batteryLevelMap, hourlyTimestampsPerDay));
}
}
public PeriodBatteryLevelData getDailyBatteryLevels() {
@@ -94,5 +115,69 @@ public final class BatteryLevelData {
Objects.toString(mDailyBatteryLevels),
Objects.toString(mHourlyBatteryLevelsPerDay));
}
@Nullable
static BatteryLevelData combine(@Nullable BatteryLevelData existingBatteryLevelData,
List<BatteryEvent> batteryLevelRecordEvents) {
final Map<Long, Integer> batteryLevelMap = new ArrayMap<>(batteryLevelRecordEvents.size());
for (BatteryEvent event : batteryLevelRecordEvents) {
batteryLevelMap.put(event.getTimestamp(), event.getBatteryLevel());
}
if (existingBatteryLevelData != null) {
List<PeriodBatteryLevelData> multiDaysData =
existingBatteryLevelData.getHourlyBatteryLevelsPerDay();
for (int dayIndex = 0; dayIndex < multiDaysData.size(); dayIndex++) {
PeriodBatteryLevelData oneDayData = multiDaysData.get(dayIndex);
for (int hourIndex = 0; hourIndex < oneDayData.getLevels().size(); hourIndex++) {
batteryLevelMap.put(oneDayData.getTimestamps().get(hourIndex),
oneDayData.getLevels().get(hourIndex));
}
}
}
return batteryLevelMap.size() < MIN_SIZE ? null : new BatteryLevelData(batteryLevelMap);
}
/**
* Computes expected daily timestamp slots.
*
* The valid result should be composed of 3 parts:
* 1) start timestamp
* 2) every 00:00 timestamp (default timezone) between the start and end
* 3) end timestamp
* Otherwise, returns an empty list.
*/
@VisibleForTesting
static List<Long> getDailyTimestamps(final List<Long> timestampList) {
Preconditions.checkArgument(
timestampList.size() >= MIN_SIZE, "timestampList size:" + timestampList.size());
final List<Long> dailyTimestampList = new ArrayList<>();
final long startTimestamp = timestampList.get(0);
final long endTimestamp = timestampList.get(timestampList.size() - 1);
for (long timestamp = startTimestamp; timestamp < endTimestamp;
timestamp = TimestampUtils.getNextDayTimestamp(timestamp)) {
dailyTimestampList.add(timestamp);
}
dailyTimestampList.add(endTimestamp);
return dailyTimestampList;
}
private static List<List<Long>> getHourlyTimestamps(final List<Long> dailyTimestamps) {
final List<List<Long>> hourlyTimestamps = new ArrayList<>();
for (int dailyIndex = 0; dailyIndex < dailyTimestamps.size() - 1; dailyIndex++) {
final List<Long> hourlyTimestampsPerDay = new ArrayList<>();
final long startTime = dailyTimestamps.get(dailyIndex);
final long endTime = dailyTimestamps.get(dailyIndex + 1);
hourlyTimestampsPerDay.add(startTime);
for (long timestamp = TimestampUtils.getNextEvenHourTimestamp(startTime);
timestamp < endTime; timestamp += TIME_SLOT) {
hourlyTimestampsPerDay.add(timestamp);
}
hourlyTimestampsPerDay.add(endTime);
hourlyTimestamps.add(hourlyTimestampsPerDay);
}
return hourlyTimestamps;
}
}

View File

@@ -144,19 +144,17 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
}
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();
final String packageName = diffEntry.getPackageName();
mMetricsFeatureProvider.action(
/* attribution */ SettingsEnums.OPEN_BATTERY_USAGE,
/* action */ isAppEntry
? SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM
: SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM,
/* action */ diffEntry.isSystemEntry()
? SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM
: SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM,
/* pageId */ SettingsEnums.OPEN_BATTERY_USAGE,
TextUtils.isEmpty(packageName) ? PACKAGE_NAME_NONE : packageName,
(int) Math.round(diffEntry.getPercentage()));
Log.d(TAG, String.format("handleClick() label=%s key=%s package=%s",
diffEntry.getAppLabel(), histEntry.getKey(), histEntry.mPackageName));
diffEntry.getAppLabel(), diffEntry.getKey(), packageName));
AdvancedPowerUsageDetail.startBatteryDetailPage(
mActivity, mFragment, diffEntry, powerPref.getPercentage(), mSlotTimestamp);
return true;

View File

@@ -21,7 +21,6 @@ import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
@@ -36,12 +35,14 @@ import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao;
import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotDao;
import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/** {@link ContentProvider} class to fetch battery usage data. */
public class BatteryUsageContentProvider extends ContentProvider {
@@ -55,7 +56,12 @@ public class BatteryUsageContentProvider extends ContentProvider {
private static final int APP_USAGE_LATEST_TIMESTAMP_CODE = 2;
private static final int APP_USAGE_EVENT_CODE = 3;
private static final int BATTERY_EVENT_CODE = 4;
private static final int LAST_FULL_CHARGE_TIMESTAMP_CODE = 5;
private static final int BATTERY_STATE_LATEST_TIMESTAMP_CODE = 6;
private static final int BATTERY_USAGE_SLOT_CODE = 7;
private static final List<Integer> ALL_BATTERY_EVENT_TYPES =
Arrays.stream(BatteryEventType.values()).map(type -> type.getNumber()).toList();
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
@@ -75,12 +81,25 @@ public class BatteryUsageContentProvider extends ContentProvider {
DatabaseUtils.AUTHORITY,
/*path=*/ DatabaseUtils.BATTERY_EVENT_TABLE,
/*code=*/ BATTERY_EVENT_CODE);
sUriMatcher.addURI(
DatabaseUtils.AUTHORITY,
/*path=*/ DatabaseUtils.LAST_FULL_CHARGE_TIMESTAMP_PATH,
/*code=*/ LAST_FULL_CHARGE_TIMESTAMP_CODE);
sUriMatcher.addURI(
DatabaseUtils.AUTHORITY,
/*path=*/ DatabaseUtils.BATTERY_STATE_LATEST_TIMESTAMP_PATH,
/*code=*/ BATTERY_STATE_LATEST_TIMESTAMP_CODE);
sUriMatcher.addURI(
DatabaseUtils.AUTHORITY,
/*path=*/ DatabaseUtils.BATTERY_USAGE_SLOT_TABLE,
/*code=*/ BATTERY_USAGE_SLOT_CODE);
}
private Clock mClock;
private BatteryStateDao mBatteryStateDao;
private AppUsageEventDao mAppUsageEventDao;
private BatteryEventDao mBatteryEventDao;
private BatteryUsageSlotDao mBatteryUsageSlotDao;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public void setClock(Clock clock) {
@@ -94,9 +113,11 @@ public class BatteryUsageContentProvider extends ContentProvider {
return false;
}
mClock = Clock.systemUTC();
mBatteryStateDao = BatteryStateDatabase.getInstance(getContext()).batteryStateDao();
mAppUsageEventDao = BatteryStateDatabase.getInstance(getContext()).appUsageEventDao();
mBatteryEventDao = BatteryStateDatabase.getInstance(getContext()).batteryEventDao();
final BatteryStateDatabase database = BatteryStateDatabase.getInstance(getContext());
mBatteryStateDao = database.batteryStateDao();
mAppUsageEventDao = database.appUsageEventDao();
mBatteryEventDao = database.batteryEventDao();
mBatteryUsageSlotDao = database.batteryUsageSlotDao();
Log.w(TAG, "create content provider from " + getCallingPackage());
return true;
}
@@ -118,6 +139,12 @@ public class BatteryUsageContentProvider extends ContentProvider {
return getAppUsageLatestTimestamp(uri);
case BATTERY_EVENT_CODE:
return getBatteryEvents(uri);
case LAST_FULL_CHARGE_TIMESTAMP_CODE:
return getLastFullChargeTimestamp(uri);
case BATTERY_STATE_LATEST_TIMESTAMP_CODE:
return getBatteryStateLatestTimestamp(uri);
case BATTERY_USAGE_SLOT_CODE:
return getBatteryUsageSlots(uri);
default:
throw new IllegalArgumentException("unknown URI: " + uri);
}
@@ -132,34 +159,31 @@ public class BatteryUsageContentProvider extends ContentProvider {
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
switch (sUriMatcher.match(uri)) {
case BATTERY_STATE_CODE:
try {
try {
switch (sUriMatcher.match(uri)) {
case BATTERY_STATE_CODE:
mBatteryStateDao.insert(BatteryState.create(contentValues));
return uri;
} catch (RuntimeException e) {
Log.e(TAG, "insert() from:" + uri + " error:" + e);
return null;
}
case APP_USAGE_EVENT_CODE:
try {
break;
case APP_USAGE_EVENT_CODE:
mAppUsageEventDao.insert(AppUsageEventEntity.create(contentValues));
return uri;
} catch (RuntimeException e) {
Log.e(TAG, "insert() from:" + uri + " error:" + e);
return null;
}
case BATTERY_EVENT_CODE:
try {
break;
case BATTERY_EVENT_CODE:
mBatteryEventDao.insert(BatteryEventEntity.create(contentValues));
return uri;
} catch (RuntimeException e) {
Log.e(TAG, "insert() from:" + uri + " error:" + e);
return null;
}
default:
throw new IllegalArgumentException("unknown URI: " + uri);
break;
case BATTERY_USAGE_SLOT_CODE:
mBatteryUsageSlotDao.insert(BatteryUsageSlotEntity.create(contentValues));
break;
default:
throw new IllegalArgumentException("unknown URI: " + uri);
}
} catch (RuntimeException e) {
if (e instanceof IllegalArgumentException) {
throw e;
}
Log.e(TAG, "insert() from:" + uri + " error:", e);
return null;
}
return uri;
}
@Override
@@ -176,21 +200,44 @@ public class BatteryUsageContentProvider extends ContentProvider {
throw new UnsupportedOperationException("unsupported!");
}
private Cursor getBatteryStates(Uri uri) {
final long queryTimestamp = getQueryTimestamp(uri);
return getBatteryStates(uri, queryTimestamp);
}
private Cursor getBatteryStates(Uri uri, long firstTimestamp) {
private Cursor getLastFullChargeTimestamp(Uri uri) {
final long timestamp = mClock.millis();
Cursor cursor = null;
try {
cursor = mBatteryStateDao.getCursorSinceLastFullCharge(firstTimestamp);
cursor = mBatteryEventDao.getLastFullChargeTimestamp();
} catch (RuntimeException e) {
Log.e(TAG, "query() from:" + uri + " error:" + e);
Log.e(TAG, "query() from:" + uri + " error:", e);
}
AsyncTask.execute(() -> BootBroadcastReceiver.invokeJobRecheck(getContext()));
Log.d(TAG, "query battery states in " + (mClock.millis() - timestamp) + "/ms");
Log.d(TAG, String.format("getLastFullChargeTimestamp() in %d/ms",
mClock.millis() - timestamp));
return cursor;
}
private Cursor getBatteryStateLatestTimestamp(Uri uri) {
final long queryTimestamp = getQueryTimestamp(uri);
final long timestamp = mClock.millis();
Cursor cursor = null;
try {
cursor = mBatteryStateDao.getLatestTimestampBefore(queryTimestamp);
} catch (RuntimeException e) {
Log.e(TAG, "query() from:" + uri + " error:", e);
}
Log.d(TAG, String.format("getBatteryStateLatestTimestamp() no later than %d in %d/ms",
queryTimestamp, mClock.millis() - timestamp));
return cursor;
}
private Cursor getBatteryStates(Uri uri) {
final long queryTimestamp = getQueryTimestamp(uri);
final long timestamp = mClock.millis();
Cursor cursor = null;
try {
cursor = mBatteryStateDao.getBatteryStatesAfter(queryTimestamp);
} catch (RuntimeException e) {
Log.e(TAG, "query() from:" + uri + " error:", e);
}
Log.d(TAG, String.format("getBatteryStates() after %d in %d/ms",
queryTimestamp, mClock.millis() - timestamp));
return cursor;
}
@@ -205,9 +252,9 @@ public class BatteryUsageContentProvider extends ContentProvider {
try {
cursor = mAppUsageEventDao.getAllForUsersAfter(queryUserIds, queryTimestamp);
} catch (RuntimeException e) {
Log.e(TAG, "query() from:" + uri + " error:" + e);
Log.e(TAG, "query() from:" + uri + " error:", e);
}
Log.w(TAG, "query app usage events in " + (mClock.millis() - timestamp) + "/ms");
Log.w(TAG, "getAppUsageEvents() in " + (mClock.millis() - timestamp) + "/ms");
return cursor;
}
@@ -221,42 +268,78 @@ public class BatteryUsageContentProvider extends ContentProvider {
try {
cursor = mAppUsageEventDao.getLatestTimestampOfUser(queryUserId);
} catch (RuntimeException e) {
Log.e(TAG, "query() from:" + uri + " error:" + e);
Log.e(TAG, "query() from:" + uri + " error:", e);
}
Log.d(TAG, String.format("query app usage latest timestamp %d for user %d in %d/ms",
timestamp, queryUserId, (mClock.millis() - timestamp)));
Log.d(TAG, String.format("getAppUsageLatestTimestamp() for user %d in %d/ms",
queryUserId, (mClock.millis() - timestamp)));
return cursor;
}
private Cursor getBatteryEvents(Uri uri) {
List<Integer> queryBatteryEventTypes = getQueryBatteryEventTypes(uri);
if (queryBatteryEventTypes == null || queryBatteryEventTypes.isEmpty()) {
queryBatteryEventTypes = ALL_BATTERY_EVENT_TYPES;
}
final long queryTimestamp = getQueryTimestamp(uri);
final long timestamp = mClock.millis();
Cursor cursor = null;
try {
cursor = mBatteryEventDao.getAllAfter(queryTimestamp);
cursor = mBatteryEventDao.getAllAfter(queryTimestamp, queryBatteryEventTypes);
} catch (RuntimeException e) {
Log.e(TAG, "query() from:" + uri + " error:" + e);
Log.e(TAG, "query() from:" + uri + " error:", e);
}
Log.w(TAG, "query app usage events in " + (mClock.millis() - timestamp) + "/ms");
Log.w(TAG, "getBatteryEvents() in " + (mClock.millis() - timestamp) + "/ms");
return cursor;
}
private Cursor getBatteryUsageSlots(Uri uri) {
final long queryTimestamp = getQueryTimestamp(uri);
final long timestamp = mClock.millis();
Cursor cursor = null;
try {
cursor = mBatteryUsageSlotDao.getAllAfter(queryTimestamp);
} catch (RuntimeException e) {
Log.e(TAG, "query() from:" + uri + " error:", e);
}
Log.w(TAG, "getBatteryUsageSlots() in " + (mClock.millis() - timestamp) + "/ms");
return cursor;
}
private List<Integer> getQueryBatteryEventTypes(Uri uri) {
Log.d(TAG, "getQueryBatteryEventTypes from uri: " + uri);
final String batteryEventTypesParameter =
uri.getQueryParameter(DatabaseUtils.QUERY_BATTERY_EVENT_TYPE);
if (TextUtils.isEmpty(batteryEventTypesParameter)) {
return null;
}
try {
List<Integer> batteryEventTypes = new ArrayList<>();
for (String typeString : batteryEventTypesParameter.split(",")) {
batteryEventTypes.add(Integer.parseInt(typeString.trim()));
}
return batteryEventTypes;
} catch (NumberFormatException e) {
Log.e(TAG, "invalid query value: " + batteryEventTypesParameter, e);
return null;
}
}
// If URI contains query parameter QUERY_KEY_USERID, use the value directly.
// Otherwise, return null.
private List<Long> getQueryUserIds(Uri uri) {
Log.d(TAG, "getQueryUserIds from uri: " + uri);
final String value = uri.getQueryParameter(DatabaseUtils.QUERY_KEY_USERID);
if (TextUtils.isEmpty(value)) {
Log.w(TAG, "empty query value");
final String userIdsParameter = uri.getQueryParameter(DatabaseUtils.QUERY_KEY_USERID);
if (TextUtils.isEmpty(userIdsParameter)) {
return null;
}
try {
return Arrays.asList(value.split(","))
.stream()
.map(s -> Long.parseLong(s.trim()))
.collect(Collectors.toList());
List<Long> userIds = new ArrayList<>();
for (String idString : userIdsParameter.split(",")) {
userIds.add(Long.parseLong(idString.trim()));
}
return userIds;
} catch (NumberFormatException e) {
Log.e(TAG, "invalid query value: " + value, e);
Log.e(TAG, "invalid query value: " + userIdsParameter, e);
return null;
}
}

View File

@@ -16,9 +16,12 @@
package com.android.settings.fuelgauge.batteryusage;
import android.app.usage.UsageEvents;
import android.content.Context;
import android.os.AsyncTask;
import android.os.BatteryUsageStats;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -27,6 +30,7 @@ import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
/** Load battery usage data in the background. */
@@ -36,6 +40,10 @@ public final class BatteryUsageDataLoader {
// For testing only.
@VisibleForTesting
static Supplier<List<BatteryEntry>> sFakeBatteryEntryListSupplier;
@VisibleForTesting
static Supplier<Map<Long, UsageEvents>> sFakeAppUsageEventsSupplier;
@VisibleForTesting
static Supplier<List<AppUsageEvent>> sFakeUsageEventsListSupplier;
private BatteryUsageDataLoader() {
}
@@ -48,9 +56,9 @@ public final class BatteryUsageDataLoader {
}
@VisibleForTesting
static void loadUsageData(final Context context, final boolean isFullChargeStart) {
static void loadBatteryStatsData(final Context context, final boolean isFullChargeStart) {
BatteryUsageLogUtils.writeLog(context, Action.FETCH_USAGE_DATA, "");
final long start = System.currentTimeMillis();
final long currentTime = System.currentTimeMillis();
final BatteryUsageStats batteryUsageStats = DataProcessor.getBatteryUsageStats(context);
final List<BatteryEntry> batteryEntryList =
sFakeBatteryEntryListSupplier != null ? sFakeBatteryEntryListSupplier.get()
@@ -59,25 +67,81 @@ public final class BatteryUsageDataLoader {
if (batteryEntryList == null || batteryEntryList.isEmpty()) {
Log.w(TAG, "getBatteryEntryList() returns null or empty content");
}
final long elapsedTime = System.currentTimeMillis() - start;
final long elapsedTime = System.currentTimeMillis() - currentTime;
Log.d(TAG, String.format("getBatteryUsageStats() in %d/ms", elapsedTime));
if (isFullChargeStart) {
DatabaseUtils.recordDateTime(
context, DatabaseUtils.KEY_LAST_LOAD_FULL_CHARGE_TIME);
DatabaseUtils.sendBatteryEventData(context, ConvertUtils.convertToBatteryEvent(
currentTime, BatteryEventType.FULL_CHARGED, 100));
}
// Uploads the BatteryEntry data into database.
DatabaseUtils.sendBatteryEntryData(
context, batteryEntryList, batteryUsageStats, isFullChargeStart);
context, currentTime, batteryEntryList, batteryUsageStats, isFullChargeStart);
DataProcessor.closeBatteryUsageStats(batteryUsageStats);
}
@VisibleForTesting
static void loadAppUsageData(final Context context) {
final long start = System.currentTimeMillis();
final Map<Long, UsageEvents> appUsageEvents =
sFakeAppUsageEventsSupplier != null
? sFakeAppUsageEventsSupplier.get()
: DataProcessor.getAppUsageEvents(context);
if (appUsageEvents == null) {
Log.w(TAG, "loadAppUsageData() returns null");
return;
}
final List<AppUsageEvent> appUsageEventList =
sFakeUsageEventsListSupplier != null
? sFakeUsageEventsListSupplier.get()
: DataProcessor.generateAppUsageEventListFromUsageEvents(
context, appUsageEvents);
if (appUsageEventList == null || appUsageEventList.isEmpty()) {
Log.w(TAG, "loadAppUsageData() returns null or empty content");
return;
}
final long elapsedTime = System.currentTimeMillis() - start;
Log.d(TAG, String.format("loadAppUsageData() size=%d in %d/ms", appUsageEventList.size(),
elapsedTime));
// Uploads the AppUsageEvent data into database.
DatabaseUtils.sendAppUsageEventData(context, appUsageEventList);
}
private static void preprocessBatteryUsageSlots(final Context context) {
final long start = System.currentTimeMillis();
final Handler handler = new Handler(Looper.getMainLooper());
final BatteryLevelData batteryLevelData = DataProcessManager.getBatteryLevelData(
context, handler, /*isFromPeriodJob=*/ true,
batteryDiffDataMap -> DatabaseUtils.sendBatteryUsageSlotData(context,
ConvertUtils.convertToBatteryUsageSlotList(batteryDiffDataMap)));
if (batteryLevelData == null) {
Log.d(TAG, "preprocessBatteryUsageSlots() no new battery usage data.");
return;
}
DatabaseUtils.sendBatteryEventData(
context, ConvertUtils.convertToBatteryEventList(batteryLevelData));
Log.d(TAG, String.format(
"preprocessBatteryUsageSlots() batteryLevelData=%s in %d/ms",
batteryLevelData, System.currentTimeMillis() - start));
}
private static void loadUsageDataSafely(
final Context context, final boolean isFullChargeStart) {
try {
loadUsageData(context, isFullChargeStart);
final long start = System.currentTimeMillis();
loadBatteryStatsData(context, isFullChargeStart);
if (!isFullChargeStart) {
// No app usage data or battery diff data at this time.
loadAppUsageData(context);
preprocessBatteryUsageSlots(context);
}
Log.d(TAG, String.format(
"loadUsageDataSafely() in %d/ms", System.currentTimeMillis() - start));
} catch (RuntimeException e) {
Log.e(TAG, "loadUsageData:" + e);
Log.e(TAG, "loadUsageData:", e);
}
}
}

View File

@@ -33,15 +33,21 @@ import android.text.format.DateFormat;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
/** A utility class to convert data into another types. */
@@ -75,7 +81,22 @@ public final class ConvertUtils {
private ConvertUtils() {
}
/** Converts {@link BatteryEntry} to content values */
/** Whether {@code consumerType} is app consumer or not. */
public static boolean isUidConsumer(final int consumerType) {
return consumerType == CONSUMER_TYPE_UID_BATTERY;
}
/** Whether {@code consumerType} is user consumer or not. */
public static boolean isUserConsumer(final int consumerType) {
return consumerType == CONSUMER_TYPE_USER_BATTERY;
}
/** Whether {@code consumerType} is system consumer or not. */
public static boolean isSystemConsumer(final int consumerType) {
return consumerType == CONSUMER_TYPE_SYSTEM_BATTERY;
}
/** Converts {@link BatteryEntry} to {@link ContentValues} */
public static ContentValues convertBatteryEntryToContentValues(
final BatteryEntry entry,
final BatteryUsageStats batteryUsageStats,
@@ -118,7 +139,7 @@ public final class ConvertUtils {
return values;
}
/** Converts {@link AppUsageEvent} to content values */
/** Converts {@link AppUsageEvent} to {@link ContentValues} */
public static ContentValues convertAppUsageEventToContentValues(final AppUsageEvent event) {
final ContentValues values = new ContentValues();
values.put(AppUsageEventEntity.KEY_UID, event.getUid());
@@ -131,7 +152,7 @@ public final class ConvertUtils {
return values;
}
/** Converts {@link BatteryEvent} to content values */
/** Converts {@link BatteryEvent} to {@link ContentValues} */
public static ContentValues convertBatteryEventToContentValues(final BatteryEvent event) {
final ContentValues values = new ContentValues();
values.put(BatteryEventEntity.KEY_TIMESTAMP, event.getTimestamp());
@@ -140,6 +161,16 @@ public final class ConvertUtils {
return values;
}
/** Converts {@link BatteryUsageSlot} to {@link ContentValues} */
public static ContentValues convertBatteryUsageSlotToContentValues(
final BatteryUsageSlot batteryUsageSlot) {
final ContentValues values = new ContentValues(2);
values.put(BatteryUsageSlotEntity.KEY_TIMESTAMP, batteryUsageSlot.getStartTimestamp());
values.put(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT,
Base64.encodeToString(batteryUsageSlot.toByteArray(), Base64.DEFAULT));
return values;
}
/** Gets the encoded string from {@link BatteryInformation} instance. */
public static String convertBatteryInformationToString(
final BatteryInformation batteryInformation) {
@@ -183,7 +214,7 @@ public final class ConvertUtils {
/*isFullChargeStart=*/ false));
}
/** Converts to {@link AppUsageEvent} from {@link Event} */
/** Converts from {@link Event} to {@link AppUsageEvent} */
@Nullable
public static AppUsageEvent convertToAppUsageEvent(
Context context, IUsageStatsManager usageStatsManager, final Event event,
@@ -234,8 +265,8 @@ public final class ConvertUtils {
return appUsageEventBuilder.build();
}
/** Converts to {@link AppUsageEvent} from {@link Cursor} */
public static AppUsageEvent convertToAppUsageEventFromCursor(final Cursor cursor) {
/** Converts from {@link Cursor} to {@link AppUsageEvent} */
public static AppUsageEvent convertToAppUsageEvent(final Cursor cursor) {
final AppUsageEvent.Builder eventBuilder = AppUsageEvent.newBuilder();
eventBuilder.setTimestamp(getLongFromCursor(cursor, AppUsageEventEntity.KEY_TIMESTAMP));
eventBuilder.setType(
@@ -253,7 +284,7 @@ public final class ConvertUtils {
return eventBuilder.build();
}
/** Converts to {@link BatteryEvent} from {@link BatteryEventType} */
/** Converts from {@link BatteryEventType} to {@link BatteryEvent} */
public static BatteryEvent convertToBatteryEvent(
long timestamp, BatteryEventType type, int batteryLevel) {
final BatteryEvent.Builder eventBuilder = BatteryEvent.newBuilder();
@@ -263,8 +294,8 @@ public final class ConvertUtils {
return eventBuilder.build();
}
/** Converts to {@link BatteryEvent} from {@link Cursor} */
public static BatteryEvent convertToBatteryEventFromCursor(final Cursor cursor) {
/** Converts from {@link Cursor} to {@link BatteryEvent} */
public static BatteryEvent convertToBatteryEvent(final Cursor cursor) {
final BatteryEvent.Builder eventBuilder = BatteryEvent.newBuilder();
eventBuilder.setTimestamp(getLongFromCursor(cursor, BatteryEventEntity.KEY_TIMESTAMP));
eventBuilder.setType(
@@ -276,6 +307,42 @@ public final class ConvertUtils {
return eventBuilder.build();
}
/** Converts from {@link BatteryLevelData} to {@link List<BatteryEvent>} */
public static List<BatteryEvent> convertToBatteryEventList(
final BatteryLevelData batteryLevelData) {
final List<BatteryEvent> batteryEventList = new ArrayList<>();
final List<BatteryLevelData.PeriodBatteryLevelData> levelDataList =
batteryLevelData.getHourlyBatteryLevelsPerDay();
for (BatteryLevelData.PeriodBatteryLevelData oneDayData : levelDataList) {
for (int hourIndex = 0; hourIndex < oneDayData.getLevels().size() - 1; hourIndex++) {
batteryEventList.add(convertToBatteryEvent(
oneDayData.getTimestamps().get(hourIndex),
BatteryEventType.EVEN_HOUR,
oneDayData.getLevels().get(hourIndex)));
}
}
return batteryEventList;
}
/** Converts from {@link Cursor} to {@link BatteryUsageSlot} */
public static BatteryUsageSlot convertToBatteryUsageSlot(final Cursor cursor) {
final BatteryUsageSlot defaultInstance = BatteryUsageSlot.getDefaultInstance();
final int columnIndex =
cursor.getColumnIndex(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT);
return columnIndex < 0 ? defaultInstance : BatteryUtils.parseProtoFromString(
cursor.getString(columnIndex), defaultInstance);
}
/** Converts from {@link Map<Long, BatteryDiffData>} to {@link List<BatteryUsageSlot>} */
public static List<BatteryUsageSlot> convertToBatteryUsageSlotList(
final Map<Long, BatteryDiffData> batteryDiffDataMap) {
List<BatteryUsageSlot> batteryUsageSlotList = new ArrayList<>();
for (BatteryDiffData batteryDiffData : batteryDiffDataMap.values()) {
batteryUsageSlotList.add(convertToBatteryUsageSlot(batteryDiffData));
}
return batteryUsageSlotList;
}
/** Converts UTC timestamp to local time string for logging only, so use the US locale for
* better readability in debugging. */
public static String utcToLocalTimeForLogging(long timestamp) {
@@ -396,6 +463,100 @@ public final class ConvertUtils {
}
}
private static BatteryUsageDiff convertToBatteryUsageDiff(BatteryDiffEntry batteryDiffEntry) {
BatteryUsageDiff.Builder builder = BatteryUsageDiff.newBuilder()
.setUid(batteryDiffEntry.mUid)
.setUserId(batteryDiffEntry.mUserId)
.setIsHidden(batteryDiffEntry.mIsHidden)
.setComponentId(batteryDiffEntry.mComponentId)
.setConsumerType(batteryDiffEntry.mConsumerType)
.setConsumePower(batteryDiffEntry.mConsumePower)
.setForegroundUsageConsumePower(batteryDiffEntry.mForegroundUsageConsumePower)
.setBackgroundUsageConsumePower(batteryDiffEntry.mBackgroundUsageConsumePower)
.setForegroundUsageTime(batteryDiffEntry.mForegroundUsageTimeInMs)
.setBackgroundUsageTime(batteryDiffEntry.mBackgroundUsageTimeInMs)
.setScreenOnTime(batteryDiffEntry.mScreenOnTimeInMs);
if (batteryDiffEntry.mKey != null) {
builder.setKey(batteryDiffEntry.mKey);
}
if (batteryDiffEntry.mLegacyPackageName != null) {
builder.setPackageName(batteryDiffEntry.mLegacyPackageName);
}
if (batteryDiffEntry.mLegacyLabel != null) {
builder.setLabel(batteryDiffEntry.mLegacyLabel);
}
return builder.build();
}
private static BatteryUsageSlot convertToBatteryUsageSlot(
final BatteryDiffData batteryDiffData) {
if (batteryDiffData == null) {
return BatteryUsageSlot.getDefaultInstance();
}
final BatteryUsageSlot.Builder builder = BatteryUsageSlot.newBuilder()
.setStartTimestamp(batteryDiffData.getStartTimestamp())
.setEndTimestamp(batteryDiffData.getEndTimestamp())
.setStartBatteryLevel(batteryDiffData.getStartBatteryLevel())
.setEndBatteryLevel(batteryDiffData.getEndBatteryLevel())
.setScreenOnTime(batteryDiffData.getScreenOnTime());
for (BatteryDiffEntry batteryDiffEntry : batteryDiffData.getAppDiffEntryList()) {
builder.addAppUsage(convertToBatteryUsageDiff(batteryDiffEntry));
}
for (BatteryDiffEntry batteryDiffEntry : batteryDiffData.getSystemDiffEntryList()) {
builder.addSystemUsage(convertToBatteryUsageDiff(batteryDiffEntry));
}
return builder.build();
}
private static BatteryDiffEntry convertToBatteryDiffEntry(
Context context, final BatteryUsageDiff batteryUsageDiff) {
return new BatteryDiffEntry(
context,
batteryUsageDiff.getUid(),
batteryUsageDiff.getUserId(),
batteryUsageDiff.getKey(),
batteryUsageDiff.getIsHidden(),
batteryUsageDiff.getComponentId(),
batteryUsageDiff.getPackageName(),
batteryUsageDiff.getLabel(),
batteryUsageDiff.getConsumerType(),
batteryUsageDiff.getForegroundUsageTime(),
batteryUsageDiff.getBackgroundUsageTime(),
batteryUsageDiff.getScreenOnTime(),
batteryUsageDiff.getConsumePower(),
batteryUsageDiff.getForegroundUsageConsumePower(),
/*foregroundServiceUsageConsumePower=*/ 0,
batteryUsageDiff.getBackgroundUsageConsumePower(),
/*cachedUsageConsumePower=*/ 0);
}
static BatteryDiffData convertToBatteryDiffData(
Context context,
final BatteryUsageSlot batteryUsageSlot,
@NonNull final Set<String> systemAppsPackageNames,
@NonNull final Set<Integer> systemAppsUids) {
final List<BatteryDiffEntry> appDiffEntries = new ArrayList<>();
final List<BatteryDiffEntry> systemDiffEntries = new ArrayList<>();
for (BatteryUsageDiff batteryUsageDiff : batteryUsageSlot.getAppUsageList()) {
appDiffEntries.add(convertToBatteryDiffEntry(context, batteryUsageDiff));
}
for (BatteryUsageDiff batteryUsageDiff : batteryUsageSlot.getSystemUsageList()) {
systemDiffEntries.add(convertToBatteryDiffEntry(context, batteryUsageDiff));
}
return new BatteryDiffData(
context,
batteryUsageSlot.getStartTimestamp(),
batteryUsageSlot.getEndTimestamp(),
batteryUsageSlot.getStartBatteryLevel(),
batteryUsageSlot.getEndBatteryLevel(),
batteryUsageSlot.getScreenOnTime(),
appDiffEntries,
systemDiffEntries,
systemAppsPackageNames,
systemAppsUids,
/*isAccumulated=*/ false);
}
private static BatteryInformation constructBatteryInformation(
final BatteryEntry entry,
final BatteryUsageStats batteryUsageStats,

View File

@@ -23,6 +23,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -33,10 +34,10 @@ import com.android.settings.Utils;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Manages the async tasks to process battery and app usage data.
@@ -69,28 +70,37 @@ import java.util.Map;
*/
public class DataProcessManager {
private static final String TAG = "DataProcessManager";
private static final List<BatteryEventType> POWER_CONNECTION_EVENTS =
List.of(BatteryEventType.POWER_CONNECTED, BatteryEventType.POWER_DISCONNECTED);
private static final List<BatteryEventType> BATTERY_LEVEL_RECORD_EVENTS =
List.of(BatteryEventType.FULL_CHARGED, BatteryEventType.EVEN_HOUR);
private final Handler mHandler;
private final DataProcessor.UsageMapAsyncResponse mCallbackFunction;
private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
private final List<BatteryEvent> mBatteryEventList = new ArrayList<>();
private Context mContext;
private UserManager mUserManager;
private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
// For testing only.
@VisibleForTesting
static Map<Long, Map<String, BatteryHistEntry>> sFakeBatteryHistoryMap;
// Raw start timestamp with round to the nearest hour.
private long mRawStartTimestamp;
private final long mRawStartTimestamp;
private final long mLastFullChargeTimestamp;
private final Context mContext;
private final Handler mHandler;
private final UserManager mUserManager;
private final OnBatteryDiffDataMapLoadedListener mCallbackFunction;
private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
private final List<BatteryEvent> mBatteryEventList = new ArrayList<>();
private final List<BatteryUsageSlot> mBatteryUsageSlotList = new ArrayList<>();
private final List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
private final Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
private boolean mIsCurrentBatteryHistoryLoaded = false;
private boolean mIsCurrentAppUsageLoaded = false;
private boolean mIsDatabaseAppUsageLoaded = false;
private boolean mIsBatteryEventLoaded = false;
private boolean mIsBatteryUsageSlotLoaded = false;
// Used to identify whether screen-on time data should be shown in the UI.
private boolean mShowScreenOnTime = true;
// Used to identify whether battery level data should be shown in the UI.
private boolean mShowBatteryLevel = true;
private Set<String> mSystemAppsPackageNames = null;
private Set<Integer> mSystemAppsUids = null;
/**
* The indexed {@link AppUsagePeriod} list data for each corresponding time slot.
@@ -100,6 +110,15 @@ public class DataProcessManager {
private Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
mAppUsagePeriodMap;
/**
* A callback listener when all the data is processed.
* This happens when all the async tasks complete and generate the final callback.
*/
public interface OnBatteryDiffDataMapLoadedListener {
/** The callback function when all the data is processed. */
void onBatteryDiffDataMapLoaded(Map<Long, BatteryDiffData> batteryDiffDataMap);
}
/**
* Constructor when there exists battery level data.
*/
@@ -107,16 +126,18 @@ public class DataProcessManager {
Context context,
Handler handler,
final long rawStartTimestamp,
@NonNull final DataProcessor.UsageMapAsyncResponse callbackFunction,
final long lastFullChargeTimestamp,
@NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction,
@NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
@NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
mContext = context.getApplicationContext();
mHandler = handler;
mUserManager = mContext.getSystemService(UserManager.class);
mRawStartTimestamp = rawStartTimestamp;
mLastFullChargeTimestamp = lastFullChargeTimestamp;
mCallbackFunction = callbackFunction;
mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
mBatteryHistoryMap = batteryHistoryMap;
mRawStartTimestamp = rawStartTimestamp;
}
/**
@@ -125,31 +146,49 @@ public class DataProcessManager {
DataProcessManager(
Context context,
Handler handler,
@NonNull final DataProcessor.UsageMapAsyncResponse callbackFunction) {
@NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction) {
mContext = context.getApplicationContext();
mHandler = handler;
mUserManager = mContext.getSystemService(UserManager.class);
mCallbackFunction = callbackFunction;
mRawStartTimestamp = 0L;
mLastFullChargeTimestamp = 0L;
mHourlyBatteryLevelsPerDay = null;
mBatteryHistoryMap = null;
// When there is no battery level data, don't show screen-on time and battery level chart on
// the UI.
mShowScreenOnTime = false;
mShowBatteryLevel = false;
}
/**
* Starts the async tasks to load battery history data and app usage data.
*/
public void start() {
start(/*isFromPeriodJob=*/ false);
}
/**
* Starts the async tasks to load battery history data and app usage data.
*/
public void start(boolean isFromPeriodJob) {
// If we have battery level data, load the battery history map and app usage simultaneously.
if (mShowBatteryLevel) {
// Loads the latest battery history data from the service.
loadCurrentBatteryHistoryMap();
if (mHourlyBatteryLevelsPerDay != null) {
if (isFromPeriodJob) {
mIsCurrentBatteryHistoryLoaded = true;
mIsCurrentAppUsageLoaded = true;
mIsBatteryUsageSlotLoaded = true;
} else {
// Loads the latest battery history data from the service.
loadCurrentBatteryHistoryMap();
// Loads the latest app usage list from the service.
loadCurrentAppUsageList();
// Loads existing battery usage slots from database.
loadBatteryUsageSlotList();
}
// Loads app usage list from database.
loadDatabaseAppUsageList();
// Loads the latest app usage list from the service.
loadCurrentAppUsageList();
// Loads the battery event list from database.
loadBatteryEventList();
loadPowerConnectionBatteryEventList();
} else {
// If there is no battery level data, only load the battery history data from service
// and show it as the app list directly.
@@ -193,11 +232,6 @@ public class DataProcessManager {
return mShowScreenOnTime;
}
@VisibleForTesting
boolean getShowBatteryLevel() {
return mShowBatteryLevel;
}
private void loadCurrentBatteryHistoryMap() {
new AsyncTask<Void, Void, Map<String, BatteryHistEntry>>() {
@Override
@@ -323,7 +357,7 @@ public class DataProcessManager {
}.execute();
}
private void loadBatteryEventList() {
private void loadPowerConnectionBatteryEventList() {
new AsyncTask<Void, Void, List<BatteryEvent>>() {
@Override
protected List<BatteryEvent> doInBackground(Void... voids) {
@@ -331,8 +365,10 @@ public class DataProcessManager {
// Loads the battery event data from the database.
final List<BatteryEvent> batteryEventList =
DatabaseUtils.getBatteryEvents(
mContext, Calendar.getInstance(), mRawStartTimestamp);
Log.d(TAG, String.format("execute loadBatteryEventList size=%d in %d/ms",
mContext, Calendar.getInstance(), mRawStartTimestamp,
POWER_CONNECTION_EVENTS);
Log.d(TAG, String.format(
"execute loadPowerConnectionBatteryEventList size=%d in %d/ms",
batteryEventList.size(), (System.currentTimeMillis() - startTime)));
return batteryEventList;
}
@@ -352,29 +388,55 @@ public class DataProcessManager {
}.execute();
}
private void loadAndApplyBatteryMapFromServiceOnly() {
new AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>>() {
private void loadBatteryUsageSlotList() {
new AsyncTask<Void, Void, List<BatteryUsageSlot>>() {
@Override
protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) {
protected List<BatteryUsageSlot> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
DataProcessor.getBatteryUsageMapFromStatsService(mContext);
DataProcessor.loadLabelAndIcon(batteryUsageMap);
Log.d(TAG, String.format(
"execute loadAndApplyBatteryMapFromServiceOnly size=%d in %d/ms",
batteryUsageMap.size(), (System.currentTimeMillis() - startTime)));
return batteryUsageMap;
// Loads the battery usage slot data from the database.
final List<BatteryUsageSlot> batteryUsageSlotList =
DatabaseUtils.getBatteryUsageSlots(
mContext, Calendar.getInstance(), mLastFullChargeTimestamp);
Log.d(TAG, String.format("execute loadBatteryUsageSlotList size=%d in %d/ms",
batteryUsageSlotList.size(), (System.currentTimeMillis() - startTime)));
return batteryUsageSlotList;
}
@Override
protected void onPostExecute(
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
// Set the unused variables to null.
mContext = null;
protected void onPostExecute(final List<BatteryUsageSlot> batteryUsageSlotList) {
if (batteryUsageSlotList == null || batteryUsageSlotList.isEmpty()) {
Log.d(TAG, "batteryUsageSlotList is null or empty");
} else {
mBatteryUsageSlotList.clear();
mBatteryUsageSlotList.addAll(batteryUsageSlotList);
}
mIsBatteryUsageSlotLoaded = true;
tryToGenerateFinalDataAndApplyCallback();
}
}.execute();
}
private void loadAndApplyBatteryMapFromServiceOnly() {
new AsyncTask<Void, Void, Map<Long, BatteryDiffData>>() {
@Override
protected Map<Long, BatteryDiffData> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
final Map<Long, BatteryDiffData> batteryDiffDataMap =
DataProcessor.getBatteryDiffDataMapFromStatsService(
mContext, mRawStartTimestamp, getSystemAppsPackageNames(),
getSystemAppsUids());
Log.d(TAG, String.format(
"execute loadAndApplyBatteryMapFromServiceOnly size=%d in %d/ms",
batteryDiffDataMap.size(), (System.currentTimeMillis() - startTime)));
return batteryDiffDataMap;
}
@Override
protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) {
// Post results back to main thread to refresh UI.
if (mHandler != null && mCallbackFunction != null) {
mHandler.post(() -> {
mCallbackFunction.onBatteryCallbackDataLoaded(batteryUsageMap);
mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
});
}
}
@@ -406,38 +468,41 @@ public class DataProcessManager {
if (!mIsCurrentBatteryHistoryLoaded
|| !mIsCurrentAppUsageLoaded
|| !mIsDatabaseAppUsageLoaded
|| !mIsBatteryEventLoaded) {
|| !mIsBatteryEventLoaded
|| !mIsBatteryUsageSlotLoaded) {
return;
}
generateFinalDataAndApplyCallback();
}
private void generateFinalDataAndApplyCallback() {
new AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>>() {
private synchronized void generateFinalDataAndApplyCallback() {
new AsyncTask<Void, Void, Map<Long, BatteryDiffData>>() {
@Override
protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) {
protected Map<Long, BatteryDiffData> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
DataProcessor.getBatteryUsageMap(
mContext, mHourlyBatteryLevelsPerDay, mBatteryHistoryMap,
mAppUsagePeriodMap);
DataProcessor.loadLabelAndIcon(batteryUsageMap);
Log.d(TAG, String.format("execute generateFinalDataAndApplyCallback in %d/ms",
(System.currentTimeMillis() - startTime)));
return batteryUsageMap;
final Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>();
for (BatteryUsageSlot batteryUsageSlot : mBatteryUsageSlotList) {
batteryDiffDataMap.put(batteryUsageSlot.getStartTimestamp(),
ConvertUtils.convertToBatteryDiffData(
mContext, batteryUsageSlot, getSystemAppsPackageNames(),
getSystemAppsUids()));
}
batteryDiffDataMap.putAll(DataProcessor.getBatteryDiffDataMap(mContext,
mHourlyBatteryLevelsPerDay, mBatteryHistoryMap, mAppUsagePeriodMap,
getSystemAppsPackageNames(), getSystemAppsUids()));
Log.d(TAG, String.format(
"execute generateFinalDataAndApplyCallback size=%d in %d/ms",
batteryDiffDataMap.size(), System.currentTimeMillis() - startTime));
return batteryDiffDataMap;
}
@Override
protected void onPostExecute(
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
// Set the unused variables to null.
mContext = null;
mHourlyBatteryLevelsPerDay = null;
mBatteryHistoryMap = null;
protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) {
// Post results back to main thread to refresh UI.
if (mHandler != null && mCallbackFunction != null) {
mHandler.post(() -> {
mCallbackFunction.onBatteryCallbackDataLoaded(batteryUsageMap);
mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
});
}
}
@@ -445,7 +510,7 @@ public class DataProcessManager {
}
// Whether we should load app usage data from service or database.
private boolean shouldLoadAppUsageData() {
private synchronized boolean shouldLoadAppUsageData() {
if (!mShowScreenOnTime) {
return false;
}
@@ -480,6 +545,20 @@ public class DataProcessManager {
return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
}
private synchronized Set<String> getSystemAppsPackageNames() {
if (mSystemAppsPackageNames == null) {
mSystemAppsPackageNames = DataProcessor.getSystemAppsPackageNames(mContext);
}
return mSystemAppsPackageNames;
}
private synchronized Set<Integer> getSystemAppsUids() {
if (mSystemAppsUids == null) {
mSystemAppsUids = DataProcessor.getSystemAppsUids(mContext);
}
return mSystemAppsUids;
}
/**
* @return Returns battery level data and start async task to compute battery diff usage data
* and load app labels + icons.
@@ -489,14 +568,55 @@ public class DataProcessManager {
public static BatteryLevelData getBatteryLevelData(
Context context,
@Nullable Handler handler,
@Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
final DataProcessor.UsageMapAsyncResponse asyncResponseDelegate) {
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
Log.d(TAG, "batteryHistoryMap is null in getBatteryLevelData()");
new DataProcessManager(context, handler, asyncResponseDelegate).start();
final boolean isFromPeriodJob,
final OnBatteryDiffDataMapLoadedListener onBatteryUsageMapLoadedListener) {
final long start = System.currentTimeMillis();
final long lastFullChargeTime = DatabaseUtils.getLastFullChargeTime(context);
final List<BatteryEvent> batteryLevelRecordEvents =
DatabaseUtils.getBatteryEvents(
context, Calendar.getInstance(), lastFullChargeTime,
BATTERY_LEVEL_RECORD_EVENTS);
final long startTimestamp = batteryLevelRecordEvents.isEmpty()
? lastFullChargeTime : batteryLevelRecordEvents.get(0).getTimestamp();
final BatteryLevelData batteryLevelData = getPeriodBatteryLevelData(context, handler,
startTimestamp, lastFullChargeTime, isFromPeriodJob,
onBatteryUsageMapLoadedListener);
Log.d(TAG, String.format("execute getBatteryLevelData in %d/ms,"
+ " batteryLevelRecordEvents.size=%d",
(System.currentTimeMillis() - start), batteryLevelRecordEvents.size()));
return isFromPeriodJob
? batteryLevelData
: BatteryLevelData.combine(batteryLevelData, batteryLevelRecordEvents);
}
private static BatteryLevelData getPeriodBatteryLevelData(
Context context,
@Nullable Handler handler,
final long startTimestamp,
final long lastFullChargeTime,
final boolean isFromPeriodJob,
final OnBatteryDiffDataMapLoadedListener onBatteryDiffDataMapLoadedListener) {
final long currentTime = System.currentTimeMillis();
Log.d(TAG, String.format("getPeriodBatteryLevelData() startTimestamp=%s",
ConvertUtils.utcToLocalTimeForLogging(startTimestamp)));
if (isFromPeriodJob
&& startTimestamp >= TimestampUtils.getLastEvenHourTimestamp(currentTime)) {
// Nothing needs to be loaded for period job.
return null;
}
handler = handler != null ? handler : new Handler(Looper.getMainLooper());
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
sFakeBatteryHistoryMap != null ? sFakeBatteryHistoryMap
: DatabaseUtils.getHistoryMapSinceLatestRecordBeforeQueryTimestamp(context,
Calendar.getInstance(), startTimestamp, lastFullChargeTime);
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
Log.d(TAG, "batteryHistoryMap is null in getPeriodBatteryLevelData()");
new DataProcessManager(context, handler, onBatteryDiffDataMapLoadedListener).start();
return null;
}
// Process raw history map data into hourly timestamps.
final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap =
DataProcessor.getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap);
@@ -505,20 +625,20 @@ public class DataProcessManager {
DataProcessor.getLevelDataThroughProcessedHistoryMap(
context, processedBatteryHistoryMap);
if (batteryLevelData == null) {
new DataProcessManager(context, handler, asyncResponseDelegate).start();
new DataProcessManager(context, handler, onBatteryDiffDataMapLoadedListener).start();
Log.d(TAG, "getBatteryLevelData() returns null");
return null;
}
final long rawStartTimestamp = Collections.min(batteryHistoryMap.keySet());
// Start the async task to compute diff usage data and load labels and icons.
new DataProcessManager(
context,
handler,
rawStartTimestamp,
asyncResponseDelegate,
startTimestamp,
lastFullChargeTime,
onBatteryDiffDataMapLoadedListener,
batteryLevelData.getHourlyBatteryLevelsPerDay(),
processedBatteryHistoryMap).start();
processedBatteryHistoryMap).start(isFromPeriodJob);
return batteryLevelData;
}

View File

@@ -17,6 +17,9 @@
package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.getEffectivePackageName;
import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isSystemConsumer;
import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUidConsumer;
import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
@@ -44,6 +47,7 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -54,6 +58,8 @@ import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.settingslib.spaprivileged.model.app.AppListRepositoryUtil;
import com.google.common.base.Preconditions;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Calendar;
@@ -76,9 +82,7 @@ public final class DataProcessor {
private static final int POWER_COMPONENT_WAKELOCK = 12;
private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
private static final int MIN_DAILY_DATA_SIZE = 2;
private static final int MIN_TIMESTAMP_DATA_SIZE = 2;
private static final int MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP = 5;
private static final long MIN_TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2;
private static final String MEDIASERVER_PACKAGE_NAME = "mediaserver";
private static final String ANDROID_CORE_APPS_SHARED_USER_ID = "android.uid.shared";
private static final Map<String, BatteryHistEntry> EMPTY_BATTERY_MAP = new ArrayMap<>();
@@ -157,11 +161,14 @@ public final class DataProcessor {
}
return batteryLevelData == null
? null
: getBatteryUsageMap(
context,
batteryLevelData.getHourlyBatteryLevelsPerDay(),
processedBatteryHistoryMap,
/*appUsagePeriodMap=*/ null);
: generateBatteryUsageMap(context,
getBatteryDiffDataMap(context,
batteryLevelData.getHourlyBatteryLevelsPerDay(),
processedBatteryHistoryMap,
/*appUsagePeriodMap=*/ null,
getSystemAppsPackageNames(context),
getSystemAppsUids(context)),
batteryLevelData);
}
/**
@@ -261,7 +268,7 @@ public final class DataProcessor {
* </ul>
*
* <p>The structure is consistent with the battery usage map returned by
* {@code getBatteryUsageMap}.</p>
* {@code generateBatteryUsageMap}.</p>
*
* <p>{@code Long} stands for the userId.</p>
* <p>{@code String} stands for the packageName.</p>
@@ -403,8 +410,8 @@ public final class DataProcessor {
/**
* @return Returns the processed history map which has interpolated to every hour data.
* The start and end timestamp must be the even hours.
* The keys of processed history map should contain every hour between the start and end
* The start timestamp is the first timestamp in batteryHistoryMap. The end timestamp is current
* time. The keys of processed history map should contain every hour between the start and end
* timestamp. If there's no data in some key, the value will be the empty map.
*/
static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapWithExpectedTimestamps(
@@ -431,28 +438,23 @@ public final class DataProcessor {
static BatteryLevelData getLevelDataThroughProcessedHistoryMap(
Context context,
final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap) {
final List<Long> timestampList = new ArrayList<>(processedBatteryHistoryMap.keySet());
Collections.sort(timestampList);
final List<Long> dailyTimestamps = getDailyTimestamps(timestampList);
// There should be at least the start and end timestamps. Otherwise, return null to not show
// data in usage chart.
if (dailyTimestamps.size() < MIN_DAILY_DATA_SIZE) {
if (processedBatteryHistoryMap.size() < MIN_DAILY_DATA_SIZE) {
return null;
}
final List<List<Long>> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps);
final BatteryLevelData.PeriodBatteryLevelData dailyLevelData =
getPeriodBatteryLevelData(context, processedBatteryHistoryMap, dailyTimestamps);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyLevelData =
getHourlyPeriodBatteryLevelData(
context, processedBatteryHistoryMap, hourlyTimestamps);
return new BatteryLevelData(dailyLevelData, hourlyLevelData);
Map<Long, Integer> batteryLevelMap = new ArrayMap<>();
for (Long timestamp : processedBatteryHistoryMap.keySet()) {
batteryLevelMap.put(
timestamp, getLevel(context, processedBatteryHistoryMap, timestamp));
}
return new BatteryLevelData(batteryLevelMap);
}
/**
* Computes expected timestamp slots. The start timestamp is the last full charge time.
* The end timestamp is current time. The middle timestamps are the sharp hour timestamps
* between the start and end timestamps.
* Computes expected timestamp slots. The start timestamp is the first timestamp in
* rawTimestampList. The end timestamp is current time. The middle timestamps are the sharp hour
* timestamps between the start and end timestamps.
*/
@VisibleForTesting
static List<Long> getTimestampSlots(final List<Long> rawTimestampList, final long currentTime) {
@@ -475,56 +477,6 @@ public final class DataProcessor {
return timestampSlots;
}
/**
* Computes expected daily timestamp slots.
*
* The valid result should be composed of 3 parts:
* 1) start timestamp
* 2) every 00:00 timestamp (default timezone) between the start and end
* 3) end timestamp
* Otherwise, returns an empty list.
*/
@VisibleForTesting
static List<Long> getDailyTimestamps(final List<Long> timestampList) {
final List<Long> dailyTimestampList = new ArrayList<>();
// If timestamp number is smaller than 2, the following computation is not necessary.
if (timestampList.size() < MIN_TIMESTAMP_DATA_SIZE) {
return dailyTimestampList;
}
final long startTime = timestampList.get(0);
final long endTime = timestampList.get(timestampList.size() - 1);
for (long timestamp = startTime; timestamp < endTime;
timestamp = TimestampUtils.getNextDayTimestamp(timestamp)) {
dailyTimestampList.add(timestamp);
}
dailyTimestampList.add(endTime);
return dailyTimestampList;
}
@VisibleForTesting
static List<List<Long>> getHourlyTimestamps(final List<Long> dailyTimestamps) {
final List<List<Long>> hourlyTimestamps = new ArrayList<>();
if (dailyTimestamps.size() < MIN_DAILY_DATA_SIZE) {
return hourlyTimestamps;
}
for (int dailyIndex = 0; dailyIndex < dailyTimestamps.size() - 1; dailyIndex++) {
final List<Long> hourlyTimestampsPerDay = new ArrayList<>();
final long startTime = dailyTimestamps.get(dailyIndex);
final long endTime = dailyTimestamps.get(dailyIndex + 1);
hourlyTimestampsPerDay.add(startTime);
for (long timestamp = TimestampUtils.getNextEvenHourTimestamp(startTime);
timestamp < endTime; timestamp += MIN_TIME_SLOT) {
hourlyTimestampsPerDay.add(timestamp);
}
hourlyTimestampsPerDay.add(endTime);
hourlyTimestamps.add(hourlyTimestampsPerDay);
}
return hourlyTimestamps;
}
@VisibleForTesting
static boolean isFromFullCharge(@Nullable final Map<String, BatteryHistEntry> entryList) {
if (entryList == null) {
@@ -560,34 +512,102 @@ public final class DataProcessor {
return results;
}
static Map<Long, BatteryDiffData> getBatteryDiffDataMap(
Context context,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
appUsagePeriodMap,
final @NonNull Set<String> systemAppsPackageNames,
final @NonNull Set<Integer> systemAppsUids) {
final Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>();
final int currentUserId = context.getUserId();
final UserHandle userHandle =
Utils.getManagedProfile(context.getSystemService(UserManager.class));
final int workProfileUserId =
userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
// Each time slot usage diff data =
// sum(Math.abs(timestamp[i+1] data - timestamp[i] data));
// since we want to aggregate every hour usage diff data into a single time slot.
for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
continue;
}
final List<Long> hourlyTimestamps =
hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
for (int hourlyIndex = 0; hourlyIndex < hourlyTimestamps.size() - 1; hourlyIndex++) {
final Long startTimestamp = hourlyTimestamps.get(hourlyIndex);
final Long endTimestamp = hourlyTimestamps.get(hourlyIndex + 1);
final int startBatteryLevel =
hourlyBatteryLevelsPerDay.get(dailyIndex).getLevels().get(hourlyIndex);
final int endBatteryLevel =
hourlyBatteryLevelsPerDay.get(dailyIndex).getLevels().get(hourlyIndex + 1);
final long slotDuration = endTimestamp - startTimestamp;
List<Map<String, BatteryHistEntry>> slotBatteryHistoryList = new ArrayList<>();
slotBatteryHistoryList.add(
batteryHistoryMap.getOrDefault(startTimestamp, EMPTY_BATTERY_MAP));
for (Long timestamp = TimestampUtils.getNextHourTimestamp(startTimestamp);
timestamp < endTimestamp; timestamp += DateUtils.HOUR_IN_MILLIS) {
slotBatteryHistoryList.add(
batteryHistoryMap.getOrDefault(timestamp, EMPTY_BATTERY_MAP));
}
slotBatteryHistoryList.add(
batteryHistoryMap.getOrDefault(endTimestamp, EMPTY_BATTERY_MAP));
final BatteryDiffData hourlyBatteryDiffData =
insertHourlyUsageDiffDataPerSlot(
context,
startTimestamp,
endTimestamp,
startBatteryLevel,
endBatteryLevel,
currentUserId,
workProfileUserId,
slotDuration,
systemAppsPackageNames,
systemAppsUids,
appUsagePeriodMap == null
|| appUsagePeriodMap.get(dailyIndex) == null
? null
: appUsagePeriodMap.get(dailyIndex).get(hourlyIndex),
slotBatteryHistoryList);
batteryDiffDataMap.put(startTimestamp, hourlyBatteryDiffData);
}
}
return batteryDiffDataMap;
}
/**
* @return Returns the indexed battery usage data for each corresponding time slot.
*
* <p>There could be 2 cases of the returned value:</p>
* <ul>
* <li>null: empty or invalid data.</li>
* <li>non-null: must be a 2d map and composed by 3 parts:</li>
* <li> null: empty or invalid data.</li>
* <li> 1 part: if batteryLevelData is null.</li>
* <p> [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]</p>
* <li> 3 parts: if batteryLevelData is not null.</li>
* <p> 1 - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]</p>
* <p> 2 - [0][SELECTED_INDEX_ALL] ~ [maxDailyIndex][SELECTED_INDEX_ALL]</p>
* <p> 3 - [0][0] ~ [maxDailyIndex][maxHourlyIndex]</p>
* </ul>
*/
@Nullable
static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMap(
static Map<Integer, Map<Integer, BatteryDiffData>> generateBatteryUsageMap(
final Context context,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
appUsagePeriodMap) {
if (batteryHistoryMap.isEmpty()) {
return null;
}
final Map<Long, BatteryDiffData> batteryDiffDataMap,
final @Nullable BatteryLevelData batteryLevelData) {
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new ArrayMap<>();
final Set<String> systemAppsPackageNames = getSystemAppsPackageNames(context);
final Set<Integer> systemAppsUids = getSystemAppsUids(context);
if (batteryLevelData == null) {
Preconditions.checkArgument(batteryDiffDataMap.size() == 1);
BatteryDiffData batteryDiffData = batteryDiffDataMap.values().stream().toList().get(0);
final Map<Integer, BatteryDiffData> allUsageMap = new ArrayMap<>();
allUsageMap.put(SELECTED_INDEX_ALL, batteryDiffData);
resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
return resultMap;
}
List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
batteryLevelData.getHourlyBatteryLevelsPerDay();
// Insert diff data from [0][0] to [maxDailyIndex][maxHourlyIndex].
insertHourlyUsageDiffData(context, systemAppsPackageNames, systemAppsUids,
hourlyBatteryLevelsPerDay, batteryHistoryMap, appUsagePeriodMap, resultMap);
insertHourlyUsageDiffData(hourlyBatteryLevelsPerDay, batteryDiffDataMap, resultMap);
// Insert diff data from [0][SELECTED_INDEX_ALL] to [maxDailyIndex][SELECTED_INDEX_ALL].
insertDailyUsageDiffData(context, hourlyBatteryLevelsPerDay, resultMap);
// Insert diff data [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL].
@@ -602,7 +622,10 @@ public final class DataProcessor {
@Nullable
static BatteryDiffData generateBatteryDiffData(
final Context context,
final List<BatteryHistEntry> batteryHistEntryList) {
final long startTimestamp,
final List<BatteryHistEntry> batteryHistEntryList,
final @NonNull Set<String> systemAppsPackageNames,
final @NonNull Set<Integer> systemAppsUids) {
if (batteryHistEntryList == null || batteryHistEntryList.isEmpty()) {
Log.w(TAG, "batteryHistEntryList is null or empty in generateBatteryDiffData()");
return null;
@@ -624,6 +647,14 @@ public final class DataProcessor {
} else {
final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
context,
entry.mUid,
entry.mUserId,
entry.getKey(),
entry.mIsHidden,
entry.mDrainType,
entry.mPackageName,
entry.mAppLabel,
entry.mConsumerType,
entry.mForegroundUsageTimeInMs,
entry.mBackgroundUsageTimeInMs,
/*screenOnTimeInMs=*/ 0,
@@ -631,8 +662,7 @@ public final class DataProcessor {
entry.mForegroundUsageConsumePower,
entry.mForegroundServiceUsageConsumePower,
entry.mBackgroundUsageConsumePower,
entry.mCachedUsageConsumePower,
entry);
entry.mCachedUsageConsumePower);
if (currentBatteryDiffEntry.isSystemEntry()) {
systemEntries.add(currentBatteryDiffEntry);
} else {
@@ -645,11 +675,10 @@ public final class DataProcessor {
if (appEntries.isEmpty() && systemEntries.isEmpty()) {
return null;
}
final Set<String> systemAppsPackageNames = getSystemAppsPackageNames(context);
final Set<Integer> systemAppsUids = getSystemAppsUids(context);
return new BatteryDiffData(context, /* screenOnTime= */ 0L, appEntries, systemEntries,
systemAppsPackageNames, systemAppsUids, /* isAccumulated= */ false);
return new BatteryDiffData(context, startTimestamp, getCurrentTimeMillis(),
/* startBatteryLevel =*/ 100, getCurrentLevel(context), /* screenOnTime= */ 0L,
appEntries, systemEntries, systemAppsPackageNames, systemAppsUids,
/* isAccumulated= */ false);
}
/**
@@ -845,21 +874,15 @@ public final class DataProcessor {
return getScreenOnTime(appUsageMap.get(userId).get(packageName));
}
/**
* @return Returns the overall battery usage data from battery stats service directly.
*
* The returned value should be always a 2d map and composed by only 1 part:
* - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]
*/
static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMapFromStatsService(
final Context context) {
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new ArrayMap<>();
final Map<Integer, BatteryDiffData> allUsageMap = new ArrayMap<>();
// Always construct the map whether the value is null or not.
allUsageMap.put(SELECTED_INDEX_ALL,
generateBatteryDiffData(context, getBatteryHistListFromFromStatsService(context)));
resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
return resultMap;
static Map<Long, BatteryDiffData> getBatteryDiffDataMapFromStatsService(
final Context context, final long startTimestamp,
@NonNull final Set<String> systemAppsPackageNames,
@NonNull final Set<Integer> systemAppsUids) {
Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>(1);
batteryDiffDataMap.put(startTimestamp, generateBatteryDiffData(
context, startTimestamp, getBatteryHistListFromFromStatsService(context),
systemAppsPackageNames, systemAppsUids));
return batteryDiffDataMap;
}
static void loadLabelAndIcon(
@@ -878,6 +901,22 @@ public final class DataProcessor {
}
}
static Set<String> getSystemAppsPackageNames(Context context) {
return sTestSystemAppsPackageNames != null ? sTestSystemAppsPackageNames
: AppListRepositoryUtil.getSystemPackageNames(context, context.getUserId());
}
static Set<Integer> getSystemAppsUids(Context context) {
Set<Integer> result = new ArraySet<>(1);
try {
result.add(context.getPackageManager().getUidForSharedUser(
ANDROID_CORE_APPS_SHARED_USER_ID));
} catch (PackageManager.NameNotFoundException e) {
// No Android Core Apps
}
return result;
}
/**
* Generates the list of {@link AppUsageEvent} within the specific time range.
* The buffer is added to make sure the app usage calculation near the boundaries is correct.
@@ -1158,28 +1197,6 @@ public final class DataProcessor {
resultMap.put(currentSlot, newHistEntryMap);
}
private static List<BatteryLevelData.PeriodBatteryLevelData> getHourlyPeriodBatteryLevelData(
Context context,
final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
final List<List<Long>> timestamps) {
final List<BatteryLevelData.PeriodBatteryLevelData> levelData = new ArrayList<>();
timestamps.forEach(
timestampList -> levelData.add(
getPeriodBatteryLevelData(
context, processedBatteryHistoryMap, timestampList)));
return levelData;
}
private static BatteryLevelData.PeriodBatteryLevelData getPeriodBatteryLevelData(
Context context,
final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
final List<Long> timestamps) {
final List<Integer> levels = new ArrayList<>();
timestamps.forEach(
timestamp -> levels.add(getLevel(context, processedBatteryHistoryMap, timestamp)));
return new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels);
}
private static Integer getLevel(
Context context,
final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
@@ -1188,13 +1205,12 @@ public final class DataProcessor {
if (entryMap == null || entryMap.isEmpty()) {
Log.e(TAG, "abnormal entry list in the timestamp:"
+ ConvertUtils.utcToLocalTimeForLogging(timestamp));
return null;
return BATTERY_LEVEL_UNKNOWN;
}
// The current time battery history hasn't been loaded yet, returns the current battery
// level.
if (entryMap.containsKey(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) {
final Intent intent = BatteryUtils.getBatteryIntent(context);
return BatteryStatus.getBatteryLevel(intent);
return getCurrentLevel(context);
}
// Averages the battery level in each time slot to avoid corner conditions.
float batteryLevelCounter = 0;
@@ -1204,20 +1220,15 @@ public final class DataProcessor {
return Math.round(batteryLevelCounter / entryMap.size());
}
private static int getCurrentLevel(Context context) {
final Intent intent = BatteryUtils.getBatteryIntent(context);
return BatteryStatus.getBatteryLevel(intent);
}
private static void insertHourlyUsageDiffData(
Context context,
final Set<String> systemAppsPackageNames,
final Set<Integer> systemAppsUids,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
appUsagePeriodMap,
final Map<Long, BatteryDiffData> batteryDiffDataMap,
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
final int currentUserId = context.getUserId();
final UserHandle userHandle =
Utils.getManagedProfile(context.getSystemService(UserManager.class));
final int workProfileUserId =
userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
// Each time slot usage diff data =
// sum(Math.abs(timestamp[i+1] data - timestamp[i] data));
// since we want to aggregate every hour usage diff data into a single time slot.
@@ -1231,33 +1242,7 @@ public final class DataProcessor {
hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
for (int hourlyIndex = 0; hourlyIndex < hourlyTimestamps.size() - 1; hourlyIndex++) {
final Long startTimestamp = hourlyTimestamps.get(hourlyIndex);
final Long endTimestamp = hourlyTimestamps.get(hourlyIndex + 1);
final long slotDuration = endTimestamp - startTimestamp;
List<Map<String, BatteryHistEntry>> slotBatteryHistoryList = new ArrayList<>();
slotBatteryHistoryList.add(
batteryHistoryMap.getOrDefault(startTimestamp, EMPTY_BATTERY_MAP));
for (Long timestamp = TimestampUtils.getNextHourTimestamp(startTimestamp);
timestamp < endTimestamp; timestamp += DateUtils.HOUR_IN_MILLIS) {
slotBatteryHistoryList.add(
batteryHistoryMap.getOrDefault(timestamp, EMPTY_BATTERY_MAP));
}
slotBatteryHistoryList.add(
batteryHistoryMap.getOrDefault(endTimestamp, EMPTY_BATTERY_MAP));
final BatteryDiffData hourlyBatteryDiffData =
insertHourlyUsageDiffDataPerSlot(
context,
currentUserId,
workProfileUserId,
slotDuration,
systemAppsPackageNames,
systemAppsUids,
appUsagePeriodMap == null
|| appUsagePeriodMap.get(dailyIndex) == null
? null
: appUsagePeriodMap.get(dailyIndex).get(hourlyIndex),
slotBatteryHistoryList);
dailyDiffMap.put(hourlyIndex, hourlyBatteryDiffData);
dailyDiffMap.put(hourlyIndex, batteryDiffDataMap.get(startTimestamp));
}
}
}
@@ -1292,6 +1277,10 @@ public final class DataProcessor {
@Nullable
private static BatteryDiffData insertHourlyUsageDiffDataPerSlot(
final Context context,
final long startTimestamp,
final long endTimestamp,
final int startBatteryLevel,
final int endBatteryLevel,
final int currentUserId,
final int workProfileUserId,
final long slotDuration,
@@ -1401,7 +1390,7 @@ public final class DataProcessor {
currentEntry.mCachedUsageConsumePower,
nextEntry.mCachedUsageConsumePower);
}
if (selectedBatteryEntry.isSystemEntry()
if (isSystemConsumer(selectedBatteryEntry.mConsumerType)
&& selectedBatteryEntry.mDrainType == BatteryConsumer.POWER_COMPONENT_SCREEN) {
// Replace Screen system component time with screen on time.
foregroundUsageTimeInMs = slotScreenOnTime;
@@ -1447,6 +1436,14 @@ public final class DataProcessor {
backgroundUsageTimeInMs, (long) slotDuration - screenOnTime);
final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
context,
selectedBatteryEntry.mUid,
selectedBatteryEntry.mUserId,
selectedBatteryEntry.getKey(),
selectedBatteryEntry.mIsHidden,
selectedBatteryEntry.mDrainType,
selectedBatteryEntry.mPackageName,
selectedBatteryEntry.mAppLabel,
selectedBatteryEntry.mConsumerType,
foregroundUsageTimeInMs,
backgroundUsageTimeInMs,
screenOnTime,
@@ -1454,8 +1451,7 @@ public final class DataProcessor {
foregroundUsageConsumePower,
foregroundServiceUsageConsumePower,
backgroundUsageConsumePower,
cachedUsageConsumePower,
selectedBatteryEntry);
cachedUsageConsumePower);
if (currentBatteryDiffEntry.isSystemEntry()) {
systemEntries.add(currentBatteryDiffEntry);
} else {
@@ -1468,7 +1464,8 @@ public final class DataProcessor {
return null;
}
return new BatteryDiffData(context, slotScreenOnTime, appEntries, systemEntries,
return new BatteryDiffData(context, startTimestamp, endTimestamp, startBatteryLevel,
endBatteryLevel, slotScreenOnTime, appEntries, systemEntries,
systemAppsPackageNames, systemAppsUids, /* isAccumulated= */ false);
}
@@ -1519,7 +1516,7 @@ public final class DataProcessor {
final int currentUserId,
final int workProfileUserId,
final BatteryHistEntry batteryHistEntry) {
return batteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY
return isUidConsumer(batteryHistEntry.mConsumerType)
&& batteryHistEntry.mUserId != currentUserId
&& batteryHistEntry.mUserId != workProfileUserId;
}
@@ -1531,11 +1528,23 @@ public final class DataProcessor {
final List<BatteryDiffEntry> appEntries = new ArrayList<>();
final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
long startTimestamp = Long.MAX_VALUE;
long endTimestamp = 0;
int startBatteryLevel = BATTERY_LEVEL_UNKNOWN;
int endBatteryLevel = BATTERY_LEVEL_UNKNOWN;
long totalScreenOnTime = 0;
for (BatteryDiffData batteryDiffData : batteryDiffDataList) {
if (batteryDiffData == null) {
continue;
}
if (startTimestamp > batteryDiffData.getStartTimestamp()) {
startTimestamp = batteryDiffData.getStartTimestamp();
startBatteryLevel = batteryDiffData.getStartBatteryLevel();
}
if (endTimestamp > batteryDiffData.getEndTimestamp()) {
endTimestamp = batteryDiffData.getEndTimestamp();
endBatteryLevel = batteryDiffData.getEndBatteryLevel();
}
totalScreenOnTime += batteryDiffData.getScreenOnTime();
for (BatteryDiffEntry entry : batteryDiffData.getAppDiffEntryList()) {
computeUsageDiffDataPerEntry(entry, diffEntryMap);
@@ -1554,8 +1563,9 @@ public final class DataProcessor {
}
}
return diffEntryList.isEmpty() ? null : new BatteryDiffData(context, totalScreenOnTime,
appEntries, systemEntries, /* systemAppsPackageNames= */ new ArraySet<>(),
return diffEntryList.isEmpty() ? null : new BatteryDiffData(context, startTimestamp,
endTimestamp, startBatteryLevel, endBatteryLevel, totalScreenOnTime, appEntries,
systemEntries, /* systemAppsPackageNames= */ new ArraySet<>(),
/* systemAppsUids= */ new ArraySet<>(), /* isAccumulated= */ true);
}
@@ -1751,22 +1761,6 @@ public final class DataProcessor {
return v2 > v1 ? v2 - v1 : 0;
}
private static Set<String> getSystemAppsPackageNames(Context context) {
return sTestSystemAppsPackageNames != null ? sTestSystemAppsPackageNames
: AppListRepositoryUtil.getSystemPackageNames(context, context.getUserId());
}
private static Set<Integer> getSystemAppsUids(Context context) {
Set<Integer> result = new ArraySet<>();
try {
result.add(context.getPackageManager().getUidForSharedUser(
ANDROID_CORE_APPS_SHARED_USER_ID));
} catch (PackageManager.NameNotFoundException e) {
// No Android Core Apps
}
return result;
}
private static long getCurrentTimeMillis() {
return sTestCurrentTimeMillis > 0 ? sTestCurrentTimeMillis : System.currentTimeMillis();
}

View File

@@ -15,6 +15,8 @@
*/
package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageStatsManager;
import android.content.ContentResolver;
@@ -76,12 +78,20 @@ public final class DatabaseUtils {
public static final String BATTERY_EVENT_TABLE = "BatteryEvent";
/** A table name for battery usage history. */
public static final String BATTERY_STATE_TABLE = "BatteryState";
/** A table name for battery usage slot. */
public static final String BATTERY_USAGE_SLOT_TABLE = "BatteryUsageSlot";
/** A path name for last full charge time query. */
public static final String LAST_FULL_CHARGE_TIMESTAMP_PATH = "lastFullChargeTimestamp";
/** A path name for querying the latest record timestamp in battery state table. */
public static final String BATTERY_STATE_LATEST_TIMESTAMP_PATH = "batteryStateLatestTimestamp";
/** A path name for app usage latest timestamp query. */
public static final String APP_USAGE_LATEST_TIMESTAMP_PATH = "appUsageLatestTimestamp";
/** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/
public static final String QUERY_KEY_TIMESTAMP = "timestamp";
/** Key for query parameter userid used in APP_USAGE_EVENT_URI **/
public static final String QUERY_KEY_USERID = "userid";
/** Key for query parameter battery event type used in BATTERY_EVENT_URI **/
public static final String QUERY_BATTERY_EVENT_TYPE = "batteryEventType";
public static final long INVALID_USER_ID = Integer.MIN_VALUE;
/**
@@ -111,6 +121,13 @@ public final class DatabaseUtils {
.authority(AUTHORITY)
.appendPath(BATTERY_STATE_TABLE)
.build();
/** A content URI to access battery usage slots data. */
public static final Uri BATTERY_USAGE_SLOT_URI =
new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.appendPath(BATTERY_USAGE_SLOT_TABLE)
.build();
// For testing only.
@VisibleForTesting
@@ -140,7 +157,7 @@ public final class DatabaseUtils {
.build();
final long latestTimestamp =
loadAppUsageLatestTimestampFromContentProvider(context, appUsageLatestTimestampUri);
final String latestTimestampString = ConvertUtils.utcToLocalTimeForLogging(latestTimestamp);
final String latestTimestampString = utcToLocalTimeForLogging(latestTimestamp);
Log.d(TAG, String.format(
"getAppUsageStartTimestampOfUser() userId=%d latestTimestamp=%s in %d/ms",
userId, latestTimestampString, (System.currentTimeMillis() - startTime)));
@@ -161,8 +178,7 @@ public final class DatabaseUtils {
// sure the app usage calculation near the boundaries is correct.
final long queryTimestamp =
Math.max(rawStartTimestamp, sixDaysAgoTimestamp) - USAGE_QUERY_BUFFER_HOURS;
Log.d(TAG, "sixDayAgoTimestamp: " + ConvertUtils.utcToLocalTimeForLogging(
sixDaysAgoTimestamp));
Log.d(TAG, "sixDaysAgoTimestamp: " + utcToLocalTimeForLogging(sixDaysAgoTimestamp));
final String queryUserIdString = userIds.stream()
.map(userId -> String.valueOf(userId))
.collect(Collectors.joining(","));
@@ -189,11 +205,15 @@ public final class DatabaseUtils {
public static List<BatteryEvent> getBatteryEvents(
Context context,
final Calendar calendar,
final long rawStartTimestamp) {
final long rawStartTimestamp,
final List<BatteryEventType> queryBatteryEventTypes) {
final long startTime = System.currentTimeMillis();
final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp);
Log.d(TAG, "getBatteryEvents for timestamp: " + queryTimestamp);
final String queryBatteryEventTypesString = queryBatteryEventTypes.stream()
.map(type -> String.valueOf(type.getNumber()))
.collect(Collectors.joining(","));
// Builds the content uri everytime to avoid cache.
final Uri batteryEventUri =
new Uri.Builder()
@@ -202,6 +222,8 @@ public final class DatabaseUtils {
.appendPath(BATTERY_EVENT_TABLE)
.appendQueryParameter(
QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
.appendQueryParameter(
QUERY_BATTERY_EVENT_TYPE, queryBatteryEventTypesString)
.build();
final List<BatteryEvent> batteryEventList =
@@ -211,13 +233,82 @@ public final class DatabaseUtils {
return batteryEventList;
}
/** Long: for timestamp and String: for BatteryHistEntry.getKey() */
public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
Context context, Calendar calendar) {
/**
* Returns the battery usage slot data after {@code rawStartTimestamp} in battery event table.
*/
public static List<BatteryUsageSlot> getBatteryUsageSlots(
Context context,
final Calendar calendar,
final long rawStartTimestamp) {
final long startTime = System.currentTimeMillis();
final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
Log.d(TAG, "sixDayAgoTimestamp: " + ConvertUtils.utcToLocalTimeForLogging(
sixDaysAgoTimestamp));
final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp);
Log.d(TAG, "getBatteryUsageSlots for timestamp: " + queryTimestamp);
// Builds the content uri everytime to avoid cache.
final Uri batteryUsageSlotUri =
new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.appendPath(BATTERY_USAGE_SLOT_TABLE)
.appendQueryParameter(
QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
.build();
final List<BatteryUsageSlot> batteryUsageSlotList =
loadBatteryUsageSlotsFromContentProvider(context, batteryUsageSlotUri);
Log.d(TAG, String.format("getBatteryUsageSlots size=%d in %d/ms",
batteryUsageSlotList.size(), (System.currentTimeMillis() - startTime)));
return batteryUsageSlotList;
}
/** Returns the last full charge time. */
public static long getLastFullChargeTime(Context context) {
final long startTime = System.currentTimeMillis();
// Builds the content uri everytime to avoid cache.
final Uri lastFullChargeTimeUri =
new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.appendPath(LAST_FULL_CHARGE_TIMESTAMP_PATH)
.build();
final long lastFullChargeTime = loadLastFullChargeTimeFromContentProvider(
context, lastFullChargeTimeUri);
final String lastFullChargeTimeString = utcToLocalTimeForLogging(lastFullChargeTime);
Log.d(TAG, String.format(
"getLastFullChargeTime() lastFullChargeTime=%s in %d/ms",
lastFullChargeTimeString, (System.currentTimeMillis() - startTime)));
return lastFullChargeTime;
}
/** Returns the first battery state timestamp no later than the {@code queryTimestamp}. */
@VisibleForTesting
static long getBatteryStateLatestTimestampBeforeQueryTimestamp(
Context context, final long queryTimestamp) {
final long startTime = System.currentTimeMillis();
// Builds the content uri everytime to avoid cache.
final Uri batteryStateLatestTimestampUri =
new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.appendPath(BATTERY_STATE_LATEST_TIMESTAMP_PATH)
.appendQueryParameter(
QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
.build();
final long batteryStateLatestTimestamp = loadBatteryStateLatestTimestampFromContentProvider(
context, batteryStateLatestTimestampUri);
final String batteryStateLatestTimestampString =
utcToLocalTimeForLogging(batteryStateLatestTimestamp);
Log.d(TAG, String.format(
"getBatteryStateLatestTimestamp() batteryStateLatestTimestamp=%s in %d/ms",
batteryStateLatestTimestampString, (System.currentTimeMillis() - startTime)));
return batteryStateLatestTimestamp;
}
/** Returns the battery history map after the given timestamp. */
@VisibleForTesting
static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceQueryTimestamp(
Context context, final long queryTimestamp) {
final long startTime = System.currentTimeMillis();
// Builds the content uri everytime to avoid cache.
final Uri batteryStateUri =
new Uri.Builder()
@@ -225,20 +316,46 @@ public final class DatabaseUtils {
.authority(AUTHORITY)
.appendPath(BATTERY_STATE_TABLE)
.appendQueryParameter(
QUERY_KEY_TIMESTAMP, Long.toString(sixDaysAgoTimestamp))
QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
.build();
final Map<Long, Map<String, BatteryHistEntry>> resultMap =
loadHistoryMapFromContentProvider(context, batteryStateUri);
if (resultMap == null || resultMap.isEmpty()) {
Log.d(TAG, "getHistoryMapSinceLastFullCharge() returns empty or null");
Log.d(TAG, "getBatteryHistoryMap() returns empty or null");
} else {
Log.d(TAG, String.format("getHistoryMapSinceLastFullCharge() size=%d in %d/ms",
Log.d(TAG, String.format("getBatteryHistoryMap() size=%d in %d/ms",
resultMap.size(), (System.currentTimeMillis() - startTime)));
}
return resultMap;
}
/**
* Returns the battery history map since the latest record no later than the given timestamp.
* If there is no record before the given timestamp or the given timestamp is before last full
* charge time, returns the history map since last full charge time.
*/
public static Map<Long, Map<String, BatteryHistEntry>>
getHistoryMapSinceLatestRecordBeforeQueryTimestamp(Context context, Calendar calendar,
final long queryTimestamp, final long lastFullChargeTime) {
final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
Log.d(TAG, "sixDaysAgoTimestamp: " + utcToLocalTimeForLogging(sixDaysAgoTimestamp));
final long batteryStateLatestTimestamp =
queryTimestamp == 0L ? 0L : getBatteryStateLatestTimestampBeforeQueryTimestamp(
context, queryTimestamp);
final long maxTimestamp = Math.max(Math.max(
sixDaysAgoTimestamp, lastFullChargeTime), batteryStateLatestTimestamp);
return getHistoryMapSinceQueryTimestamp(context, maxTimestamp);
}
/** Returns the history map since last full charge time. */
public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
Context context, Calendar calendar) {
final long lastFullChargeTime = getLastFullChargeTime(context);
return getHistoryMapSinceLatestRecordBeforeQueryTimestamp(
context, calendar, 0, lastFullChargeTime);
}
/** Clears all data in the battery usage database. */
public static void clearAll(Context context) {
AsyncTask.execute(() -> {
@@ -248,6 +365,7 @@ public final class DatabaseUtils {
database.appUsageEventDao().clearAll();
database.batteryEventDao().clearAll();
database.batteryStateDao().clearAll();
database.batteryUsageSlotDao().clearAll();
} catch (RuntimeException e) {
Log.e(TAG, "clearAll() failed", e);
}
@@ -265,6 +383,7 @@ public final class DatabaseUtils {
database.appUsageEventDao().clearAllBefore(earliestTimestamp);
database.batteryEventDao().clearAllBefore(earliestTimestamp);
database.batteryStateDao().clearAllBefore(earliestTimestamp);
database.batteryUsageSlotDao().clearAllBefore(earliestTimestamp);
} catch (RuntimeException e) {
Log.e(TAG, "clearAllBefore() failed", e);
}
@@ -293,7 +412,7 @@ public final class DatabaseUtils {
/*user=*/ context.getSystemService(UserManager.class)
.getProfileParent(context.getUser()));
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "context.createPackageContextAsUser() fail:" + e);
Log.e(TAG, "context.createPackageContextAsUser() fail:", e);
return null;
}
}
@@ -320,7 +439,7 @@ public final class DatabaseUtils {
resolver.notifyChange(APP_USAGE_EVENT_URI, /*observer=*/ null);
Log.d(TAG, "insert() app usage events data into database");
} catch (Exception e) {
Log.e(TAG, "bulkInsert() app usage data into database error:\n" + e);
Log.e(TAG, "bulkInsert() app usage data into database error:", e);
}
}
Log.d(TAG, String.format("sendAppUsageEventData() size=%d in %d/ms",
@@ -346,8 +465,65 @@ public final class DatabaseUtils {
return contentValues;
}
static List<ContentValues> sendBatteryEventData(
final Context context, final List<BatteryEvent> batteryEventList) {
final long startTime = System.currentTimeMillis();
// Creates the ContentValues list to insert them into provider.
final List<ContentValues> valuesList = new ArrayList<>();
batteryEventList.stream()
.forEach(batteryEvent -> valuesList.add(
ConvertUtils.convertBatteryEventToContentValues(batteryEvent)));
int size = 0;
final ContentResolver resolver = context.getContentResolver();
// Inserts all ContentValues into battery provider.
if (!valuesList.isEmpty()) {
final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
valuesList.toArray(valuesArray);
try {
size = resolver.bulkInsert(BATTERY_EVENT_URI, valuesArray);
resolver.notifyChange(BATTERY_EVENT_URI, /*observer=*/ null);
Log.d(TAG, "insert() battery event data into database");
} catch (Exception e) {
Log.e(TAG, "bulkInsert() battery event data into database error:", e);
}
}
Log.d(TAG, String.format("sendBatteryEventData() size=%d in %d/ms",
size, (System.currentTimeMillis() - startTime)));
clearMemory();
return valuesList;
}
static List<ContentValues> sendBatteryUsageSlotData(
final Context context, final List<BatteryUsageSlot> batteryUsageSlotList) {
final long startTime = System.currentTimeMillis();
// Creates the ContentValues list to insert them into provider.
final List<ContentValues> valuesList = new ArrayList<>();
batteryUsageSlotList.stream()
.forEach(batteryUsageSlot -> valuesList.add(
ConvertUtils.convertBatteryUsageSlotToContentValues(batteryUsageSlot)));
int size = 0;
final ContentResolver resolver = context.getContentResolver();
// Inserts all ContentValues into battery provider.
if (!valuesList.isEmpty()) {
final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
valuesList.toArray(valuesArray);
try {
size = resolver.bulkInsert(BATTERY_USAGE_SLOT_URI, valuesArray);
resolver.notifyChange(BATTERY_USAGE_SLOT_URI, /*observer=*/ null);
Log.d(TAG, "insert() battery usage slots data into database");
} catch (Exception e) {
Log.e(TAG, "bulkInsert() battery usage slots data into database error:", e);
}
}
Log.d(TAG, String.format("sendBatteryUsageSlotData() size=%d in %d/ms",
size, (System.currentTimeMillis() - startTime)));
clearMemory();
return valuesList;
}
static List<ContentValues> sendBatteryEntryData(
final Context context,
final long snapshotTimestamp,
final List<BatteryEntry> batteryEntryList,
final BatteryUsageStats batteryUsageStats,
final boolean isFullChargeStart) {
@@ -364,7 +540,6 @@ public final class DatabaseUtils {
final int batteryHealth = intent.getIntExtra(
BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
// We should use the same timestamp for each data snapshot.
final long snapshotTimestamp = Clock.systemUTC().millis();
final long snapshotBootTimestamp = SystemClock.elapsedRealtime();
// Creates the ContentValues list to insert them into provider.
@@ -409,8 +584,7 @@ public final class DatabaseUtils {
Log.d(TAG, "insert() battery states data into database with isFullChargeStart:"
+ isFullChargeStart);
} catch (Exception e) {
errorMessage = e.toString();
Log.e(TAG, "bulkInsert() data into database error:\n" + errorMessage);
Log.e(TAG, "bulkInsert() data into database error:", e);
}
} else {
// Inserts one fake data into battery provider.
@@ -430,8 +604,7 @@ public final class DatabaseUtils {
+ isFullChargeStart);
} catch (Exception e) {
errorMessage = e.toString();
Log.e(TAG, "insert() data into database error:\n" + errorMessage);
Log.e(TAG, "insert() data into database error:", e);
}
valuesList.add(contentValues);
}
@@ -504,8 +677,7 @@ public final class DatabaseUtils {
static void recordDateTime(Context context, String preferenceKey) {
final SharedPreferences sharedPreferences = getSharedPreferences(context);
if (sharedPreferences != null) {
final String currentTime = ConvertUtils.utcToLocalTimeForLogging(
System.currentTimeMillis());
final String currentTime = utcToLocalTimeForLogging(System.currentTimeMillis());
sharedPreferences.edit().putString(preferenceKey, currentTime).apply();
}
}
@@ -533,11 +705,6 @@ public final class DatabaseUtils {
cursor.moveToFirst();
// There is only one column returned so use the index 0 directly.
final long latestTimestamp = cursor.getLong(/*columnIndex=*/ 0);
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, "cursor.close() failed", e);
}
// If there is no data for this user, 0 will be returned from the database.
return latestTimestamp == 0 ? INVALID_USER_ID : latestTimestamp;
}
@@ -556,14 +723,9 @@ public final class DatabaseUtils {
if (cursor == null || cursor.getCount() == 0) {
return appUsageEventList;
}
// Loads and recovers all AppUsageEvent data from cursor.
// Loads and converts all AppUsageEvent data from cursor.
while (cursor.moveToNext()) {
appUsageEventList.add(ConvertUtils.convertToAppUsageEventFromCursor(cursor));
}
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, "cursor.close() failed", e);
appUsageEventList.add(ConvertUtils.convertToAppUsageEvent(cursor));
}
}
return appUsageEventList;
@@ -582,19 +744,71 @@ public final class DatabaseUtils {
if (cursor == null || cursor.getCount() == 0) {
return batteryEventList;
}
// Loads and recovers all AppUsageEvent data from cursor.
// Loads and converts all AppUsageEvent data from cursor.
while (cursor.moveToNext()) {
batteryEventList.add(ConvertUtils.convertToBatteryEventFromCursor(cursor));
}
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, "cursor.close() failed", e);
batteryEventList.add(ConvertUtils.convertToBatteryEvent(cursor));
}
}
return batteryEventList;
}
private static List<BatteryUsageSlot> loadBatteryUsageSlotsFromContentProvider(
Context context, Uri batteryUsageSlotUri) {
final List<BatteryUsageSlot> batteryUsageSlotList = new ArrayList<>();
context = getParentContext(context);
if (context == null) {
return batteryUsageSlotList;
}
try (Cursor cursor = sFakeSupplier != null
? sFakeSupplier.get()
: context.getContentResolver().query(batteryUsageSlotUri, null, null, null)) {
if (cursor == null || cursor.getCount() == 0) {
return batteryUsageSlotList;
}
// Loads and converts all AppUsageEvent data from cursor.
while (cursor.moveToNext()) {
batteryUsageSlotList.add(ConvertUtils.convertToBatteryUsageSlot(cursor));
}
}
return batteryUsageSlotList;
}
private static long loadLastFullChargeTimeFromContentProvider(
Context context, final Uri lastFullChargeTimeUri) {
// We have already make sure the context here is with profile parent's user identity. Don't
// need to check whether current user is work profile.
try (Cursor cursor = sFakeSupplier != null
? sFakeSupplier.get()
: context.getContentResolver().query(
lastFullChargeTimeUri, null, null, null)) {
if (cursor == null || cursor.getCount() == 0) {
return 0L;
}
cursor.moveToFirst();
// There is only one column returned so use the index 0 directly.
final long lastFullChargeTime = cursor.getLong(/*columnIndex=*/ 0);
return lastFullChargeTime;
}
}
private static long loadBatteryStateLatestTimestampFromContentProvider(
Context context, final Uri batteryStateLatestTimestampUri) {
// We have already make sure the context here is with profile parent's user identity. Don't
// need to check whether current user is work profile.
try (Cursor cursor = sFakeSupplier != null
? sFakeSupplier.get()
: context.getContentResolver().query(
batteryStateLatestTimestampUri, null, null, null)) {
if (cursor == null || cursor.getCount() == 0) {
return 0L;
}
cursor.moveToFirst();
// There is only one column returned so use the index 0 directly.
final long batteryStateLatestTimestamp = cursor.getLong(/*columnIndex=*/ 0);
return batteryStateLatestTimestamp;
}
}
private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
Context context, Uri batteryStateUri) {
context = getParentContext(context);
@@ -607,7 +821,7 @@ public final class DatabaseUtils {
if (cursor == null || cursor.getCount() == 0) {
return resultMap;
}
// Loads and recovers all BatteryHistEntry data from cursor.
// Loads and converts all BatteryHistEntry data from cursor.
while (cursor.moveToNext()) {
final BatteryHistEntry entry = new BatteryHistEntry(cursor);
final long timestamp = entry.mTimestamp;
@@ -620,11 +834,6 @@ public final class DatabaseUtils {
}
batteryHistEntryMap.put(key, entry);
}
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, "cursor.close() failed", e);
}
}
return resultMap;
}

View File

@@ -44,7 +44,6 @@ public final class PeriodicJobReceiver extends BroadcastReceiver {
}
BatteryUsageLogUtils.writeLog(context, Action.EXECUTE_JOB, "");
BatteryUsageDataLoader.enqueueWork(context, /*isFullChargeStart=*/ false);
AppUsageDataLoader.enqueueWork(context);
Log.d(TAG, "refresh periodic job from action=" + action);
PeriodicJobManager.getInstance(context).refreshJob(/*fromBoot=*/ false);
DatabaseUtils.clearExpiredDataIfNeeded(context);

View File

@@ -21,12 +21,13 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.SearchIndexableResource;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
@@ -39,11 +40,13 @@ import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.utils.AsyncLoaderCompat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/** Advanced power usage. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
@@ -55,16 +58,17 @@ public class PowerUsageAdvanced extends PowerUsageBase {
@VisibleForTesting
BatteryHistoryPreference mHistPref;
@VisibleForTesting
Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
@VisibleForTesting
final BatteryHistoryLoaderCallbacks mBatteryHistoryLoaderCallbacks =
new BatteryHistoryLoaderCallbacks();
final BatteryLevelDataLoaderCallbacks mBatteryLevelDataLoaderCallbacks =
new BatteryLevelDataLoaderCallbacks();
private boolean mIsChartDataLoaded = false;
private long mResumeTimestamp;
private BatteryChartPreferenceController mBatteryChartPreferenceController;
private Optional<BatteryLevelData> mBatteryLevelData;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final ContentObserver mBatteryObserver =
new ContentObserver(new Handler()) {
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
Log.d(TAG, "onBatteryContentChange: " + selfChange);
@@ -79,6 +83,7 @@ public class PowerUsageAdvanced extends PowerUsageBase {
super.onCreate(icicle);
mHistPref = findPreference(KEY_BATTERY_CHART);
setBatteryChartPreferenceController();
AsyncTask.execute(() -> BootBroadcastReceiver.invokeJobRecheck(getContext()));
}
@Override
@@ -109,6 +114,7 @@ public class PowerUsageAdvanced extends PowerUsageBase {
super.onPause();
// Resets the flag to reload usage data in onResume() callback.
mIsChartDataLoaded = false;
mBatteryLevelData = null;
final Uri uri = DatabaseUtils.BATTERY_CONTENT_URI;
if (uri != null) {
getContext().getContentResolver().unregisterContentObserver(mBatteryObserver);
@@ -118,6 +124,7 @@ public class PowerUsageAdvanced extends PowerUsageBase {
@Override
public void onResume() {
super.onResume();
mResumeTimestamp = System.currentTimeMillis();
final Uri uri = DatabaseUtils.BATTERY_CONTENT_URI;
if (uri != null) {
getContext().getContentResolver().registerContentObserver(
@@ -158,21 +165,9 @@ public class PowerUsageAdvanced extends PowerUsageBase {
return controllers;
}
@Override
protected boolean isBatteryHistoryNeeded() {
return true;
}
@Override
protected void refreshUi(@BatteryUpdateType int refreshType) {
final Context context = getContext();
if (context == null) {
return;
}
updatePreference(mHistPref);
if (mBatteryChartPreferenceController != null && mBatteryHistoryMap != null) {
mBatteryChartPreferenceController.setBatteryHistoryMap(mBatteryHistoryMap);
}
// Do nothing
}
@Override
@@ -181,11 +176,32 @@ public class PowerUsageAdvanced extends PowerUsageBase {
bundle.putInt(KEY_REFRESH_TYPE, refreshType);
if (!mIsChartDataLoaded) {
mIsChartDataLoaded = true;
restartLoader(LoaderIndex.BATTERY_HISTORY_LOADER, bundle,
mBatteryHistoryLoaderCallbacks);
restartLoader(LoaderIndex.BATTERY_LEVEL_DATA_LOADER, bundle,
mBatteryLevelDataLoaderCallbacks);
}
}
private void onBatteryLevelDataUpdate(BatteryLevelData batteryLevelData) {
mBatteryLevelData = Optional.ofNullable(batteryLevelData);
if (mBatteryChartPreferenceController != null) {
mBatteryChartPreferenceController.onBatteryLevelDataUpdate(batteryLevelData);
Log.d(TAG, String.format("Battery chart shows in %d millis",
System.currentTimeMillis() - mResumeTimestamp));
}
}
private void onBatteryDiffDataMapUpdate(Map<Long, BatteryDiffData> batteryDiffDataMap) {
if (mBatteryLevelData != null && mBatteryChartPreferenceController != null) {
Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
DataProcessor.generateBatteryUsageMap(
getContext(), batteryDiffDataMap, mBatteryLevelData.orElse(null));
DataProcessor.loadLabelAndIcon(batteryUsageMap);
mBatteryChartPreferenceController.onBatteryUsageMapUpdate(batteryUsageMap);
}
Log.d(TAG, String.format("Battery usage list shows in %d millis",
System.currentTimeMillis() - mResumeTimestamp));
}
private void setBatteryChartPreferenceController() {
if (mHistPref != null && mBatteryChartPreferenceController != null) {
mHistPref.setChartPreferenceController(mBatteryChartPreferenceController);
@@ -216,28 +232,31 @@ public class PowerUsageAdvanced extends PowerUsageBase {
}
};
private class BatteryHistoryLoaderCallbacks
implements LoaderManager.LoaderCallbacks<Map<Long, Map<String, BatteryHistEntry>>> {
private int mRefreshType;
private class BatteryLevelDataLoaderCallbacks
implements LoaderManager.LoaderCallbacks<BatteryLevelData> {
@Override
@NonNull
public Loader<Map<Long, Map<String, BatteryHistEntry>>> onCreateLoader(
int id, Bundle bundle) {
mRefreshType = bundle.getInt(KEY_REFRESH_TYPE);
return new BatteryHistoryLoader(getContext());
public Loader<BatteryLevelData> onCreateLoader(int id, Bundle bundle) {
return new AsyncLoaderCompat<BatteryLevelData>(getContext().getApplicationContext()) {
@Override
protected void onDiscardResult(BatteryLevelData result) {}
@Override
public BatteryLevelData loadInBackground() {
return DataProcessManager.getBatteryLevelData(
getContext(), mHandler, /*isFromPeriodJob=*/ false,
map -> PowerUsageAdvanced.this.onBatteryDiffDataMapUpdate(map));
}
};
}
@Override
public void onLoadFinished(Loader<Map<Long, Map<String, BatteryHistEntry>>> loader,
Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
mBatteryHistoryMap = batteryHistoryMap;
PowerUsageAdvanced.this.onLoadFinished(mRefreshType);
public void onLoadFinished(Loader<BatteryLevelData> loader,
BatteryLevelData batteryLevelData) {
PowerUsageAdvanced.this.onBatteryLevelDataUpdate(batteryLevelData);
}
@Override
public void onLoaderReset(Loader<Map<Long, Map<String, BatteryHistEntry>>> loader) {
public void onLoaderReset(Loader<BatteryLevelData> loader) {
}
}
}

View File

@@ -32,7 +32,6 @@ import androidx.loader.content.Loader;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.BatteryBroadcastReceiver;
import com.android.settings.fuelgauge.BatteryUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -63,14 +62,14 @@ public abstract class PowerUsageBase extends DashboardFragment {
LoaderIndex.BATTERY_USAGE_STATS_LOADER,
LoaderIndex.BATTERY_INFO_LOADER,
LoaderIndex.BATTERY_TIP_LOADER,
LoaderIndex.BATTERY_HISTORY_LOADER
LoaderIndex.BATTERY_LEVEL_DATA_LOADER
})
public @interface LoaderIndex {
int BATTERY_USAGE_STATS_LOADER = 0;
int BATTERY_INFO_LOADER = 1;
int BATTERY_TIP_LOADER = 2;
int BATTERY_HISTORY_LOADER = 3;
int BATTERY_LEVEL_DATA_LOADER = 3;
}
@Override
@@ -108,7 +107,7 @@ public abstract class PowerUsageBase extends DashboardFragment {
protected void restartBatteryStatsLoader(int refreshType) {
final Bundle bundle = new Bundle();
bundle.putInt(KEY_REFRESH_TYPE, refreshType);
bundle.putBoolean(KEY_INCLUDE_HISTORY, isBatteryHistoryNeeded());
bundle.putBoolean(KEY_INCLUDE_HISTORY, false);
restartLoader(LoaderIndex.BATTERY_USAGE_STATS_LOADER, bundle,
mBatteryUsageStatsLoaderCallbacks);
}
@@ -137,14 +136,6 @@ public abstract class PowerUsageBase extends DashboardFragment {
protected abstract void refreshUi(@BatteryUpdateType int refreshType);
protected abstract boolean isBatteryHistoryNeeded();
protected void updatePreference(BatteryHistoryPreference historyPref) {
final long startTime = System.currentTimeMillis();
historyPref.setBatteryUsageStats(mBatteryUsageStats);
BatteryUtils.logRuntime(TAG, "updatePreference", startTime);
}
private class BatteryUsageStatsLoaderCallbacks
implements LoaderManager.LoaderCallbacks<BatteryUsageStats> {
private int mRefreshType;

View File

@@ -45,7 +45,6 @@ import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.LayoutPreference;
import java.util.List;
@@ -69,8 +68,6 @@ public class PowerUsageSummary extends PowerUsageBase implements
@VisibleForTesting
BatteryUtils mBatteryUtils;
@VisibleForTesting
LayoutPreference mBatteryLayoutPref;
@VisibleForTesting
BatteryInfo mBatteryInfo;
@VisibleForTesting
@@ -208,11 +205,6 @@ public class PowerUsageSummary extends PowerUsageBase implements
return R.string.help_url_battery;
}
@Override
protected boolean isBatteryHistoryNeeded() {
return false;
}
protected void refreshUi(@BatteryUpdateType int refreshType) {
final Context context = getContext();
if (context == null) {
@@ -239,11 +231,6 @@ public class PowerUsageSummary extends PowerUsageBase implements
restartLoader(LoaderIndex.BATTERY_TIP_LOADER, Bundle.EMPTY, mBatteryTipsCallbacks);
}
@VisibleForTesting
void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
mBatteryLayoutPref = layoutPreference;
}
@VisibleForTesting
void initFeatureProvider() {
final Context context = getContext();

View File

@@ -36,9 +36,16 @@ public interface BatteryEventDao {
@Query("SELECT * FROM BatteryEventEntity ORDER BY timestamp DESC")
List<BatteryEventEntity> getAll();
/** Gets the {@link Cursor} of the last full charge time . */
@Query("SELECT MAX(timestamp) FROM BatteryEventEntity"
+ " WHERE batteryEventType = 3") // BatteryEventType.FULL_CHARGED = 3
Cursor getLastFullChargeTimestamp();
/** Gets the {@link Cursor} of all recorded data after a specific timestamp. */
@Query("SELECT * FROM BatteryEventEntity WHERE timestamp > :timestamp ORDER BY timestamp DESC")
Cursor getAllAfter(long timestamp);
@Query("SELECT * FROM BatteryEventEntity"
+ " WHERE timestamp > :timestamp AND batteryEventType IN (:batteryEventTypes)"
+ " ORDER BY timestamp DESC")
Cursor getAllAfter(long timestamp, List<Integer> batteryEventTypes);
/** Deletes all recorded data before a specific timestamp. */
@Query("DELETE FROM BatteryEventEntity WHERE timestamp <= :timestamp")

View File

@@ -37,16 +37,18 @@ public interface BatteryStateDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<BatteryState> states);
/** Gets the {@link Cursor} of the latest record timestamp no later than the given timestamp. */
@Query("SELECT MAX(timestamp) FROM BatteryState WHERE timestamp <= :timestamp")
Cursor getLatestTimestampBefore(long timestamp);
/** Lists all recorded battery states after a specific timestamp. */
@Query("SELECT * FROM BatteryState WHERE timestamp >= :timestamp ORDER BY timestamp ASC")
Cursor getBatteryStatesAfter(long timestamp);
/** Lists all recorded data after a specific timestamp. */
@Query("SELECT * FROM BatteryState WHERE timestamp > :timestamp ORDER BY timestamp DESC")
List<BatteryState> getAllAfter(long timestamp);
/** Gets the {@link Cursor} of all recorded data since last full charge within 7 days. */
@Query("SELECT * FROM BatteryState WHERE timestamp >= :timestampSixDaysAgo AND timestamp >= "
+ "(SELECT IFNULL((SELECT MAX(timestamp) FROM BatteryState "
+ "WHERE isFullChargeCycleStart = 1), 0)) ORDER BY timestamp ASC")
Cursor getCursorSinceLastFullCharge(long timestampSixDaysAgo);
/** Get the count of distinct timestamp after a specific timestamp. */
@Query("SELECT COUNT(DISTINCT timestamp) FROM BatteryState WHERE timestamp > :timestamp")
int getDistinctTimestampCount(long timestamp);

View File

@@ -25,7 +25,8 @@ import androidx.room.RoomDatabase;
/** A {@link RoomDatabase} for battery usage states history. */
@Database(
entities = {AppUsageEventEntity.class, BatteryEventEntity.class, BatteryState.class},
entities = {AppUsageEventEntity.class, BatteryEventEntity.class, BatteryState.class,
BatteryUsageSlotEntity.class},
version = 1)
public abstract class BatteryStateDatabase extends RoomDatabase {
private static final String TAG = "BatteryStateDatabase";
@@ -38,13 +39,15 @@ public abstract class BatteryStateDatabase extends RoomDatabase {
public abstract BatteryEventDao batteryEventDao();
/** Provides DAO for battery state table. */
public abstract BatteryStateDao batteryStateDao();
/** Provides DAO for battery usage slot table. */
public abstract BatteryUsageSlotDao batteryUsageSlotDao();
/** Gets or creates an instance of {@link RoomDatabase}. */
public static BatteryStateDatabase getInstance(Context context) {
if (sBatteryStateDatabase == null) {
sBatteryStateDatabase =
Room.databaseBuilder(
context, BatteryStateDatabase.class, "battery-usage-db-v8")
context, BatteryStateDatabase.class, "battery-usage-db-v9")
// Allows accessing data in the main thread for dumping bugreport.
.allowMainThreadQueries()
.fallbackToDestructiveMigration()

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 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.db;
import android.database.Cursor;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import java.util.List;
/** Data access object for accessing {@link BatteryUsageSlotEntity} in the database. */
@Dao
public interface BatteryUsageSlotDao {
/** Inserts a {@link BatteryUsageSlotEntity} data into the database. */
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(BatteryUsageSlotEntity event);
/** Gets all recorded data. */
@Query("SELECT * FROM BatteryUsageSlotEntity ORDER BY timestamp ASC")
List<BatteryUsageSlotEntity> getAll();
/** Gets the {@link Cursor} of all recorded data after a specific timestamp. */
@Query("SELECT * FROM BatteryUsageSlotEntity WHERE timestamp >= :timestamp"
+ " ORDER BY timestamp ASC")
Cursor getAllAfter(long timestamp);
/** Deletes all recorded data before a specific timestamp. */
@Query("DELETE FROM BatteryUsageSlotEntity WHERE timestamp <= :timestamp")
void clearAllBefore(long timestamp);
/** Clears all recorded data in the database. */
@Query("DELETE FROM BatteryUsageSlotEntity")
void clearAll();
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2023 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.db;
import android.content.ContentValues;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Locale;
/** A {@link Entity} class to save battery usage slot into database. */
@Entity
public class BatteryUsageSlotEntity {
/** Keys for accessing {@link ContentValues}. */
public static final String KEY_TIMESTAMP = "timestamp";
public static final String KEY_BATTERY_USAGE_SLOT = "batteryUsageSlot";
@PrimaryKey(autoGenerate = true)
private long mId;
public final long timestamp;
public final String batteryUsageSlot;
public BatteryUsageSlotEntity(final long timestamp, final String batteryUsageSlot) {
this.timestamp = timestamp;
this.batteryUsageSlot = batteryUsageSlot;
}
/** Sets the auto-generated content ID. */
public void setId(long id) {
this.mId = id;
}
/** Gets the auto-generated content ID. */
public long getId() {
return mId;
}
@Override
public String toString() {
final String recordAtDateTime = ConvertUtils.utcToLocalTimeForLogging(timestamp);
final StringBuilder builder = new StringBuilder()
.append("\nBatteryUsageSlot{")
.append(String.format(Locale.US, "\n\ttimestamp=%s|batteryUsageSlot=%s",
recordAtDateTime, batteryUsageSlot))
.append("\n}");
return builder.toString();
}
/** Creates new {@link BatteryUsageSlotEntity} from {@link ContentValues}. */
public static BatteryUsageSlotEntity create(ContentValues contentValues) {
Builder builder = BatteryUsageSlotEntity.newBuilder();
if (contentValues.containsKey(KEY_TIMESTAMP)) {
builder.setTimestamp(contentValues.getAsLong(KEY_TIMESTAMP));
}
if (contentValues.containsKey(KEY_BATTERY_USAGE_SLOT)) {
builder.setBatteryUsageSlot(contentValues.getAsString(KEY_BATTERY_USAGE_SLOT));
}
return builder.build();
}
/** Creates a new {@link Builder} instance. */
public static Builder newBuilder() {
return new Builder();
}
/** A convenience builder class to improve readability. */
public static class Builder {
private long mTimestamp;
private String mBatteryUsageSlot;
/** Sets the timestamp. */
@CanIgnoreReturnValue
public Builder setTimestamp(final long timestamp) {
mTimestamp = timestamp;
return this;
}
/** Sets the battery usage slot. */
@CanIgnoreReturnValue
public Builder setBatteryUsageSlot(final String batteryUsageSlot) {
mBatteryUsageSlot = batteryUsageSlot;
return this;
}
/** Builds the {@link BatteryUsageSlotEntity}. */
public BatteryUsageSlotEntity build() {
return new BatteryUsageSlotEntity(mTimestamp, mBatteryUsageSlot);
}
private Builder() {}
}
}

View File

@@ -23,6 +23,14 @@ java_library {
srcs: ["battery_event.proto"],
}
java_library {
name: "battery-usage-slot-protos-lite",
proto: {
type: "lite",
},
srcs: ["battery_usage_slot.proto"],
}
java_library {
name: "fuelgauge-usage-state-protos-lite",
proto: {

View File

@@ -8,6 +8,8 @@ enum BatteryEventType {
UNKNOWN_EVENT = 0;
POWER_CONNECTED = 1;
POWER_DISCONNECTED = 2;
FULL_CHARGED = 3;
EVEN_HOUR = 4;
}
message BatteryEvent {

View File

@@ -0,0 +1,32 @@
syntax = "proto2";
option java_multiple_files = true;
option java_package = "com.android.settings.fuelgauge.batteryusage";
option java_outer_classname = "BatteryUsageSlotProto";
message BatteryUsageSlot {
optional int64 start_timestamp = 1;
optional int64 end_timestamp = 2;
optional int32 start_battery_level = 3;
optional int32 end_battery_level = 4;
optional int64 screen_on_time = 5;
repeated BatteryUsageDiff app_usage = 6;
repeated BatteryUsageDiff system_usage = 7;
}
message BatteryUsageDiff {
optional int64 uid = 1;
optional int64 user_id = 2;
optional string package_name = 3;
optional string label = 4;
optional string key = 5;
optional bool is_hidden = 6;
optional int32 component_id = 7;
optional int32 consumer_type = 8;
optional double consume_power = 9;
optional double foreground_usage_consume_power = 10;
optional double background_usage_consume_power = 11;
optional int64 foreground_usage_time = 12;
optional int64 background_usage_time = 13;
optional int64 screen_on_time = 14;
}