From 50da7feeb9e0226a7f49a5a296e43613eacc4771 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Fri, 14 Jul 2023 20:57:49 +0800 Subject: [PATCH] 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 --- Android.bp | 1 + .../fuelgauge/AdvancedPowerUsageDetail.java | 8 +- .../batteryusage/AppUsageDataLoader.java | 83 ---- .../BatteryChartPreferenceController.java | 34 +- .../batteryusage/BatteryChartView.java | 5 +- .../batteryusage/BatteryDiffData.java | 69 ++- .../batteryusage/BatteryDiffEntry.java | 240 ++++------ .../batteryusage/BatteryHistEntry.java | 15 - .../batteryusage/BatteryHistoryLoader.java | 45 -- .../BatteryHistoryPreference.java | 17 - .../batteryusage/BatteryLevelData.java | 113 ++++- .../BatteryUsageBreakdownController.java | 12 +- .../BatteryUsageContentProvider.java | 193 +++++--- .../batteryusage/BatteryUsageDataLoader.java | 76 +++- .../fuelgauge/batteryusage/ConvertUtils.java | 179 +++++++- .../batteryusage/DataProcessManager.java | 272 +++++++---- .../fuelgauge/batteryusage/DataProcessor.java | 392 ++++++++-------- .../fuelgauge/batteryusage/DatabaseUtils.java | 301 ++++++++++-- .../batteryusage/PeriodicJobReceiver.java | 1 - .../batteryusage/PowerUsageAdvanced.java | 91 ++-- .../batteryusage/PowerUsageBase.java | 15 +- .../batteryusage/PowerUsageSummary.java | 13 - .../batteryusage/db/BatteryEventDao.java | 11 +- .../batteryusage/db/BatteryStateDao.java | 14 +- .../batteryusage/db/BatteryStateDatabase.java | 7 +- .../batteryusage/db/BatteryUsageSlotDao.java | 51 +++ .../db/BatteryUsageSlotEntity.java | 112 +++++ .../settings/fuelgauge/protos/Android.bp | 8 + .../fuelgauge/protos/battery_event.proto | 2 + .../fuelgauge/protos/battery_usage_slot.proto | 32 ++ .../batteryusage/AppUsageDataLoaderTest.java | 102 ----- .../BatteryChartPreferenceControllerTest.java | 100 ++-- .../batteryusage/BatteryDiffDataTest.java | 11 +- .../batteryusage/BatteryDiffEntryTest.java | 72 ++- .../batteryusage/BatteryHistEntryTest.java | 40 +- .../BatteryHistoryPreferenceTest.java | 4 - .../batteryusage/BatteryLevelDataTest.java | 214 +++++++++ .../BatteryUsageBreakdownControllerTest.java | 50 +- .../BatteryUsageContentProviderTest.java | 187 ++++++-- .../BatteryUsageDataLoaderTest.java | 55 ++- .../batteryusage/ConvertUtilsTest.java | 64 ++- .../batteryusage/DataProcessManagerTest.java | 72 ++- .../batteryusage/DataProcessorTest.java | 428 +++++++----------- .../batteryusage/DatabaseUtilsTest.java | 43 +- .../batteryusage/PowerUsageBaseTest.java | 5 - .../batteryusage/db/BatteryEventDaoTest.java | 82 +++- .../batteryusage/db/BatteryStateDaoTest.java | 61 +-- .../db/BatteryUsageSlotDaoTest.java | 110 +++++ .../db/BatteryUsageSlotEntityTest.java | 44 ++ 49 files changed, 2713 insertions(+), 1443 deletions(-) delete mode 100644 src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java delete mode 100644 src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java create mode 100644 src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java create mode 100644 src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntity.java create mode 100644 src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto delete mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDaoTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntityTest.java diff --git a/Android.bp b/Android.bp index 71e0542f793..31895db32b1 100644 --- a/Android.bp +++ b/Android.bp @@ -83,6 +83,7 @@ android_library { "net-utils-framework-common", "app-usage-event-protos-lite", "battery-event-protos-lite", + "battery-usage-slot-protos-lite", "power-anomaly-event-protos-lite", "settings-contextual-card-protos-lite", "settings-log-bridge-protos-lite", diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 41ead68b623..d38dede6847 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -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); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java deleted file mode 100644 index c336fcdfc65..00000000000 --- a/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java +++ /dev/null @@ -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> sFakeAppUsageEventsSupplier; - @VisibleForTesting - static Supplier> 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 appUsageEvents = - sFakeAppUsageEventsSupplier != null - ? sFakeAppUsageEventsSupplier.get() - : DataProcessor.getAppUsageEvents(context); - if (appUsageEvents == null) { - Log.w(TAG, "loadAppUsageData() returns null"); - return; - } - final List 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); - } - } -} diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 3231e55ccee..5b965e3998d 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -125,7 +125,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll Map> 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> 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> 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; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java index 891e5e0b0ec..086f56c03fe 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java @@ -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( diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java index 47ae5685837..53861e38d9d 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java @@ -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 mAppEntries; private final List 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 appDiffEntries, final @NonNull List systemDiffEntries, final @NonNull Set systemAppsPackageNames, final @NonNull Set 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 getAppDiffEntryList() { + List getAppDiffEntryList() { return mAppEntries; } - public List getSystemDiffEntryList() { + List 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 systemAppsUids, final @NonNull List appEntries) { final List systemAppsAllowlist = featureProvider.getSystemAppsAllowlist(); - BatteryDiffEntry.SystemAppsBatteryDiffEntry systemAppsDiffEntry = null; + BatteryDiffEntry systemAppsDiffEntry = null; final Iterator 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 othersSystemComponentSet = featureProvider.getOthersSystemComponentSet(); final Set othersCustomComponentNameSet = featureProvider.getOthersCustomComponentNameSet(); - BatteryDiffEntry.OthersBatteryDiffEntry othersDiffEntry = null; + BatteryDiffEntry othersDiffEntry = null; final Iterator 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 systemAppsAllowlist, final @NonNull Set systemAppsPackageNames, final @NonNull Set 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); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java index 2ed91965b21..b284ea5beb4 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java @@ -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 sResourceCache = new HashMap<>(); // Whether a specific item is valid to launch restriction page? @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final Map sValidForRestriction = new HashMap<>(); - + static final Map sValidForRestriction = new HashMap<>(); /** A comparator for {@link BatteryDiffEntry} based on the sorting key. */ - public static final Comparator COMPARATOR = + static final Comparator 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> 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 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; - } - } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java index 827f0fccb4e..6f785668d02 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java @@ -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) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java deleted file mode 100644 index 9a0e410b3c9..00000000000 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java +++ /dev/null @@ -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>> { - private static final String TAG = "BatteryHistoryLoader"; - - private final Context mContext; - - public BatteryHistoryLoader(Context context) { - super(context); - mContext = context; - } - - @Override - protected void onDiscardResult(Map> result) { - } - - @Override - public Map> loadInBackground() { - return DatabaseUtils.getHistoryMapSinceLastFullCharge(mContext, Calendar.getInstance()); - } -} diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java index c78b3c7e78f..d64bf34fc1e 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java @@ -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); diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java index 4ff9eeba9b2..53ebbd90595 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java @@ -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 mLevels; public PeriodBatteryLevelData( - @NonNull List timestamps, @NonNull List levels) { - Preconditions.checkArgument(timestamps.size() == levels.size(), - /* errorMessage= */ "Timestamp: " + timestamps.size() + ", Level: " - + levels.size()); + @NonNull Map batteryLevelMap, + @NonNull List 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 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 mHourlyBatteryLevelsPerDay; - public BatteryLevelData( - @NonNull PeriodBatteryLevelData dailyBatteryLevels, - @NonNull List 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 batteryLevelMap) { + final int mapSize = batteryLevelMap.size(); + Preconditions.checkArgument(mapSize >= MIN_SIZE, "batteryLevelMap size:" + mapSize); + + final List timestampList = new ArrayList<>(batteryLevelMap.keySet()); + Collections.sort(timestampList); + final List dailyTimestamps = getDailyTimestamps(timestampList); + final List> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps); + + mDailyBatteryLevels = new PeriodBatteryLevelData(batteryLevelMap, dailyTimestamps); + mHourlyBatteryLevelsPerDay = new ArrayList<>(hourlyTimestamps.size()); + for (List 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 batteryLevelRecordEvents) { + final Map batteryLevelMap = new ArrayMap<>(batteryLevelRecordEvents.size()); + for (BatteryEvent event : batteryLevelRecordEvents) { + batteryLevelMap.put(event.getTimestamp(), event.getBatteryLevel()); + } + if (existingBatteryLevelData != null) { + List 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 getDailyTimestamps(final List timestampList) { + Preconditions.checkArgument( + timestampList.size() >= MIN_SIZE, "timestampList size:" + timestampList.size()); + final List 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> getHourlyTimestamps(final List dailyTimestamps) { + final List> hourlyTimestamps = new ArrayList<>(); + for (int dailyIndex = 0; dailyIndex < dailyTimestamps.size() - 1; dailyIndex++) { + final List 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; + } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java index b262dee93ab..8aa31e2e0a6 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java @@ -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; diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java index 1b2d4cd0406..edba7c445ae 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java @@ -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 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 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 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 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 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 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; } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java index ae86095dc29..48a39f4dd2b 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java @@ -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> sFakeBatteryEntryListSupplier; + @VisibleForTesting + static Supplier> sFakeAppUsageEventsSupplier; + @VisibleForTesting + static Supplier> 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 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 appUsageEvents = + sFakeAppUsageEventsSupplier != null + ? sFakeAppUsageEventsSupplier.get() + : DataProcessor.getAppUsageEvents(context); + if (appUsageEvents == null) { + Log.w(TAG, "loadAppUsageData() returns null"); + return; + } + final List 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); } } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java index 5fc4ad52b83..ec0d01a4a85 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java @@ -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} */ + public static List convertToBatteryEventList( + final BatteryLevelData batteryLevelData) { + final List batteryEventList = new ArrayList<>(); + final List 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} to {@link List} */ + public static List convertToBatteryUsageSlotList( + final Map batteryDiffDataMap) { + List 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 systemAppsPackageNames, + @NonNull final Set systemAppsUids) { + final List appDiffEntries = new ArrayList<>(); + final List 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, diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java index 1c851fd4ca9..1a226fd24ac 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java @@ -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 POWER_CONNECTION_EVENTS = + List.of(BatteryEventType.POWER_CONNECTED, BatteryEventType.POWER_DISCONNECTED); + private static final List BATTERY_LEVEL_RECORD_EVENTS = + List.of(BatteryEventType.FULL_CHARGED, BatteryEventType.EVEN_HOUR); - private final Handler mHandler; - private final DataProcessor.UsageMapAsyncResponse mCallbackFunction; - private final List mAppUsageEventList = new ArrayList<>(); - private final List mBatteryEventList = new ArrayList<>(); - - private Context mContext; - private UserManager mUserManager; - private List mHourlyBatteryLevelsPerDay; - private Map> mBatteryHistoryMap; + // For testing only. + @VisibleForTesting + static Map> 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 mAppUsageEventList = new ArrayList<>(); + private final List mBatteryEventList = new ArrayList<>(); + private final List mBatteryUsageSlotList = new ArrayList<>(); + private final List mHourlyBatteryLevelsPerDay; + private final Map> 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 mSystemAppsPackageNames = null; + private Set mSystemAppsUids = null; /** * The indexed {@link AppUsagePeriod} list data for each corresponding time slot. @@ -100,6 +110,15 @@ public class DataProcessManager { private Map>>>> 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 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 hourlyBatteryLevelsPerDay, @NonNull final Map> 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>() { @Override @@ -323,7 +357,7 @@ public class DataProcessManager { }.execute(); } - private void loadBatteryEventList() { + private void loadPowerConnectionBatteryEventList() { new AsyncTask>() { @Override protected List doInBackground(Void... voids) { @@ -331,8 +365,10 @@ public class DataProcessManager { // Loads the battery event data from the database. final List 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>>() { + private void loadBatteryUsageSlotList() { + new AsyncTask>() { @Override - protected Map> doInBackground(Void... voids) { + protected List doInBackground(Void... voids) { final long startTime = System.currentTimeMillis(); - final Map> 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 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> batteryUsageMap) { - // Set the unused variables to null. - mContext = null; + protected void onPostExecute(final List 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>() { + @Override + protected Map doInBackground(Void... voids) { + final long startTime = System.currentTimeMillis(); + final Map 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 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>>() { + private synchronized void generateFinalDataAndApplyCallback() { + new AsyncTask>() { @Override - protected Map> doInBackground(Void... voids) { + protected Map doInBackground(Void... voids) { final long startTime = System.currentTimeMillis(); - final Map> 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 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> batteryUsageMap) { - // Set the unused variables to null. - mContext = null; - mHourlyBatteryLevelsPerDay = null; - mBatteryHistoryMap = null; + protected void onPostExecute(final Map 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 getSystemAppsPackageNames() { + if (mSystemAppsPackageNames == null) { + mSystemAppsPackageNames = DataProcessor.getSystemAppsPackageNames(mContext); + } + return mSystemAppsPackageNames; + } + + private synchronized Set 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> 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 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> 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> 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; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java index 620ae31c961..925cb3abbf0 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java @@ -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 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 { * * *

The structure is consistent with the battery usage map returned by - * {@code getBatteryUsageMap}.

+ * {@code generateBatteryUsageMap}.

* *

{@code Long} stands for the userId.

*

{@code String} stands for the packageName.

@@ -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> getHistoryMapWithExpectedTimestamps( @@ -431,28 +438,23 @@ public final class DataProcessor { static BatteryLevelData getLevelDataThroughProcessedHistoryMap( Context context, final Map> processedBatteryHistoryMap) { - final List timestampList = new ArrayList<>(processedBatteryHistoryMap.keySet()); - Collections.sort(timestampList); - final List 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> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps); - final BatteryLevelData.PeriodBatteryLevelData dailyLevelData = - getPeriodBatteryLevelData(context, processedBatteryHistoryMap, dailyTimestamps); - final List hourlyLevelData = - getHourlyPeriodBatteryLevelData( - context, processedBatteryHistoryMap, hourlyTimestamps); - return new BatteryLevelData(dailyLevelData, hourlyLevelData); + Map 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 getTimestampSlots(final List 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 getDailyTimestamps(final List timestampList) { - final List 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> getHourlyTimestamps(final List dailyTimestamps) { - final List> hourlyTimestamps = new ArrayList<>(); - if (dailyTimestamps.size() < MIN_DAILY_DATA_SIZE) { - return hourlyTimestamps; - } - - for (int dailyIndex = 0; dailyIndex < dailyTimestamps.size() - 1; dailyIndex++) { - final List 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 entryList) { if (entryList == null) { @@ -560,34 +512,102 @@ public final class DataProcessor { return results; } + static Map getBatteryDiffDataMap( + Context context, + final List hourlyBatteryLevelsPerDay, + final Map> batteryHistoryMap, + final Map>>>> + appUsagePeriodMap, + final @NonNull Set systemAppsPackageNames, + final @NonNull Set systemAppsUids) { + final Map 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 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> 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. * *

There could be 2 cases of the returned value:

*
    - *
  • null: empty or invalid data.
  • - *
  • non-null: must be a 2d map and composed by 3 parts:
  • + *
  • null: empty or invalid data.
  • + *
  • 1 part: if batteryLevelData is null.
  • + *

    [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]

    + *
  • 3 parts: if batteryLevelData is not null.
  • *

    1 - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]

    *

    2 - [0][SELECTED_INDEX_ALL] ~ [maxDailyIndex][SELECTED_INDEX_ALL]

    *

    3 - [0][0] ~ [maxDailyIndex][maxHourlyIndex]

    *
*/ - @Nullable - static Map> getBatteryUsageMap( + static Map> generateBatteryUsageMap( final Context context, - final List hourlyBatteryLevelsPerDay, - final Map> batteryHistoryMap, - final Map>>>> - appUsagePeriodMap) { - if (batteryHistoryMap.isEmpty()) { - return null; - } + final Map batteryDiffDataMap, + final @Nullable BatteryLevelData batteryLevelData) { final Map> resultMap = new ArrayMap<>(); - final Set systemAppsPackageNames = getSystemAppsPackageNames(context); - final Set systemAppsUids = getSystemAppsUids(context); + if (batteryLevelData == null) { + Preconditions.checkArgument(batteryDiffDataMap.size() == 1); + BatteryDiffData batteryDiffData = batteryDiffDataMap.values().stream().toList().get(0); + final Map allUsageMap = new ArrayMap<>(); + allUsageMap.put(SELECTED_INDEX_ALL, batteryDiffData); + resultMap.put(SELECTED_INDEX_ALL, allUsageMap); + return resultMap; + } + List 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 batteryHistEntryList) { + final long startTimestamp, + final List batteryHistEntryList, + final @NonNull Set systemAppsPackageNames, + final @NonNull Set 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 systemAppsPackageNames = getSystemAppsPackageNames(context); - final Set 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> getBatteryUsageMapFromStatsService( - final Context context) { - final Map> resultMap = new ArrayMap<>(); - final Map 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 getBatteryDiffDataMapFromStatsService( + final Context context, final long startTimestamp, + @NonNull final Set systemAppsPackageNames, + @NonNull final Set systemAppsUids) { + Map 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 getSystemAppsPackageNames(Context context) { + return sTestSystemAppsPackageNames != null ? sTestSystemAppsPackageNames + : AppListRepositoryUtil.getSystemPackageNames(context, context.getUserId()); + } + + static Set getSystemAppsUids(Context context) { + Set 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 getHourlyPeriodBatteryLevelData( - Context context, - final Map> processedBatteryHistoryMap, - final List> timestamps) { - final List levelData = new ArrayList<>(); - timestamps.forEach( - timestampList -> levelData.add( - getPeriodBatteryLevelData( - context, processedBatteryHistoryMap, timestampList))); - return levelData; - } - - private static BatteryLevelData.PeriodBatteryLevelData getPeriodBatteryLevelData( - Context context, - final Map> processedBatteryHistoryMap, - final List timestamps) { - final List 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> 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 systemAppsPackageNames, - final Set systemAppsUids, final List hourlyBatteryLevelsPerDay, - final Map> batteryHistoryMap, - final Map>>>> - appUsagePeriodMap, + final Map batteryDiffDataMap, final Map> resultMap) { - final int currentUserId = context.getUserId(); - final UserHandle userHandle = - Utils.getManagedProfile(context.getSystemService(UserManager.class)); - final int workProfileUserId = - userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE; // Each time slot usage diff data = // 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> 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 appEntries = new ArrayList<>(); final List 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 getSystemAppsPackageNames(Context context) { - return sTestSystemAppsPackageNames != null ? sTestSystemAppsPackageNames - : AppListRepositoryUtil.getSystemPackageNames(context, context.getUserId()); - } - - private static Set getSystemAppsUids(Context context) { - Set 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(); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java index b54563bd566..465afbe8c59 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java @@ -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 getBatteryEvents( Context context, final Calendar calendar, - final long rawStartTimestamp) { + final long rawStartTimestamp, + final List 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 batteryEventList = @@ -211,13 +233,82 @@ public final class DatabaseUtils { return batteryEventList; } - /** Long: for timestamp and String: for BatteryHistEntry.getKey() */ - public static Map> getHistoryMapSinceLastFullCharge( - Context context, Calendar calendar) { + /** + * Returns the battery usage slot data after {@code rawStartTimestamp} in battery event table. + */ + public static List 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 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> 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> 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> + 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> 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 sendBatteryEventData( + final Context context, final List batteryEventList) { + final long startTime = System.currentTimeMillis(); + // Creates the ContentValues list to insert them into provider. + final List 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 sendBatteryUsageSlotData( + final Context context, final List batteryUsageSlotList) { + final long startTime = System.currentTimeMillis(); + // Creates the ContentValues list to insert them into provider. + final List 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 sendBatteryEntryData( final Context context, + final long snapshotTimestamp, final List 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 loadBatteryUsageSlotsFromContentProvider( + Context context, Uri batteryUsageSlotUri) { + final List 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> 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; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java index 2bd04669b31..2371a19f85c 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java @@ -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); diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java index 9a8680e2244..e4f8b39540d 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java @@ -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> mBatteryHistoryMap; - @VisibleForTesting - final BatteryHistoryLoaderCallbacks mBatteryHistoryLoaderCallbacks = - new BatteryHistoryLoaderCallbacks(); + final BatteryLevelDataLoaderCallbacks mBatteryLevelDataLoaderCallbacks = + new BatteryLevelDataLoaderCallbacks(); private boolean mIsChartDataLoaded = false; + private long mResumeTimestamp; private BatteryChartPreferenceController mBatteryChartPreferenceController; + private Optional 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 batteryDiffDataMap) { + if (mBatteryLevelData != null && mBatteryChartPreferenceController != null) { + Map> 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>> { - private int mRefreshType; - + private class BatteryLevelDataLoaderCallbacks + implements LoaderManager.LoaderCallbacks { @Override - @NonNull - public Loader>> onCreateLoader( - int id, Bundle bundle) { - mRefreshType = bundle.getInt(KEY_REFRESH_TYPE); - return new BatteryHistoryLoader(getContext()); + public Loader onCreateLoader(int id, Bundle bundle) { + return new AsyncLoaderCompat(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>> loader, - Map> batteryHistoryMap) { - mBatteryHistoryMap = batteryHistoryMap; - PowerUsageAdvanced.this.onLoadFinished(mRefreshType); + public void onLoadFinished(Loader loader, + BatteryLevelData batteryLevelData) { + PowerUsageAdvanced.this.onBatteryLevelDataUpdate(batteryLevelData); } @Override - public void onLoaderReset(Loader>> loader) { + public void onLoaderReset(Loader loader) { } } - } diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBase.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBase.java index ed3a9215352..22856b60d86 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBase.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBase.java @@ -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 { private int mRefreshType; diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java index 0bec4908d10..40fd3f4eaf1 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java @@ -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(); diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java index a638d094519..0a6de712e2d 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java @@ -36,9 +36,16 @@ public interface BatteryEventDao { @Query("SELECT * FROM BatteryEventEntity ORDER BY timestamp DESC") List 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 batteryEventTypes); /** Deletes all recorded data before a specific timestamp. */ @Query("DELETE FROM BatteryEventEntity WHERE timestamp <= :timestamp") diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java index 6d2ab8d3c26..520c6bed484 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java @@ -37,16 +37,18 @@ public interface BatteryStateDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List 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 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); diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java index 466a7ca4740..28a0012234a 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java @@ -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() diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java new file mode 100644 index 00000000000..a695f6ab75e --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java @@ -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 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(); +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntity.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntity.java new file mode 100644 index 00000000000..c2d5631aae8 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntity.java @@ -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() {} + } +} diff --git a/src/com/android/settings/fuelgauge/protos/Android.bp b/src/com/android/settings/fuelgauge/protos/Android.bp index 1f3cdd98b02..531bdc32293 100644 --- a/src/com/android/settings/fuelgauge/protos/Android.bp +++ b/src/com/android/settings/fuelgauge/protos/Android.bp @@ -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: { diff --git a/src/com/android/settings/fuelgauge/protos/battery_event.proto b/src/com/android/settings/fuelgauge/protos/battery_event.proto index 80ccb3b24f4..58ab3be2409 100644 --- a/src/com/android/settings/fuelgauge/protos/battery_event.proto +++ b/src/com/android/settings/fuelgauge/protos/battery_event.proto @@ -8,6 +8,8 @@ enum BatteryEventType { UNKNOWN_EVENT = 0; POWER_CONNECTED = 1; POWER_DISCONNECTED = 2; + FULL_CHARGED = 3; + EVEN_HOUR = 4; } message BatteryEvent { diff --git a/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto b/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto new file mode 100644 index 00000000000..e3b604ba0a7 --- /dev/null +++ b/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto @@ -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; +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java deleted file mode 100644 index 4b250a3dd12..00000000000 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.fuelgauge.batteryusage; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.os.UserManager; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -public final class AppUsageDataLoaderTest { - private Context mContext; - @Mock - private ContentResolver mMockContentResolver; - @Mock - private UserManager mUserManager; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - doReturn(mContext).when(mContext).getApplicationContext(); - doReturn(mMockContentResolver).when(mContext).getContentResolver(); - doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); - doReturn(new Intent()).when(mContext).registerReceiver(any(), any()); - } - - @Test - public void loadAppUsageData_withData_insertFakeDataIntoProvider() { - final List AppUsageEventList = new ArrayList<>(); - final AppUsageEvent appUsageEvent = AppUsageEvent.newBuilder().setUid(0).build(); - AppUsageEventList.add(appUsageEvent); - AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>(); - AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> AppUsageEventList; - - AppUsageDataLoader.loadAppUsageData(mContext); - - verify(mMockContentResolver).bulkInsert(any(), any()); - verify(mMockContentResolver).notifyChange(any(), any()); - } - - @Test - public void loadAppUsageData_nullAppUsageEvents_notInsertDataIntoProvider() { - AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> null; - - AppUsageDataLoader.loadAppUsageData(mContext); - - verifyNoMoreInteractions(mMockContentResolver); - } - - @Test - public void loadAppUsageData_nullUsageEventsList_notInsertDataIntoProvider() { - AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>(); - AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> null; - - AppUsageDataLoader.loadAppUsageData(mContext); - - verifyNoMoreInteractions(mMockContentResolver); - } - - @Test - public void loadAppUsageData_emptyUsageEventsList_notInsertDataIntoProvider() { - AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>(); - AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> new ArrayList<>(); - - AppUsageDataLoader.loadAppUsageData(mContext); - - verifyNoMoreInteractions(mMockContentResolver); - } -} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java index e1c193cf004..a54d4c174d7 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java @@ -16,6 +16,8 @@ package com.android.settings.fuelgauge.batteryusage; +import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_ALL; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyFloat; @@ -30,7 +32,6 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -39,6 +40,7 @@ import android.os.Bundle; import android.os.LocaleList; import android.os.UserManager; import android.text.format.DateUtils; +import android.util.ArrayMap; import android.view.View; import android.view.ViewPropertyAnimator; import android.widget.LinearLayout; @@ -54,7 +56,6 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -112,6 +113,7 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mPrefContext = mContext; mBatteryChartPreferenceController.mDailyChartView = mDailyChartView; mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView; + BatteryDiffEntry.clearCache(); // Adds fake testing data. BatteryDiffEntry.sResourceCache.put( "fakeBatteryDiffEntryKey", @@ -144,7 +146,7 @@ public final class BatteryChartPreferenceControllerTest { reset(mHourlyChartView); setupHourlyChartViewAnimationMock(); - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE); // Ignore fast refresh ui from the data processor callback. @@ -176,16 +178,18 @@ public final class BatteryChartPreferenceControllerTest { BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS, mBatteryChartPreferenceController.mDailyChartLabelTextGenerator); - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE); verify(mViewPropertyAnimator, atLeastOnce()).alpha(0f); - verify(mDailyChartView).setViewModel(expectedDailyViewModel); + verify(mDailyChartView, atLeastOnce()).setViewModel(expectedDailyViewModel); reset(mDailyChartView); reset(mHourlyChartView); setupHourlyChartViewAnimationMock(); doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams(); + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.refreshUi(); verify(mDailyChartView).setVisibility(View.VISIBLE); @@ -245,8 +249,7 @@ public final class BatteryChartPreferenceControllerTest { setupHourlyChartViewAnimationMock(); doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams(); mBatteryChartPreferenceController.mDailyChartIndex = 2; - mBatteryChartPreferenceController.mHourlyChartIndex = - BatteryChartViewModel.SELECTED_INDEX_ALL; + mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; mBatteryChartPreferenceController.refreshUi(); verify(mDailyChartView).setVisibility(View.VISIBLE); verify(mViewPropertyAnimator, atLeastOnce()).alpha(1f); @@ -272,13 +275,15 @@ public final class BatteryChartPreferenceControllerTest { @Test public void refreshUi_normalCase_returnTrue() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); + mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue(); } @Test public void refreshUi_batteryIndexedMapIsNull_returnTrue() { - mBatteryChartPreferenceController.setBatteryHistoryMap(null); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(null); + mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue(); } @@ -296,38 +301,34 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_selectAllDaysAllHours_returnNull() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60)); - mBatteryChartPreferenceController.mDailyChartIndex = - BatteryChartViewModel.SELECTED_INDEX_ALL; - mBatteryChartPreferenceController.mHourlyChartIndex = - BatteryChartViewModel.SELECTED_INDEX_ALL; + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + mBatteryChartPreferenceController.mDailyChartIndex = SELECTED_INDEX_ALL; + mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null); } @Test public void selectedSlotText_onlyOneDayDataSelectAllHours_returnNull() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); mBatteryChartPreferenceController.mDailyChartIndex = 0; - mBatteryChartPreferenceController.mHourlyChartIndex = - BatteryChartViewModel.SELECTED_INDEX_ALL; + mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null); } @Test public void selectedSlotText_selectADayAllHours_onlyDayText() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); mBatteryChartPreferenceController.mDailyChartIndex = 1; - mBatteryChartPreferenceController.mHourlyChartIndex = - BatteryChartViewModel.SELECTED_INDEX_ALL; + mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo("Sunday"); } @Test public void selectedSlotText_onlyOneDayDataSelectAnHour_onlyHourText() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = 2; @@ -337,7 +338,7 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_SelectADayAnHour_dayAndHourText() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); mBatteryChartPreferenceController.mDailyChartIndex = 1; mBatteryChartPreferenceController.mHourlyChartIndex = 8; @@ -347,7 +348,7 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_selectFirstSlot_withMinuteText() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = 0; @@ -357,7 +358,7 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_selectLastSlot_withNowText() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = 3; @@ -367,7 +368,7 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_selectOnlySlot_withMinuteAndNowText() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(1)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(1)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = 0; @@ -388,7 +389,7 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mHourlyChartIndex = -1; mBatteryChartPreferenceController.onCreate(bundle); - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(25)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(25)); assertThat(mBatteryChartPreferenceController.mDailyChartIndex) .isEqualTo(expectedDailyIndex); @@ -398,9 +399,7 @@ public final class BatteryChartPreferenceControllerTest { @Test public void getTotalHours_getExpectedResult() { - Map> batteryHistoryMap = createBatteryHistoryMap(60); - BatteryLevelData batteryLevelData = - DataProcessManager.getBatteryLevelData(mContext, null, batteryHistoryMap, null); + BatteryLevelData batteryLevelData = createBatteryLevelData(60); final int totalHour = BatteryChartPreferenceController.getTotalHours(batteryLevelData); @@ -413,37 +412,26 @@ public final class BatteryChartPreferenceControllerTest { return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS; } - private static Map> createBatteryHistoryMap( - int numOfHours) { - final Map> batteryHistoryMap = new HashMap<>(); - for (int index = 0; index < numOfHours; index++) { - final ContentValues values = new ContentValues(); - final DeviceBatteryState deviceBatteryState = - DeviceBatteryState - .newBuilder() - .setBatteryLevel(100 - index) - .build(); - final BatteryInformation batteryInformation = - BatteryInformation - .newBuilder() - .setDeviceBatteryState(deviceBatteryState) - .setConsumePower(100 - index) - .build(); - values.put(BatteryHistEntry.KEY_BATTERY_INFORMATION, - ConvertUtils.convertBatteryInformationToString(batteryInformation)); - values.put(BatteryHistEntry.KEY_PACKAGE_NAME, "package" + index); - final BatteryHistEntry entry = new BatteryHistEntry(values); - final Map entryMap = new HashMap<>(); - entryMap.put("fake_entry_key" + index, entry); - long timestamp = generateTimestamp(index); + private static BatteryLevelData createBatteryLevelData(int numOfHours) { + Map batteryLevelMap = new ArrayMap<>(); + for (int index = 0; index < numOfHours; index += 2) { + final Integer level = 100 - index; + Long timestamp = generateTimestamp(index); if (index == 0) { timestamp += DateUtils.MINUTE_IN_MILLIS; + index--; } - batteryHistoryMap.put(timestamp, entryMap); + batteryLevelMap.put(timestamp, level); } - DataProcessor.sTestCurrentTimeMillis = - generateTimestamp(numOfHours - 1) + DateUtils.MINUTE_IN_MILLIS * 2; - return batteryHistoryMap; + long current = generateTimestamp(numOfHours - 1) + DateUtils.MINUTE_IN_MILLIS * 2; + batteryLevelMap.put(current, 66); + DataProcessor.sTestCurrentTimeMillis = current; + return new BatteryLevelData(batteryLevelMap); + } + + private static Map> getEmptyBatteryUsageMap() { + return Map.of(SELECTED_INDEX_ALL, Map.of(SELECTED_INDEX_ALL, new BatteryDiffData( + null, 0, 0, 0, 0, 0, List.of(), List.of(), Set.of(), Set.of(), false))); } private BatteryChartPreferenceController createController() { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java index 27539a58a54..d4bae2952de 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java @@ -147,6 +147,14 @@ public class BatteryDiffDataTest { /*foregroundUsageTimeInMs=*/ 0L, /*backgroundUsageTimeInMs=*/ 0L, isHidden); return new BatteryDiffEntry( context, + batteryHistEntry.mUid, + batteryHistEntry.mUserId, + batteryHistEntry.getKey(), + batteryHistEntry.mIsHidden, + batteryHistEntry.mDrainType, + batteryHistEntry.mPackageName, + batteryHistEntry.mAppLabel, + batteryHistEntry.mConsumerType, /*foregroundUsageTimeInMs=*/ 0, /*backgroundUsageTimeInMs=*/ 0, /*screenOnTimeInMs=*/ 0, @@ -154,8 +162,7 @@ public class BatteryDiffDataTest { /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0, - /*cachedUsageConsumePower=*/ 0, - batteryHistEntry); + /*cachedUsageConsumePower=*/ 0); } private static BatteryHistEntry createBatteryHistEntry( diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java index 655f1e41869..9bb4b73a658 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java @@ -95,6 +95,14 @@ public final class BatteryDiffEntryTest { final BatteryDiffEntry entry = new BatteryDiffEntry( mContext, + /*uid=*/ 0, + /*userId=*/ 0, + /*key=*/ "key", + /*isHidden=*/ false, + /*componentId=*/ -1, + /*legacyPackageName=*/ null, + /*legacyLabel=*/ null, + /*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10001L, /*backgroundUsageTimeInMs=*/ 20002L, /*screenOnTimeInMs=*/ 30003L, @@ -102,8 +110,7 @@ public final class BatteryDiffEntryTest { /*foregroundUsageConsumePower=*/ 10.0, /*foregroundServiceUsageConsumePower=*/ 10.0, /*backgroundUsageConsumePower=*/ 1.0, - /*cachedUsageConsumePower=*/ 1.0, - /*batteryHistEntry=*/ null); + /*cachedUsageConsumePower=*/ 1.0); entry.setTotalConsumePower(100.0); assertThat(entry.getPercentage()).isEqualTo(22.0); @@ -114,6 +121,14 @@ public final class BatteryDiffEntryTest { final BatteryDiffEntry entry = new BatteryDiffEntry( mContext, + /*uid=*/ 0, + /*userId=*/ 0, + /*key=*/ "key", + /*isHidden=*/ false, + /*componentId=*/ -1, + /*legacyPackageName=*/ null, + /*legacyLabel=*/ null, + /*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10001L, /*backgroundUsageTimeInMs=*/ 20002L, /*screenOnTimeInMs=*/ 30003L, @@ -121,8 +136,7 @@ public final class BatteryDiffEntryTest { /*foregroundUsageConsumePower=*/ 10.0, /*foregroundServiceUsageConsumePower=*/ 10.0, /*backgroundUsageConsumePower=*/ 1.0, - /*cachedUsageConsumePower=*/ 1.0, - /*batteryHistEntry=*/ null); + /*cachedUsageConsumePower=*/ 1.0); entry.setTotalConsumePower(0); assertThat(entry.getPercentage()).isEqualTo(0); @@ -133,7 +147,24 @@ public final class BatteryDiffEntryTest { final List entryList = new ArrayList<>(); // Generates fake testing data. BatteryDiffEntry systemAppsBatteryDiffEntry = - new BatteryDiffEntry.SystemAppsBatteryDiffEntry(mContext); + new BatteryDiffEntry( + mContext, + /*uid=*/ 0, + /*userId=*/ 0, + /*key=*/ BatteryDiffEntry.SYSTEM_APPS_KEY, + /*isHidden=*/ false, + /*componentId=*/ -1, + /*legacyPackageName=*/ null, + /*legacyLabel=*/ BatteryDiffEntry.SYSTEM_APPS_KEY, + /*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY, + /*foregroundUsageTimeInMs=*/ 0, + /*backgroundUsageTimeInMs=*/ 0, + /*screenOnTimeInMs=*/ 0, + /*consumePower=*/ 0, + /*foregroundUsageConsumePower=*/ 0, + /*foregroundServiceUsageConsumePower=*/ 0, + /*backgroundUsageConsumePower=*/ 0, + /*cachedUsageConsumePower=*/ 0); systemAppsBatteryDiffEntry.mConsumePower = 16; systemAppsBatteryDiffEntry.setTotalConsumePower(100); entryList.add(systemAppsBatteryDiffEntry); @@ -448,17 +479,16 @@ public final class BatteryDiffEntryTest { private BatteryDiffEntry createBatteryDiffEntry( int consumerType, long uid, boolean isHidden) { - final ContentValues values = getContentValuesWithType(consumerType); - final BatteryInformation batteryInformation = - BatteryInformation - .newBuilder() - .setIsHidden(isHidden) - .build(); - values.put(BatteryHistEntry.KEY_BATTERY_INFORMATION, - ConvertUtils.convertBatteryInformationToString(batteryInformation)); - values.put(BatteryHistEntry.KEY_UID, uid); return new BatteryDiffEntry( mContext, + /*uid=*/ uid, + /*userId=*/ 0, + /*key=*/ "key", + /*isHidden=*/ isHidden, + /*componentId=*/ -1, + /*legacyPackageName=*/ null, + /*legacyLabel=*/ null, + /*consumerType*/ consumerType, /*foregroundUsageTimeInMs=*/ 0, /*backgroundUsageTimeInMs=*/ 0, /*screenOnTimeInMs=*/ 0, @@ -466,14 +496,21 @@ public final class BatteryDiffEntryTest { /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0, - /*cachedUsageConsumePower=*/ 0, - new BatteryHistEntry(values)); + /*cachedUsageConsumePower=*/ 0); } private BatteryDiffEntry createBatteryDiffEntry( double consumePower, BatteryHistEntry batteryHistEntry) { final BatteryDiffEntry entry = new BatteryDiffEntry( mContext, + batteryHistEntry.mUid, + batteryHistEntry.mUserId, + batteryHistEntry.getKey(), + batteryHistEntry.mIsHidden, + batteryHistEntry.mDrainType, + batteryHistEntry.mPackageName, + batteryHistEntry.mAppLabel, + batteryHistEntry.mConsumerType, /*foregroundUsageTimeInMs=*/ 0, /*backgroundUsageTimeInMs=*/ 0, /*screenOnTimeInMs=*/ 0, @@ -481,8 +518,7 @@ public final class BatteryDiffEntryTest { /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0, - /*cachedUsageConsumePower=*/ 0, - batteryHistEntry); + /*cachedUsageConsumePower=*/ 0); entry.setTotalConsumePower(100.0); return entry; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java index 96677609241..609f2fc8d25 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java @@ -15,6 +15,10 @@ */ package com.android.settings.fuelgauge.batteryusage; +import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isSystemConsumer; +import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUidConsumer; +import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUserConsumer; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; @@ -147,32 +151,32 @@ public final class BatteryHistEntryTest { @Test public void testIsAppEntry_returnExpectedResult() { - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).isAppEntry()) - .isFalse(); - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).isAppEntry()) - .isFalse(); - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).isAppEntry()) - .isTrue(); + assertThat(isUidConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).mConsumerType)).isFalse(); + assertThat(isUidConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).mConsumerType)).isFalse(); + assertThat(isUidConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).mConsumerType)).isTrue(); } @Test public void testIsUserEntry_returnExpectedResult() { - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).isUserEntry()) - .isFalse(); - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).isUserEntry()) - .isTrue(); - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).isUserEntry()) - .isFalse(); + assertThat(isUserConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).mConsumerType)).isFalse(); + assertThat(isUserConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).mConsumerType)).isTrue(); + assertThat(isUserConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).mConsumerType)).isFalse(); } @Test public void testIsSystemEntry_returnExpectedResult() { - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).isSystemEntry()) - .isTrue(); - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).isSystemEntry()) - .isFalse(); - assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).isSystemEntry()) - .isFalse(); + assertThat(isSystemConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).mConsumerType)).isTrue(); + assertThat(isSystemConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).mConsumerType)).isFalse(); + assertThat(isSystemConsumer( + createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).mConsumerType)).isFalse(); } @Test diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreferenceTest.java index e14ead51bb7..9155c6607a9 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreferenceTest.java @@ -27,7 +27,6 @@ import android.widget.TextView; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; -import com.android.settings.fuelgauge.BatteryInfo; import org.junit.Before; import org.junit.Test; @@ -42,8 +41,6 @@ public class BatteryHistoryPreferenceTest { @Mock private PreferenceViewHolder mViewHolder; @Mock - private BatteryInfo mBatteryInfo; - @Mock private TextView mTextView; @Mock private BatteryChartView mDailyChartView; @@ -59,7 +56,6 @@ public class BatteryHistoryPreferenceTest { LayoutInflater.from(context).inflate(R.layout.battery_chart_graph, null); mBatteryHistoryPreference = new BatteryHistoryPreference(context, null); - mBatteryHistoryPreference.mBatteryInfo = mBatteryInfo; mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(itemView)); when(mViewHolder.findViewById(R.id.daily_battery_chart)).thenReturn(mDailyChartView); when(mViewHolder.findViewById(R.id.hourly_battery_chart)).thenReturn(mHourlyChartView); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java new file mode 100644 index 00000000000..13d60bb6c12 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java @@ -0,0 +1,214 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +@RunWith(RobolectricTestRunner.class) +public class BatteryLevelDataTest { + + @Before + public void setUp() { + TimeZone.setDefault(TimeZone.getTimeZone("GMT+8")); + } + + @Test + public void getDailyTimestamps_allDataInOneHour_returnExpectedList() { + // Timezone GMT+8 + final List timestamps = List.of( + 1640970006000L, // 2022-01-01 01:00:06 + 1640973608000L // 2022-01-01 01:00:08 + ); + + final List expectedTimestamps = List.of( + 1640970006000L, // 2022-01-01 01:00:06 + 1640973608000L // 2022-01-01 01:00:08 + ); + assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); + } + + @Test + public void getDailyTimestamps_OneHourDataPerDay_returnExpectedList() { + // Timezone GMT+8 + final List timestamps = List.of( + 1641049200000L, // 2022-01-01 23:00:00 + 1641052800000L, // 2022-01-02 00:00:00 + 1641056400000L // 2022-01-02 01:00:00 + ); + + final List expectedTimestamps = List.of( + 1641049200000L, // 2022-01-01 23:00:00 + 1641052800000L, // 2022-01-02 00:00:00 + 1641056400000L // 2022-01-02 01:00:00 + ); + assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); + } + + @Test + public void getDailyTimestamps_OneDayData_returnExpectedList() { + // Timezone GMT+8 + final List timestamps = List.of( + 1640966400000L, // 2022-01-01 00:00:00 + 1640970000000L, // 2022-01-01 01:00:00 + 1640973600000L, // 2022-01-01 02:00:00 + 1640977200000L, // 2022-01-01 03:00:00 + 1640980800000L // 2022-01-01 04:00:00 + ); + + final List expectedTimestamps = List.of( + 1640966400000L, // 2022-01-01 00:00:00 + 1640980800000L // 2022-01-01 04:00:00 + ); + assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); + } + + @Test + public void getDailyTimestamps_MultipleDaysData_returnExpectedList() { + // Timezone GMT+8 + final List timestamps = List.of( + 1641045600000L, // 2022-01-01 22:00:00 + 1641060000000L, // 2022-01-02 02:00:00 + 1641160800000L, // 2022-01-03 06:00:00 + 1641232800000L // 2022-01-04 02:00:00 + ); + + final List expectedTimestamps = List.of( + 1641045600000L, // 2022-01-01 22:00:00 + 1641052800000L, // 2022-01-02 00:00:00 + 1641139200000L, // 2022-01-03 00:00:00 + 1641225600000L, // 2022-01-04 00:00:00 + 1641232800000L // 2022-01-04 02:00:00 + ); + assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); + } + + @Test + public void getDailyTimestamps_FirstDayOneHourData_returnExpectedList() { + // Timezone GMT+8 + final List timestamps = List.of( + 1641049200000L, // 2022-01-01 23:00:00 + 1641060000000L, // 2022-01-02 02:00:00 + 1641160800000L, // 2022-01-03 06:00:00 + 1641254400000L // 2022-01-04 08:00:00 + ); + + final List expectedTimestamps = List.of( + 1641049200000L, // 2022-01-01 23:00:00 + 1641052800000L, // 2022-01-02 00:00:00 + 1641139200000L, // 2022-01-03 00:00:00 + 1641225600000L, // 2022-01-04 00:00:00 + 1641254400000L // 2022-01-04 08:00:00 + ); + assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); + } + + @Test + public void getDailyTimestamps_LastDayNoData_returnExpectedList() { + // Timezone GMT+8 + final List timestamps = List.of( + 1640988000000L, // 2022-01-01 06:00:00 + 1641060000000L, // 2022-01-02 02:00:00 + 1641160800000L, // 2022-01-03 06:00:00 + 1641225600000L // 2022-01-04 00:00:00 + ); + + final List expectedTimestamps = List.of( + 1640988000000L, // 2022-01-01 06:00:00 + 1641052800000L, // 2022-01-02 00:00:00 + 1641139200000L, // 2022-01-03 00:00:00 + 1641225600000L // 2022-01-04 00:00:00 + ); + assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); + } + + @Test + public void getDailyTimestamps_LastDayOneHourData_returnExpectedList() { + // Timezone GMT+8 + final List timestamps = List.of( + 1640988000000L, // 2022-01-01 06:00:00 + 1641060000000L, // 2022-01-02 02:00:00 + 1641160800000L, // 2022-01-03 06:00:00 + 1641229200000L // 2022-01-04 01:00:00 + ); + + final List expectedTimestamps = List.of( + 1640988000000L, // 2022-01-01 06:00:00 + 1641052800000L, // 2022-01-02 00:00:00 + 1641139200000L, // 2022-01-03 00:00:00 + 1641225600000L, // 2022-01-04 00:00:00 + 1641229200000L // 2022-01-04 01:00:00 + ); + assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); + } + + @Test + public void combine_normalFlow_returnExpectedResult() { + final BatteryLevelData batteryLevelData = + new BatteryLevelData(Map.of(1691596800000L, 90, 1691604000000L, 80)); + final List batteryLevelRecordEvents = List.of( + BatteryEvent.newBuilder().setTimestamp(1691586000166L).setBatteryLevel(100) + .setType(BatteryEventType.FULL_CHARGED).build(), + BatteryEvent.newBuilder().setTimestamp(1691589600000L).setBatteryLevel(98) + .setType(BatteryEventType.EVEN_HOUR).build()); + + BatteryLevelData result = + BatteryLevelData.combine(batteryLevelData, batteryLevelRecordEvents); + + assertThat(result.getDailyBatteryLevels().getTimestamps()) + .isEqualTo(List.of(1691586000166L, 1691596800000L, 1691604000000L)); + assertThat(result.getDailyBatteryLevels().getLevels()) + .isEqualTo(List.of(100, 90, 80)); + assertThat(result.getHourlyBatteryLevelsPerDay()) + .hasSize(2); + assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getTimestamps()) + .isEqualTo(List.of(1691586000166L, 1691589600000L, 1691596800000L)); + assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getLevels()) + .isEqualTo(List.of(100, 98, 90)); + assertThat(result.getHourlyBatteryLevelsPerDay().get(1).getTimestamps()) + .isEqualTo(List.of(1691596800000L, 1691604000000L)); + assertThat(result.getHourlyBatteryLevelsPerDay().get(1).getLevels()) + .isEqualTo(List.of(90, 80)); + } + + @Test + public void combine_existingBatteryLevelDataIsNull_returnExpectedResult() { + final List batteryLevelRecordEvents = List.of( + BatteryEvent.newBuilder().setTimestamp(1691586000166L).setBatteryLevel(100) + .setType(BatteryEventType.FULL_CHARGED).build(), + BatteryEvent.newBuilder().setTimestamp(1691589600000L).setBatteryLevel(98) + .setType(BatteryEventType.EVEN_HOUR).build()); + + BatteryLevelData result = + BatteryLevelData.combine(null, batteryLevelRecordEvents); + + assertThat(result.getHourlyBatteryLevelsPerDay()) + .hasSize(1); + assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getTimestamps()) + .isEqualTo(List.of(1691586000166L, 1691589600000L)); + assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getLevels()) + .isEqualTo(List.of(100, 98)); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java index 3a9ce2b4c52..d89c06b1150 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java @@ -96,6 +96,14 @@ public final class BatteryUsageBreakdownControllerTest { mBatteryUsageBreakdownController.mAppListPreferenceGroup = mAppListPreferenceGroup; mBatteryDiffEntry = new BatteryDiffEntry( mContext, + /*uid=*/ 0L, + /*userId=*/ 0L, + /*key=*/ "key", + /*isHidden=*/ false, + /*componentId=*/ -1, + /*legacyPackageName=*/ null, + /*legacyLabel=*/ null, + /*consumerType=*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 1, /*backgroundUsageTimeInMs=*/ 2, /*screenOnTimeInMs=*/ 0, @@ -103,13 +111,14 @@ public final class BatteryUsageBreakdownControllerTest { /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 1, /*backgroundUsageConsumePower=*/ 2, - /*cachedUsageConsumePower=*/ 0, - mBatteryHistEntry); + /*cachedUsageConsumePower=*/ 0); mBatteryDiffEntry = spy(mBatteryDiffEntry); mBatteryUsageBreakdownController.mBatteryDiffData = - new BatteryDiffData(mContext, /* screenOnTime= */ 0L, - Arrays.asList(mBatteryDiffEntry), Arrays.asList(), Set.of(), Set.of(), - /* isAccumulated= */ false); + new BatteryDiffData(mContext, /* startTimestamp= */ 0L, /* endTimestamp= */ 0L, + /* startBatteryLevel= */ 0, /* endBatteryLevel= */ 0, + /* screenOnTime= */ 0L, Arrays.asList(mBatteryDiffEntry), Arrays.asList(), + Set.of(), Set.of(), /* isAccumulated= */ false); + BatteryDiffEntry.clearCache(); // Adds fake testing data. BatteryDiffEntry.sResourceCache.put( "fakeBatteryDiffEntryKey", @@ -140,7 +149,7 @@ public final class BatteryUsageBreakdownControllerTest { doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount(); doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon(); doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel(); - doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); + doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey(); doReturn(null).when(mAppListPreferenceGroup).findPreference(PREF_KEY); doReturn(false).when(mBatteryDiffEntry).validForRestriction(); @@ -168,7 +177,7 @@ public final class BatteryUsageBreakdownControllerTest { doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount(); doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon(); doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel(); - doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); + doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey(); doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY); mBatteryUsageBreakdownController.addAllPreferences(); @@ -197,7 +206,7 @@ public final class BatteryUsageBreakdownControllerTest { public void removeAndCacheAllUnusedPreferences_keepPref_KeepAllPreference() { doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount(); doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0); - doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); + doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey(); doReturn(PREF_KEY).when(mPowerGaugePreference).getKey(); doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY); // Ensures the testing data is correct. @@ -222,7 +231,7 @@ public final class BatteryUsageBreakdownControllerTest { @Test public void handlePreferenceTreeClick_forAppEntry_returnTrue() { - doReturn(false).when(mBatteryHistEntry).isAppEntry(); + mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY; doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry(); assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick( @@ -238,7 +247,7 @@ public final class BatteryUsageBreakdownControllerTest { @Test public void handlePreferenceTreeClick_forSystemEntry_returnTrue() { - doReturn(true).when(mBatteryHistEntry).isAppEntry(); + mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_UID_BATTERY; doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry(); assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick( @@ -394,10 +403,23 @@ public final class BatteryUsageBreakdownControllerTest { contentValues.put(BatteryHistEntry.KEY_USER_ID, Integer.valueOf(1001)); final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(contentValues); return new BatteryDiffEntry( - mContext, foregroundUsageTimeInMs, backgroundUsageTimeInMs, screenOnTimeInMs, - /*consumePower=*/ 0, /*foregroundUsageConsumePower=*/ 0, - /*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0, - /*cachedUsageConsumePower=*/ 0, batteryHistEntry); + mContext, + batteryHistEntry.mUid, + batteryHistEntry.mUserId, + batteryHistEntry.getKey(), + batteryHistEntry.mIsHidden, + batteryHistEntry.mDrainType, + batteryHistEntry.mPackageName, + batteryHistEntry.mAppLabel, + batteryHistEntry.mConsumerType, + foregroundUsageTimeInMs, + backgroundUsageTimeInMs, + screenOnTimeInMs, + /*consumePower=*/ 0, + /*foregroundUsageConsumePower=*/ 0, + /*foregroundServiceUsageConsumePower=*/ 0, + /*backgroundUsageConsumePower=*/ 0, + /*cachedUsageConsumePower=*/ 0); } private BatteryUsageBreakdownController createController() { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java index 05a6f2bc89c..999a9217112 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java @@ -20,11 +20,9 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import android.app.Application; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; -import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -34,6 +32,7 @@ import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity; import com.android.settings.fuelgauge.batteryusage.db.BatteryState; import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase; +import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity; import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.FakeClock; @@ -41,12 +40,10 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.Shadows; import java.time.Duration; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** Tests for {@link BatteryUsageContentProvider}. */ @@ -126,11 +123,29 @@ public final class BatteryUsageContentProviderTest { () -> mProvider.insert(uri, /*contentValues=*/ null)); } + @Test + public void query_getLastFullChargeTimestamp_returnsExpectedResult() throws Exception { + mProvider.onCreate(); + ContentValues values = new ContentValues(); + values.put(BatteryEventEntity.KEY_TIMESTAMP, 10001L); + values.put(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE, + BatteryEventType.FULL_CHARGED.getNumber()); + values.put(BatteryEventEntity.KEY_BATTERY_LEVEL, 100); + mProvider.insert(DatabaseUtils.BATTERY_EVENT_URI, values); + + final Cursor cursor = getCursorOfLastFullChargeTimestamp(); + + assertThat(cursor.getCount()).isEqualTo(1); + cursor.moveToFirst(); + final long lastFullChargeTimestamp = cursor.getLong(0); + assertThat(lastFullChargeTimestamp).isEqualTo(10001L); + } + @Test public void query_batteryState_returnsExpectedResult() throws Exception { mProvider.onCreate(); final Duration currentTime = Duration.ofHours(52); - final long expiredTimeCutoff = currentTime.toMillis() - 3; + final long expiredTimeCutoff = currentTime.toMillis() - 8; final Cursor cursor = insertBatteryState(currentTime, Long.toString(expiredTimeCutoff)); @@ -150,19 +165,13 @@ public final class BatteryUsageContentProviderTest { final String actualPackageName3 = cursor.getString(packageNameIndex); assertThat(actualPackageName3).isEqualTo(PACKAGE_NAME3); cursor.close(); - // Verifies the broadcast intent. - TimeUnit.SECONDS.sleep(1); - final List intents = Shadows.shadowOf((Application) mContext).getBroadcastIntents(); - assertThat(intents).hasSize(1); - assertThat(intents.get(0).getAction()).isEqualTo( - BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK); } @Test public void query_batteryStateTimestamp_returnsExpectedResult() throws Exception { mProvider.onCreate(); final Duration currentTime = Duration.ofHours(52); - final long expiredTimeCutoff = currentTime.toMillis() - 1; + final long expiredTimeCutoff = currentTime.toMillis() - 2; final Cursor cursor = insertBatteryState(currentTime, Long.toString(expiredTimeCutoff)); @@ -178,12 +187,25 @@ public final class BatteryUsageContentProviderTest { final String actualPackageName2 = cursor.getString(packageNameIndex); assertThat(actualPackageName2).isEqualTo(PACKAGE_NAME3); cursor.close(); - // Verifies the broadcast intent. - TimeUnit.SECONDS.sleep(1); - final List intents = Shadows.shadowOf((Application) mContext).getBroadcastIntents(); - assertThat(intents).hasSize(1); - assertThat(intents.get(0).getAction()).isEqualTo( - BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK); + } + + @Test + public void query_getBatteryStateLatestTimestamp_returnsExpectedResult() throws Exception { + mProvider.onCreate(); + final Duration currentTime = Duration.ofHours(52); + insertBatteryState(currentTime, Long.toString(currentTime.toMillis())); + + final Cursor cursor1 = getCursorOfBatteryStateLatestTimestamp(currentTime.toMillis() - 5); + assertThat(cursor1.getCount()).isEqualTo(1); + cursor1.moveToFirst(); + final long latestTimestamp1 = cursor1.getLong(0); + assertThat(latestTimestamp1).isEqualTo(currentTime.toMillis() - 6); + + final Cursor cursor2 = getCursorOfBatteryStateLatestTimestamp(currentTime.toMillis() - 2); + assertThat(cursor2.getCount()).isEqualTo(1); + cursor2.moveToFirst(); + final long latestTimestamp2 = cursor2.getLong(0); + assertThat(latestTimestamp2).isEqualTo(currentTime.toMillis() - 2); } @Test @@ -355,7 +377,7 @@ public final class BatteryUsageContentProviderTest { } @Test - public void insert_batteryEvent_returnsExpectedResult() { + public void insertAndQuery_batteryEvent_returnsExpectedResult() { mProvider.onCreate(); ContentValues values = new ContentValues(); values.put(BatteryEventEntity.KEY_TIMESTAMP, 10001L); @@ -366,7 +388,7 @@ public final class BatteryUsageContentProviderTest { final Uri uri = mProvider.insert(DatabaseUtils.BATTERY_EVENT_URI, values); assertThat(uri).isEqualTo(DatabaseUtils.BATTERY_EVENT_URI); - // Verifies the AppUsageEventEntity content. + // Verifies the BatteryEventEntity content. final List entities = BatteryStateDatabase.getInstance(mContext).batteryEventDao().getAll(); assertThat(entities).hasSize(1); @@ -374,6 +396,50 @@ public final class BatteryUsageContentProviderTest { assertThat(entities.get(0).batteryEventType).isEqualTo( BatteryEventType.POWER_CONNECTED.getNumber()); assertThat(entities.get(0).batteryLevel).isEqualTo(66); + + final Cursor cursor1 = getCursorOfBatteryEvents( + 0L, List.of(BatteryEventType.POWER_CONNECTED.getNumber())); + assertThat(cursor1.getCount()).isEqualTo(1); + cursor1.moveToFirst(); + assertThat(cursor1.getLong(cursor1.getColumnIndex(BatteryEventEntity.KEY_TIMESTAMP))) + .isEqualTo(10001L); + assertThat( + cursor1.getInt(cursor1.getColumnIndex(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE))) + .isEqualTo(BatteryEventType.POWER_CONNECTED.getNumber()); + assertThat(cursor1.getInt(cursor1.getColumnIndex(BatteryEventEntity.KEY_BATTERY_LEVEL))) + .isEqualTo(66); + + final Cursor cursor2 = getCursorOfBatteryEvents( + 0L, List.of(BatteryEventType.POWER_DISCONNECTED.getNumber())); + assertThat(cursor2.getCount()).isEqualTo(0); + } + + @Test + public void insertAndQuery_batteryUsageSlot_returnsExpectedResult() { + mProvider.onCreate(); + ContentValues values = new ContentValues(); + values.put(BatteryUsageSlotEntity.KEY_TIMESTAMP, 10001L); + values.put(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT, "TEST_STRING"); + + final Uri uri = mProvider.insert(DatabaseUtils.BATTERY_USAGE_SLOT_URI, values); + // Verifies the BatteryUsageSlotEntity content. + assertThat(uri).isEqualTo(DatabaseUtils.BATTERY_USAGE_SLOT_URI); + final List entities = + BatteryStateDatabase.getInstance(mContext).batteryUsageSlotDao().getAll(); + assertThat(entities).hasSize(1); + assertThat(entities.get(0).timestamp).isEqualTo(10001L); + assertThat(entities.get(0).batteryUsageSlot).isEqualTo("TEST_STRING"); + + final Cursor cursor1 = getCursorOfBatteryUsageSlots(10001L); + assertThat(cursor1.getCount()).isEqualTo(1); + cursor1.moveToFirst(); + assertThat(cursor1.getLong(cursor1.getColumnIndex(BatteryUsageSlotEntity.KEY_TIMESTAMP))) + .isEqualTo(10001L); + assertThat(cursor1.getString(cursor1.getColumnIndex( + BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT))).isEqualTo("TEST_STRING"); + + final Cursor cursor2 = getCursorOfBatteryUsageSlots(10002L); + assertThat(cursor2.getCount()).isEqualTo(0); } @Test @@ -404,10 +470,10 @@ public final class BatteryUsageContentProviderTest { final long currentTimestamp = currentTime.toMillis(); // Inserts some valid testing data. BatteryTestUtils.insertDataToBatteryStateTable( - mContext, currentTimestamp - 2, PACKAGE_NAME1, + mContext, currentTimestamp - 6, PACKAGE_NAME1, /*isFullChargeStart=*/ true); BatteryTestUtils.insertDataToBatteryStateTable( - mContext, currentTimestamp - 1, PACKAGE_NAME2); + mContext, currentTimestamp - 2, PACKAGE_NAME2); BatteryTestUtils.insertDataToBatteryStateTable( mContext, currentTimestamp, PACKAGE_NAME3); @@ -420,17 +486,35 @@ public final class BatteryUsageContentProviderTest { DatabaseUtils.QUERY_KEY_TIMESTAMP, queryTimestamp) .build(); - final Cursor cursor = - mProvider.query( - batteryStateQueryContentUri, - /*strings=*/ null, - /*s=*/ null, - /*strings1=*/ null, - /*s1=*/ null); + final Cursor cursor = query(batteryStateQueryContentUri); return cursor; } + private Cursor getCursorOfLastFullChargeTimestamp() { + final Uri lastFullChargeTimestampContentUri = + new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(DatabaseUtils.AUTHORITY) + .appendPath(DatabaseUtils.LAST_FULL_CHARGE_TIMESTAMP_PATH) + .build(); + + return query(lastFullChargeTimestampContentUri); + } + + private Cursor getCursorOfBatteryStateLatestTimestamp(final long queryTimestamp) { + final Uri batteryStateLatestTimestampUri = + new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(DatabaseUtils.AUTHORITY) + .appendPath(DatabaseUtils.BATTERY_STATE_LATEST_TIMESTAMP_PATH) + .appendQueryParameter( + DatabaseUtils.QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp)) + .build(); + + return query(batteryStateLatestTimestampUri); + } + private void insertAppUsageEvent() { mProvider.onCreate(); // Inserts some valid testing data. @@ -452,12 +536,7 @@ public final class BatteryUsageContentProviderTest { DatabaseUtils.QUERY_KEY_USERID, Long.toString(userId)) .build(); - return mProvider.query( - appUsageLatestTimestampQueryContentUri, - /*strings=*/ null, - /*s=*/ null, - /*strings1=*/ null, - /*s1=*/ null); + return query(appUsageLatestTimestampQueryContentUri); } private Cursor getCursorOfAppUsage(final List userIds, final long queryTimestamp) { @@ -474,7 +553,43 @@ public final class BatteryUsageContentProviderTest { .appendQueryParameter(DatabaseUtils.QUERY_KEY_USERID, queryUserIdString) .build(); + return query(appUsageEventUri); + } + + private Cursor getCursorOfBatteryEvents( + final long queryTimestamp, final List batteryEventTypes) { + final String batteryEventTypesString = batteryEventTypes.stream() + .map(type -> String.valueOf(type)) + .collect(Collectors.joining(",")); + final Uri batteryEventUri = + new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(DatabaseUtils.AUTHORITY) + .appendPath(DatabaseUtils.BATTERY_EVENT_TABLE) + .appendQueryParameter( + DatabaseUtils.QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp)) + .appendQueryParameter( + DatabaseUtils.QUERY_BATTERY_EVENT_TYPE, batteryEventTypesString) + .build(); + + return query(batteryEventUri); + } + + private Cursor getCursorOfBatteryUsageSlots(final long queryTimestamp) { + final Uri batteryUsageSlotUri = + new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(DatabaseUtils.AUTHORITY) + .appendPath(DatabaseUtils.BATTERY_USAGE_SLOT_TABLE) + .appendQueryParameter( + DatabaseUtils.QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp)) + .build(); + + return query(batteryUsageSlotUri); + } + + private Cursor query(Uri uri) { return mProvider.query( - appUsageEventUri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null, /*s1=*/ null); + uri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null, /*s1=*/ null); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java index 9aeff794907..f3965fd48cd 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.ContentResolver; @@ -31,6 +32,7 @@ import android.content.pm.PackageManager; import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.UserManager; import org.junit.Before; import org.junit.Test; @@ -43,6 +45,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; @RunWith(RobolectricTestRunner.class) @@ -56,6 +59,8 @@ public final class BatteryUsageDataLoaderTest { @Mock private PackageManager mPackageManager; @Mock + private UserManager mUserManager; + @Mock private BatteryUsageStats mBatteryUsageStats; @Mock private BatteryEntry mMockBatteryEntry; @@ -70,6 +75,7 @@ public final class BatteryUsageDataLoaderTest { doReturn(mBatteryStatsManager).when(mContext).getSystemService( Context.BATTERY_STATS_SERVICE); doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); doReturn(mMockContentResolver).when(mContext).getContentResolver(); doReturn(new Intent()).when(mContext).registerReceiver(any(), any()); } @@ -82,7 +88,7 @@ public final class BatteryUsageDataLoaderTest { .thenReturn(mBatteryUsageStats); BatteryUsageDataLoader.sFakeBatteryEntryListSupplier = () -> batteryEntryList; - BatteryUsageDataLoader.loadUsageData(mContext, /*isFullChargeStart=*/ false); + BatteryUsageDataLoader.loadBatteryStatsData(mContext, /*isFullChargeStart=*/ false); final int queryFlags = mStatsQueryCaptor.getValue().getFlags(); assertThat(queryFlags @@ -97,7 +103,7 @@ public final class BatteryUsageDataLoaderTest { .thenReturn(mBatteryUsageStats); BatteryUsageDataLoader.sFakeBatteryEntryListSupplier = () -> null; - BatteryUsageDataLoader.loadUsageData(mContext, /*isFullChargeStart=*/ false); + BatteryUsageDataLoader.loadBatteryStatsData(mContext, /*isFullChargeStart=*/ false); verify(mMockContentResolver).insert(any(), any()); } @@ -108,8 +114,51 @@ public final class BatteryUsageDataLoaderTest { .thenReturn(mBatteryUsageStats); BatteryUsageDataLoader.sFakeBatteryEntryListSupplier = () -> new ArrayList<>(); - BatteryUsageDataLoader.loadUsageData(mContext, /*isFullChargeStart=*/ false); + BatteryUsageDataLoader.loadBatteryStatsData(mContext, /*isFullChargeStart=*/ false); verify(mMockContentResolver).insert(any(), any()); } + + @Test + public void loadAppUsageData_withData_insertFakeDataIntoProvider() { + final List AppUsageEventList = new ArrayList<>(); + final AppUsageEvent appUsageEvent = AppUsageEvent.newBuilder().setUid(0).build(); + AppUsageEventList.add(appUsageEvent); + BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>(); + BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> AppUsageEventList; + + BatteryUsageDataLoader.loadAppUsageData(mContext); + + verify(mMockContentResolver).bulkInsert(any(), any()); + verify(mMockContentResolver).notifyChange(any(), any()); + } + + @Test + public void loadAppUsageData_nullAppUsageEvents_notInsertDataIntoProvider() { + BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> null; + + BatteryUsageDataLoader.loadAppUsageData(mContext); + + verifyNoMoreInteractions(mMockContentResolver); + } + + @Test + public void loadAppUsageData_nullUsageEventsList_notInsertDataIntoProvider() { + BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>(); + BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> null; + + BatteryUsageDataLoader.loadAppUsageData(mContext); + + verifyNoMoreInteractions(mMockContentResolver); + } + + @Test + public void loadAppUsageData_emptyUsageEventsList_notInsertDataIntoProvider() { + BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>(); + BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> new ArrayList<>(); + + BatteryUsageDataLoader.loadAppUsageData(mContext); + + verifyNoMoreInteractions(mMockContentResolver); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java index e9108bc7efe..f06dc634005 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java @@ -39,6 +39,7 @@ import android.os.UserHandle; 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 org.junit.Before; import org.junit.Test; @@ -48,7 +49,10 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Set; import java.util.TimeZone; @RunWith(RobolectricTestRunner.class) @@ -212,6 +216,22 @@ public final class ConvertUtilsTest { assertThat(values.getAsInteger(BatteryEventEntity.KEY_BATTERY_LEVEL)).isEqualTo(66); } + @Test + public void convertBatteryUsageSlotToContentValues_normalCase_returnsExpectedContentValues() { + final BatteryUsageSlot batteryUsageSlot = + BatteryUsageSlot.newBuilder() + .setStartTimestamp(10001L) + .setEndTimestamp(30003L) + .setStartBatteryLevel(88) + .setEndBatteryLevel(66) + .setScreenOnTime(123L) + .build(); + final ContentValues values = + ConvertUtils.convertBatteryUsageSlotToContentValues(batteryUsageSlot); + assertThat(values.getAsLong(BatteryUsageSlotEntity.KEY_TIMESTAMP)).isEqualTo(10001L); + assertThat(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT).isNotEmpty(); + } + @Test public void convertToBatteryHistEntry_returnsExpectedResult() { final int expectedType = 3; @@ -363,7 +383,7 @@ public final class ConvertUtilsTest { } @Test - public void convertToAppUsageEventFromCursor_returnExpectedResult() { + public void convertToAppUsageEvent_returnExpectedResult() { final MatrixCursor cursor = new MatrixCursor( new String[]{ AppUsageEventEntity.KEY_UID, @@ -384,7 +404,7 @@ public final class ConvertUtilsTest { 100001L}); cursor.moveToFirst(); - final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEventFromCursor(cursor); + final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(cursor); assertThat(appUsageEvent.getUid()).isEqualTo(101L); assertThat(appUsageEvent.getUserId()).isEqualTo(1001L); @@ -396,7 +416,7 @@ public final class ConvertUtilsTest { } @Test - public void convertToAppUsageEventFromCursor_emptyInstanceIdAndRootName_returnExpectedResult() { + public void convertToAppUsageEvent_emptyInstanceIdAndRootName_returnExpectedResult() { final MatrixCursor cursor = new MatrixCursor( new String[]{ AppUsageEventEntity.KEY_UID, @@ -413,7 +433,7 @@ public final class ConvertUtilsTest { AppUsageEventType.DEVICE_SHUTDOWN.getNumber()}); cursor.moveToFirst(); - final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEventFromCursor(cursor); + final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(cursor); assertThat(appUsageEvent.getUid()).isEqualTo(101L); assertThat(appUsageEvent.getUserId()).isEqualTo(1001L); @@ -433,6 +453,42 @@ public final class ConvertUtilsTest { assertThat(batteryEvent.getBatteryLevel()).isEqualTo(88); } + @Test + public void convertToBatteryEventList_normalCase_returnsExpectedResult() { + final BatteryLevelData batteryLevelData = new BatteryLevelData(Map.of( + 1691589600000L, 98, 1691596800000L, 90, 1691596812345L, 80)); + + final List batteryEventList = + ConvertUtils.convertToBatteryEventList(batteryLevelData); + + assertThat(batteryEventList).hasSize(2); + assertThat(batteryEventList.get(0).getTimestamp()).isEqualTo(1691589600000L); + assertThat(batteryEventList.get(0).getType()).isEqualTo(BatteryEventType.EVEN_HOUR); + assertThat(batteryEventList.get(0).getBatteryLevel()).isEqualTo(98); + assertThat(batteryEventList.get(1).getTimestamp()).isEqualTo(1691596800000L); + assertThat(batteryEventList.get(1).getType()).isEqualTo(BatteryEventType.EVEN_HOUR); + assertThat(batteryEventList.get(1).getBatteryLevel()).isEqualTo(90); + } + + @Test + public void convertToBatteryUsageSlotList_normalCase_returnsExpectedResult() { + BatteryDiffData batteryDiffData1 = new BatteryDiffData( + mContext, 11L, 12L, 13, 14, 15, List.of(), List.of(), Set.of(), Set.of(), false); + BatteryDiffData batteryDiffData2 = new BatteryDiffData( + mContext, 21L, 22L, 23, 24, 25, List.of(), List.of(), Set.of(), Set.of(), false); + BatteryDiffData batteryDiffData3 = new BatteryDiffData( + mContext, 31L, 32L, 33, 34, 35, List.of(), List.of(), Set.of(), Set.of(), false); + final Map batteryDiffDataMap = Map.of( + 11L, batteryDiffData1, 21L, batteryDiffData2, 31L, batteryDiffData3); + + final List batteryUsageSlotList = + ConvertUtils.convertToBatteryUsageSlotList(batteryDiffDataMap); + + assertThat(batteryUsageSlotList).hasSize(3); + assertThat(batteryUsageSlotList.stream().map((s) -> s.getScreenOnTime()).sorted().toList()) + .isEqualTo(List.of(15L, 25L, 35L)); + } + @Test public void getLocale_nullContext_returnDefaultLocale() { assertThat(ConvertUtils.getLocale(/*context=*/ null)) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java index b610cfbf289..94fa00fddd9 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java @@ -30,8 +30,12 @@ import android.app.usage.UsageEvents; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.database.MatrixCursor; import android.os.BatteryManager; +import android.os.BatteryStatsManager; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; import android.os.Parcel; import android.os.RemoteException; import android.os.UserManager; @@ -39,9 +43,12 @@ import android.text.format.DateUtils; import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -52,6 +59,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; @RunWith(RobolectricTestRunner.class) public final class DataProcessManagerTest { @@ -65,7 +73,13 @@ public final class DataProcessManagerTest { @Mock private UserManager mUserManager; @Mock + private BatteryStatsManager mBatteryStatsManager; + @Mock + private BatteryUsageStats mBatteryUsageStats; + @Mock private Intent mIntent; + @Captor + private ArgumentCaptor mBatteryUsageStatsQueryCaptor; @Before public void setUp() { @@ -77,22 +91,32 @@ public final class DataProcessManagerTest { doReturn(mUserManager) .when(mContext) .getSystemService(UserManager.class); + doReturn(mBatteryStatsManager).when(mContext).getSystemService( + Context.BATTERY_STATS_SERVICE); + doReturn(mBatteryUsageStats).when( + mBatteryStatsManager).getBatteryUsageStats(mBatteryUsageStatsQueryCaptor.capture()); doReturn(mIntent).when(mContext).registerReceiver(any(), any()); doReturn(100).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_SCALE), anyInt()); doReturn(66).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_LEVEL), anyInt()); mDataProcessManager = new DataProcessManager( mContext, /*handler=*/ null, /*rawStartTimestamp=*/ 0L, - /*callbackFunction=*/ null, /*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(), + /*lastFullChargeTimestamp=*/ 0L, /*callbackFunction=*/ null, + /*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(), /*batteryHistoryMap=*/ new HashMap<>()); } + @After + public void cleanUp() { + DatabaseUtils.sFakeSupplier = null; + DataProcessManager.sFakeBatteryHistoryMap = null; + } + @Test public void constructor_noLevelData() { final DataProcessManager dataProcessManager = new DataProcessManager(mContext, /*handler=*/ null, /*callbackFunction=*/ null); assertThat(dataProcessManager.getShowScreenOnTime()).isFalse(); - assertThat(dataProcessManager.getShowBatteryLevel()).isFalse(); } @Test @@ -122,16 +146,18 @@ public final class DataProcessManagerTest { final String packageName = "package"; // Adds the day 1 data. final List timestamps1 = List.of(2L, 3L, 4L); - final List levels1 = List.of(100, 100, 100); + final Map batteryLevelMap1 = + Map.of(timestamps1.get(0), 100, timestamps1.get(1), 100, timestamps1.get(2), 100); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps1, levels1)); + new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap1, timestamps1)); // Adds the day 2 data. hourlyBatteryLevelsPerDay.add(null); // Adds the day 3 data. final List timestamps2 = List.of(5L, 6L); - final List levels2 = List.of(100, 100); + final Map batteryLevelMap2 = + Map.of(timestamps2.get(0), 100, timestamps2.get(1), 100); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps2, levels2)); + new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap2, timestamps2)); // Fake current usage data. final UsageEvents.Event event1 = getUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, /*timestamp=*/ 1, packageName); @@ -171,10 +197,18 @@ public final class DataProcessManagerTest { cursor.addRow(new Object[] { AppUsageEventType.ACTIVITY_STOPPED.getNumber(), /*timestamp=*/ 6, /*userId=*/ 1, /*instanceId=*/ 2, packageName}); - DatabaseUtils.sFakeSupplier = () -> cursor; + DatabaseUtils.sFakeSupplier = new Supplier<>() { + private int mTimes = 0; + @Override + public Cursor get() { + mTimes++; + return mTimes <= 2 ? null : cursor; + } + }; final DataProcessManager dataProcessManager = new DataProcessManager( - mContext, /*handler=*/ null, /*rawStartTimestamp=*/ 2L, /*callbackFunction=*/ null, + mContext, /*handler=*/ null, /*rawStartTimestamp=*/ 2L, + /*lastFullChargeTimestamp=*/ 1L, /*callbackFunction=*/ null, hourlyBatteryLevelsPerDay, /*batteryHistoryMap=*/ new HashMap<>()); dataProcessManager.start(); @@ -254,12 +288,13 @@ public final class DataProcessManagerTest { assertThat(DataProcessManager.getBatteryLevelData( mContext, /*handler=*/ null, - /*batteryHistoryMap=*/ null, - /*asyncResponseDelegate=*/ null)) - .isNull(); + /*isFromPeriodJob=*/ false, + /*asyncResponseDelegate=*/ null)).isNull(); assertThat(DataProcessManager.getBatteryLevelData( - mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null)) - .isNull(); + mContext, + /*handler=*/ null, + /*isFromPeriodJob=*/ true, + /*asyncResponseDelegate=*/ null)).isNull(); } @Test @@ -270,18 +305,16 @@ public final class DataProcessManagerTest { DateUtils.HOUR_IN_MILLIS * 2 - 200L, DateUtils.HOUR_IN_MILLIS * 2 - 100L}; final int[] levels = {100, 99, 98}; - final Map> batteryHistoryMap = - createHistoryMap(timestamps, levels); + DataProcessManager.sFakeBatteryHistoryMap = createHistoryMap(timestamps, levels); DataProcessor.sTestCurrentTimeMillis = timestamps[timestamps.length - 1]; final BatteryLevelData resultData = DataProcessManager.getBatteryLevelData( mContext, /*handler=*/ null, - batteryHistoryMap, + /*isFromPeriodJob=*/ false, /*asyncResponseDelegate=*/ null); - final List expectedDailyTimestamps = List.of( DateUtils.HOUR_IN_MILLIS * 2 - 300L, DateUtils.HOUR_IN_MILLIS * 2 - 100L); @@ -301,15 +334,14 @@ public final class DataProcessManagerTest { // Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00 final long[] timestamps = {1640966400000L, 1640970000000L}; final int[] levels = {100, 99}; - final Map> batteryHistoryMap = - createHistoryMap(timestamps, levels); + DataProcessManager.sFakeBatteryHistoryMap = createHistoryMap(timestamps, levels); DataProcessor.sTestCurrentTimeMillis = timestamps[timestamps.length - 1]; final BatteryLevelData resultData = DataProcessManager.getBatteryLevelData( mContext, /*handler=*/ null, - batteryHistoryMap, + /*isFromPeriodJob=*/ false, /*asyncResponseDelegate=*/ null); final List expectedDailyTimestamps = List.of( diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java index c9b635eeb0e..c4394f76130 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java @@ -16,6 +16,9 @@ package com.android.settings.fuelgauge.batteryusage; +import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.FAKE_PACKAGE_NAME; +import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.anyInt; @@ -42,6 +45,7 @@ import android.os.BatteryUsageStats; import android.os.Parcel; import android.os.RemoteException; import android.os.UserManager; +import android.util.ArrayMap; import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; @@ -188,16 +192,18 @@ public final class DataProcessorTest { final String packageName = "com.android.settings"; // Adds the day 1 data. final List timestamps1 = List.of(14400000L, 18000000L, 21600000L); - final List levels1 = List.of(100, 100, 100); + final Map batteryLevelMap1 = + Map.of(timestamps1.get(0), 100, timestamps1.get(1), 100, timestamps1.get(2), 100); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps1, levels1)); + new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap1, timestamps1)); // Adds the day 2 data. hourlyBatteryLevelsPerDay.add(null); // Adds the day 3 data. final List timestamps2 = List.of(45200000L, 48800000L); - final List levels2 = List.of(100, 100); + final Map batteryLevelMap2 = + Map.of(timestamps2.get(0), 100, timestamps2.get(1), 100); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps2, levels2)); + new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap2, timestamps2)); final List appUsageEventList = new ArrayList<>(); // Adds some events before the start timestamp. appUsageEventList.add(buildAppUsageEvent( @@ -285,7 +291,7 @@ public final class DataProcessorTest { final List hourlyBatteryLevelsPerDay = new ArrayList<>(); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>())); + new BatteryLevelData.PeriodBatteryLevelData(new ArrayMap<>(), new ArrayList<>())); assertThat(DataProcessor.generateAppUsagePeriodMap( mContext, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>())) .isNull(); @@ -370,19 +376,6 @@ public final class DataProcessorTest { DataProcessor.CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)).isTrue(); } - @Test - public void getLevelDataThroughProcessedHistoryMap_notEnoughData_returnNull() { - final long[] timestamps = {100L}; - final int[] levels = {100}; - final Map> batteryHistoryMap = - createHistoryMap(timestamps, levels); - DataProcessor.sTestCurrentTimeMillis = timestamps[timestamps.length - 1]; - - assertThat( - DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap)) - .isNull(); - } - @Test public void getLevelDataThroughProcessedHistoryMap_OneDayData_returnExpectedResult() { // Timezone GMT+8 @@ -441,7 +434,7 @@ public final class DataProcessorTest { ); final List expectedDailyLevels = new ArrayList<>(); expectedDailyLevels.add(100); - expectedDailyLevels.add(null); + expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN); expectedDailyLevels.add(82); final List> expectedHourlyTimestamps = List.of( List.of( @@ -459,13 +452,13 @@ public final class DataProcessorTest { ); final List expectedHourlyLevels1 = new ArrayList<>(); expectedHourlyLevels1.add(100); - expectedHourlyLevels1.add(null); - expectedHourlyLevels1.add(null); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); final List expectedHourlyLevels2 = new ArrayList<>(); - expectedHourlyLevels2.add(null); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); expectedHourlyLevels2.add(94); expectedHourlyLevels2.add(90); - expectedHourlyLevels2.add(null); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); expectedHourlyLevels2.add(82); final List> expectedHourlyLevels = List.of( expectedHourlyLevels1, @@ -503,8 +496,8 @@ public final class DataProcessorTest { ); final List expectedDailyLevels = new ArrayList<>(); expectedDailyLevels.add(100); - expectedDailyLevels.add(null); - expectedDailyLevels.add(null); + expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN); + expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN); expectedDailyLevels.add(88); final List> expectedHourlyTimestamps = List.of( List.of( @@ -542,32 +535,32 @@ public final class DataProcessorTest { ); final List expectedHourlyLevels1 = new ArrayList<>(); expectedHourlyLevels1.add(100); - expectedHourlyLevels1.add(null); - expectedHourlyLevels1.add(null); - expectedHourlyLevels1.add(null); - expectedHourlyLevels1.add(null); - expectedHourlyLevels1.add(null); - expectedHourlyLevels1.add(null); - expectedHourlyLevels1.add(null); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); final List expectedHourlyLevels2 = new ArrayList<>(); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); final List expectedHourlyLevels3 = new ArrayList<>(); - expectedHourlyLevels3.add(null); - expectedHourlyLevels3.add(null); - expectedHourlyLevels3.add(null); + expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN); expectedHourlyLevels3.add(88); final List> expectedHourlyLevels = List.of( expectedHourlyLevels1, @@ -606,8 +599,8 @@ public final class DataProcessorTest { ); final List expectedDailyLevels = new ArrayList<>(); expectedDailyLevels.add(100); - expectedDailyLevels.add(null); - expectedDailyLevels.add(null); + expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN); + expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN); expectedDailyLevels.add(88); final List> expectedHourlyTimestamps = List.of( List.of( @@ -638,25 +631,25 @@ public final class DataProcessorTest { ); final List expectedHourlyLevels1 = new ArrayList<>(); expectedHourlyLevels1.add(100); - expectedHourlyLevels1.add(null); + expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN); final List expectedHourlyLevels2 = new ArrayList<>(); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); - expectedHourlyLevels2.add(null); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN); final List expectedHourlyLevels3 = new ArrayList<>(); - expectedHourlyLevels3.add(null); - expectedHourlyLevels3.add(null); - expectedHourlyLevels3.add(null); + expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN); + expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN); expectedHourlyLevels3.add(88); final List> expectedHourlyLevels = List.of( expectedHourlyLevels1, @@ -734,141 +727,6 @@ public final class DataProcessorTest { verifyExpectedTimestampSlots(startTimestamp, endTimestamp, expectedTimestamps); } - @Test - public void getDailyTimestamps_notEnoughData_returnEmptyList() { - assertThat(DataProcessor.getDailyTimestamps(new ArrayList<>())).isEmpty(); - assertThat(DataProcessor.getDailyTimestamps(List.of(100L))).isEmpty(); - } - - @Test - public void getDailyTimestamps_allDataInOneHour_returnExpectedList() { - // Timezone GMT+8 - final List timestamps = List.of( - 1640970006000L, // 2022-01-01 01:00:06 - 1640973608000L // 2022-01-01 01:00:08 - ); - - final List expectedTimestamps = List.of( - 1640970006000L, // 2022-01-01 01:00:06 - 1640973608000L // 2022-01-01 01:00:08 - ); - assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); - } - - @Test - public void getDailyTimestamps_OneHourDataPerDay_returnExpectedList() { - // Timezone GMT+8 - final List timestamps = List.of( - 1641049200000L, // 2022-01-01 23:00:00 - 1641052800000L, // 2022-01-02 00:00:00 - 1641056400000L // 2022-01-02 01:00:00 - ); - - final List expectedTimestamps = List.of( - 1641049200000L, // 2022-01-01 23:00:00 - 1641052800000L, // 2022-01-02 00:00:00 - 1641056400000L // 2022-01-02 01:00:00 - ); - assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); - } - - @Test - public void getDailyTimestamps_OneDayData_returnExpectedList() { - // Timezone GMT+8 - final List timestamps = List.of( - 1640966400000L, // 2022-01-01 00:00:00 - 1640970000000L, // 2022-01-01 01:00:00 - 1640973600000L, // 2022-01-01 02:00:00 - 1640977200000L, // 2022-01-01 03:00:00 - 1640980800000L // 2022-01-01 04:00:00 - ); - - final List expectedTimestamps = List.of( - 1640966400000L, // 2022-01-01 00:00:00 - 1640980800000L // 2022-01-01 04:00:00 - ); - assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); - } - - @Test - public void getDailyTimestamps_MultipleDaysData_returnExpectedList() { - // Timezone GMT+8 - final List timestamps = List.of( - 1641045600000L, // 2022-01-01 22:00:00 - 1641060000000L, // 2022-01-02 02:00:00 - 1641160800000L, // 2022-01-03 06:00:00 - 1641232800000L // 2022-01-04 02:00:00 - ); - - final List expectedTimestamps = List.of( - 1641045600000L, // 2022-01-01 22:00:00 - 1641052800000L, // 2022-01-02 00:00:00 - 1641139200000L, // 2022-01-03 00:00:00 - 1641225600000L, // 2022-01-04 00:00:00 - 1641232800000L // 2022-01-04 02:00:00 - ); - assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); - } - - @Test - public void getDailyTimestamps_FirstDayOneHourData_returnExpectedList() { - // Timezone GMT+8 - final List timestamps = List.of( - 1641049200000L, // 2022-01-01 23:00:00 - 1641060000000L, // 2022-01-02 02:00:00 - 1641160800000L, // 2022-01-03 06:00:00 - 1641254400000L // 2022-01-04 08:00:00 - ); - - final List expectedTimestamps = List.of( - 1641049200000L, // 2022-01-01 23:00:00 - 1641052800000L, // 2022-01-02 00:00:00 - 1641139200000L, // 2022-01-03 00:00:00 - 1641225600000L, // 2022-01-04 00:00:00 - 1641254400000L // 2022-01-04 08:00:00 - ); - assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); - } - - @Test - public void getDailyTimestamps_LastDayNoData_returnExpectedList() { - // Timezone GMT+8 - final List timestamps = List.of( - 1640988000000L, // 2022-01-01 06:00:00 - 1641060000000L, // 2022-01-02 02:00:00 - 1641160800000L, // 2022-01-03 06:00:00 - 1641225600000L // 2022-01-04 00:00:00 - ); - - final List expectedTimestamps = List.of( - 1640988000000L, // 2022-01-01 06:00:00 - 1641052800000L, // 2022-01-02 00:00:00 - 1641139200000L, // 2022-01-03 00:00:00 - 1641225600000L // 2022-01-04 00:00:00 - ); - assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); - } - - @Test - public void getDailyTimestamps_LastDayOneHourData_returnExpectedList() { - // Timezone GMT+8 - final List timestamps = List.of( - 1640988000000L, // 2022-01-01 06:00:00 - 1641060000000L, // 2022-01-02 02:00:00 - 1641160800000L, // 2022-01-03 06:00:00 - 1641229200000L // 2022-01-04 01:00:00 - ); - - final List expectedTimestamps = List.of( - 1640988000000L, // 2022-01-01 06:00:00 - 1641052800000L, // 2022-01-02 00:00:00 - 1641139200000L, // 2022-01-03 00:00:00 - 1641225600000L, // 2022-01-04 00:00:00 - 1641229200000L // 2022-01-04 01:00:00 - ); - assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); - } - @Test public void isFromFullCharge_emptyData_returnFalse() { assertThat(DataProcessor.isFromFullCharge(null)).isFalse(); @@ -916,20 +774,53 @@ public final class DataProcessorTest { } @Test - public void getBatteryUsageMap_emptyHistoryMap_returnNull() { + public void getBatteryDiffDataMap_emptyHistoryMap_returnEmpty() { final List hourlyBatteryLevelsPerDay = new ArrayList<>(); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>())); + new BatteryLevelData.PeriodBatteryLevelData(new ArrayMap<>(), new ArrayList<>())); - assertThat(DataProcessor.getBatteryUsageMap( - mContext, hourlyBatteryLevelsPerDay, new HashMap<>(), /*appUsagePeriodMap=*/ null)) - .isNull(); + assertThat(DataProcessor.getBatteryDiffDataMap(mContext, hourlyBatteryLevelsPerDay, + new HashMap<>(), /*appUsagePeriodMap=*/ null, Set.of(), Set.of())).isEmpty(); } @Test - public void getBatteryUsageMap_returnsExpectedResult() { + public void getBatteryDiffDataMap_normalFlow_returnExpectedResult() { + final int userId = mContext.getUserId(); + final long[] batteryHistoryKeys = new long[]{ + 1641045600000L, // 2022-01-01 22:00:00 + 1641049200000L, // 2022-01-01 23:00:00 + 1641052800000L, // 2022-01-02 00:00:00 + }; + final Map> batteryHistoryMap = Map.of( + batteryHistoryKeys[0], Map.of(FAKE_PACKAGE_NAME, createBatteryHistEntry( + FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0, 0, 0, + 0, 0, 0L, userId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, 0L, 0L, false)), + batteryHistoryKeys[1], Map.of(FAKE_PACKAGE_NAME, createBatteryHistEntry( + FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 5, 0, 0, + 0, 0, 0L, userId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, 0L, 0L, false)), + batteryHistoryKeys[2], Map.of(FAKE_PACKAGE_NAME, createBatteryHistEntry( + FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 16, 0, 0, + 0, 0, 0L, userId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, 0L, 0L, false))); + final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys); + final Map>>>> + appUsagePeriodMap = Map.of(0, Map.of(0, Map.of(Long.valueOf(userId), Map.of( + FAKE_PACKAGE_NAME, List.of(buildAppUsagePeriod(0, 6)))))); + + Map batteryDiffDataMap = DataProcessor.getBatteryDiffDataMap( + mContext, batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap, + appUsagePeriodMap, Set.of(), Set.of()); + + assertThat(batteryDiffDataMap).hasSize(1); + assertThat(batteryDiffDataMap).containsKey(batteryHistoryKeys[0]); + BatteryDiffData batteryDiffData = batteryDiffDataMap.get(batteryHistoryKeys[0]); + assertThat(batteryDiffData.getStartTimestamp()).isEqualTo(batteryHistoryKeys[0]); + assertThat(batteryDiffData.getEndTimestamp()).isEqualTo(batteryHistoryKeys[2]); + } + + @Test + public void generateBatteryUsageMap_returnsExpectedResult() { final long[] batteryHistoryKeys = new long[]{ 1641045600000L, // 2022-01-01 22:00:00 1641049200000L, // 2022-01-01 23:00:00 @@ -940,7 +831,7 @@ public final class DataProcessorTest { final Map> batteryHistoryMap = new HashMap<>(); final int currentUserId = mContext.getUserId(); final BatteryHistEntry fakeEntry = createBatteryHistEntry( - ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0, + FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0, /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0, /*cachedUsageConsumePower=*/ 0, /*uid=*/ 0L, currentUserId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, @@ -1030,19 +921,7 @@ public final class DataProcessorTest { entryMap.put(entry.getKey(), entry); entryMap.put(fakeEntry.getKey(), fakeEntry); batteryHistoryMap.put(batteryHistoryKeys[4], entryMap); - final List hourlyBatteryLevelsPerDay = - new ArrayList<>(); - // Adds the day 1 data. - List timestamps = - List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); - final List levels = List.of(100, 100); - hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); - // Adds the day 2 data. - timestamps = List.of(batteryHistoryKeys[2], batteryHistoryKeys[4]); - hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); - + final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys); // Adds app usage data to test screen on time. final Map>>>> appUsagePeriodMap = new HashMap<>(); @@ -1066,8 +945,12 @@ public final class DataProcessorTest { appUsagePeriodMap.get(1).put(0, appUsageMap); final Map> resultMap = - DataProcessor.getBatteryUsageMap( - mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap, appUsagePeriodMap); + DataProcessor.generateBatteryUsageMap( + mContext, + DataProcessor.getBatteryDiffDataMap(mContext, + batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap, + appUsagePeriodMap, Set.of(), Set.of()), + batteryLevelData); BatteryDiffData resultDiffData = resultMap @@ -1128,7 +1011,7 @@ public final class DataProcessorTest { } @Test - public void getBatteryUsageMap_multipleUsers_returnsExpectedResult() { + public void generateBatteryUsageMap_multipleUsers_returnsExpectedResult() { final long[] batteryHistoryKeys = new long[]{ 1641052800000L, // 2022-01-02 00:00:00 1641056400000L, // 2022-01-02 01:00:00 @@ -1217,17 +1100,15 @@ public final class DataProcessorTest { /*backgroundUsageTimeInMs=*/ 30L, /*isHidden=*/ false); entryMap.put(entry.getKey(), entry); batteryHistoryMap.put(batteryHistoryKeys[2], entryMap); - final List hourlyBatteryLevelsPerDay = - new ArrayList<>(); - List timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); - final List levels = List.of(100, 100); - hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); + final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys); final Map> resultMap = - DataProcessor.getBatteryUsageMap( - mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap, - /*appUsagePeriodMap=*/ null); + DataProcessor.generateBatteryUsageMap( + mContext, + DataProcessor.getBatteryDiffDataMap(mContext, + batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap, + /*appUsagePeriodMap=*/ null, Set.of(), Set.of()), + batteryLevelData); final BatteryDiffData resultDiffData = resultMap @@ -1247,7 +1128,7 @@ public final class DataProcessorTest { } @Test - public void getBatteryUsageMap_usageTimeExceed_returnsExpectedResult() { + public void generateBatteryUsageMap_usageTimeExceed_returnsExpectedResult() { final long[] batteryHistoryKeys = new long[]{ 1641052800000L, // 2022-01-02 00:00:00 1641056400000L, // 2022-01-02 01:00:00 @@ -1288,12 +1169,7 @@ public final class DataProcessorTest { /*backgroundUsageTimeInMs=*/ 7200000L, /*isHidden=*/ false); entryMap.put(entry.getKey(), entry); batteryHistoryMap.put(batteryHistoryKeys[2], entryMap); - final List hourlyBatteryLevelsPerDay = - new ArrayList<>(); - List timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); - final List levels = List.of(100, 100); - hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); + final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys); // Adds app usage data to test screen on time. final Map>>>> @@ -1307,8 +1183,12 @@ public final class DataProcessorTest { appUsagePeriodMap.get(0).put(0, appUsageMap); final Map> resultMap = - DataProcessor.getBatteryUsageMap( - mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap, appUsagePeriodMap); + DataProcessor.generateBatteryUsageMap( + mContext, + DataProcessor.getBatteryDiffDataMap(mContext, + batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap, + appUsagePeriodMap, Set.of(), Set.of()), + batteryLevelData); final BatteryDiffData resultDiffData = resultMap @@ -1338,7 +1218,7 @@ public final class DataProcessorTest { } @Test - public void getBatteryUsageMap_hideApplicationEntries_returnsExpectedResult() { + public void generateBatteryUsageMap_hideApplicationEntries_returnsExpectedResult() { final long[] batteryHistoryKeys = new long[]{ 1641052800000L, // 2022-01-02 00:00:00 1641056400000L, // 2022-01-02 01:00:00 @@ -1403,19 +1283,17 @@ public final class DataProcessorTest { /*backgroundUsageTimeInMs=*/ 20L, /*isHidden=*/ false); entryMap.put(entry.getKey(), entry); batteryHistoryMap.put(batteryHistoryKeys[2], entryMap); - final List hourlyBatteryLevelsPerDay = - new ArrayList<>(); - List timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); - final List levels = List.of(100, 100); - hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); + final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys); when(mPowerUsageFeatureProvider.getHideApplicationSet()) .thenReturn(Set.of("package1")); final Map> resultMap = - DataProcessor.getBatteryUsageMap( - mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap, - /*appUsagePeriodMap=*/ null); + DataProcessor.generateBatteryUsageMap( + mContext, + DataProcessor.getBatteryDiffDataMap(mContext, + batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap, + /*appUsagePeriodMap=*/ null, Set.of(), Set.of()), + batteryLevelData); final BatteryDiffData resultDiffData = resultMap @@ -1431,7 +1309,7 @@ public final class DataProcessorTest { } @Test - public void getBatteryUsageMap_hideBackgroundUsageTime_returnsExpectedResult() { + public void generateBatteryUsageMap_hideBackgroundUsageTime_returnsExpectedResult() { final long[] batteryHistoryKeys = new long[]{ 1641052800000L, // 2022-01-02 00:00:00 1641056400000L, // 2022-01-02 01:00:00 @@ -1496,19 +1374,17 @@ public final class DataProcessorTest { /*backgroundUsageTimeInMs=*/ 20L, /*isHidden=*/ false); entryMap.put(entry.getKey(), entry); batteryHistoryMap.put(batteryHistoryKeys[2], entryMap); - final List hourlyBatteryLevelsPerDay = - new ArrayList<>(); - List timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]); - final List levels = List.of(100, 100); - hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels)); + final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys); when(mPowerUsageFeatureProvider.getHideBackgroundUsageTimeSet()) .thenReturn(new HashSet(Arrays.asList((CharSequence) "package2"))); final Map> resultMap = - DataProcessor.getBatteryUsageMap( - mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap, - /*appUsagePeriodMap=*/ null); + DataProcessor.generateBatteryUsageMap( + mContext, + DataProcessor.getBatteryDiffDataMap(mContext, + batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap, + /*appUsagePeriodMap=*/ null, Set.of(), Set.of()), + batteryLevelData); final BatteryDiffData resultDiffData = resultMap @@ -1523,7 +1399,10 @@ public final class DataProcessorTest { @Test public void generateBatteryDiffData_emptyBatteryEntryList_returnNull() { assertThat(DataProcessor.generateBatteryDiffData(mContext, - DataProcessor.convertToBatteryHistEntry(null, mBatteryUsageStats))).isNull(); + System.currentTimeMillis(), + DataProcessor.convertToBatteryHistEntry(null, mBatteryUsageStats), + /* systemAppsPackageNames= */ Set.of(), + /* systemAppsUids= */ Set.of())).isNull(); } @Test @@ -1574,7 +1453,10 @@ public final class DataProcessorTest { .when(mMockBatteryEntry4).getPowerComponentId(); final BatteryDiffData batteryDiffData = DataProcessor.generateBatteryDiffData(mContext, - DataProcessor.convertToBatteryHistEntry(batteryEntryList, mBatteryUsageStats)); + System.currentTimeMillis(), + DataProcessor.convertToBatteryHistEntry(batteryEntryList, mBatteryUsageStats), + /* systemAppsPackageNames= */ Set.of(), + /* systemAppsUids= */ Set.of()); assertBatteryDiffEntry( batteryDiffData.getAppDiffEntryList().get(0), 0, /*uid=*/ 2L, @@ -2041,9 +1923,9 @@ public final class DataProcessorTest { final double backgroundUsageConsumePower, final double cachedUsageConsumePower, final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs, final long screenOnTimeInMs) { - assertThat(entry.mBatteryHistEntry.mUserId).isEqualTo(userId); - assertThat(entry.mBatteryHistEntry.mUid).isEqualTo(uid); - assertThat(entry.mBatteryHistEntry.mConsumerType).isEqualTo(consumerType); + assertThat(entry.mUserId).isEqualTo(userId); + assertThat(entry.mUid).isEqualTo(uid); + assertThat(entry.mConsumerType).isEqualTo(consumerType); assertThat(entry.getPercentage()).isEqualTo(consumePercentage); assertThat(entry.mForegroundUsageConsumePower).isEqualTo(foregroundUsageConsumePower); assertThat(entry.mForegroundServiceUsageConsumePower) @@ -2054,4 +1936,12 @@ public final class DataProcessorTest { assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs); assertThat(entry.mScreenOnTimeInMs).isEqualTo(screenOnTimeInMs); } + + private BatteryLevelData generateBatteryLevelData(long[] timestamps) { + Map batteryLevelMap = new ArrayMap<>(); + for (long timestamp : timestamps) { + batteryLevelMap.put(timestamp, 100); + } + return new BatteryLevelData(batteryLevelMap); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java index 8a1ba137958..f72b333b570 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java @@ -173,8 +173,8 @@ public final class DatabaseUtilsTest { doReturn(null).when(mContext).registerReceiver(any(), any()); assertThat( DatabaseUtils.sendBatteryEntryData( - mContext, /*batteryEntryList=*/ null, mBatteryUsageStats, - /*isFullChargeStart=*/ false)) + mContext, System.currentTimeMillis(), /*batteryEntryList=*/ null, + mBatteryUsageStats, /*isFullChargeStart=*/ false)) .isNull(); } @@ -193,7 +193,10 @@ public final class DatabaseUtilsTest { final List valuesList = DatabaseUtils.sendBatteryEntryData( - mContext, batteryEntryList, mBatteryUsageStats, + mContext, + System.currentTimeMillis(), + batteryEntryList, + mBatteryUsageStats, /*isFullChargeStart=*/ false); assertThat(valuesList).hasSize(2); @@ -216,6 +219,7 @@ public final class DatabaseUtilsTest { final List valuesList = DatabaseUtils.sendBatteryEntryData( mContext, + System.currentTimeMillis(), new ArrayList<>(), mBatteryUsageStats, /*isFullChargeStart=*/ false); @@ -235,6 +239,7 @@ public final class DatabaseUtilsTest { final List valuesList = DatabaseUtils.sendBatteryEntryData( mContext, + System.currentTimeMillis(), /*batteryEntryList=*/ null, mBatteryUsageStats, /*isFullChargeStart=*/ false); @@ -254,6 +259,7 @@ public final class DatabaseUtilsTest { final List valuesList = DatabaseUtils.sendBatteryEntryData( mContext, + System.currentTimeMillis(), /*batteryEntryList=*/ null, /*batteryUsageStats=*/ null, /*isFullChargeStart=*/ false); @@ -359,7 +365,7 @@ public final class DatabaseUtilsTest { } @Test - public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() { + public void getHistoryMap_emptyCursorContent_returnEmptyMap() { final MatrixCursor cursor = new MatrixCursor( new String[] { BatteryHistEntry.KEY_UID, @@ -367,36 +373,33 @@ public final class DatabaseUtilsTest { BatteryHistEntry.KEY_TIMESTAMP}); DatabaseUtils.sFakeSupplier = () -> cursor; - assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge( - mContext, /*calendar=*/ null)).isEmpty(); + assertThat(DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, 0)).isEmpty(); } @Test - public void getHistoryMapSinceLastFullCharge_nullCursor_returnEmptyMap() { + public void getHistoryMap_nullCursor_returnEmptyMap() { DatabaseUtils.sFakeSupplier = () -> null; - assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge( - mContext, /*calendar=*/ null)).isEmpty(); + assertThat(DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, 0)).isEmpty(); } @Test - public void getHistoryMapSinceLastFullCharge_returnExpectedMap() { + public void getHistoryMap_returnExpectedMap() { final Long timestamp1 = Long.valueOf(1001L); final Long timestamp2 = Long.valueOf(1002L); final MatrixCursor cursor = getMatrixCursor(); // Adds fake data into the cursor. cursor.addRow(new Object[] { - "app name1", timestamp1, 1, ConvertUtils.CONSUMER_TYPE_UID_BATTERY}); + "app name1", timestamp1, 1, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, true}); cursor.addRow(new Object[] { - "app name2", timestamp2, 2, ConvertUtils.CONSUMER_TYPE_UID_BATTERY}); + "app name2", timestamp2, 2, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, false}); cursor.addRow(new Object[] { - "app name3", timestamp2, 3, ConvertUtils.CONSUMER_TYPE_UID_BATTERY}); + "app name3", timestamp2, 3, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, false}); cursor.addRow(new Object[] { - "app name4", timestamp2, 4, ConvertUtils.CONSUMER_TYPE_UID_BATTERY}); + "app name4", timestamp2, 4, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, false}); DatabaseUtils.sFakeSupplier = () -> cursor; final Map> batteryHistMap = - DatabaseUtils.getHistoryMapSinceLastFullCharge( - mContext, /*calendar=*/ null); + DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, timestamp1); assertThat(batteryHistMap).hasSize(2); // Verifies the BatteryHistEntry data for timestamp1. @@ -412,7 +415,7 @@ public final class DatabaseUtilsTest { } @Test - public void getHistoryMapSinceLastFullCharge_withWorkProfile_returnExpectedMap() + public void getHistoryMap_withWorkProfile_returnExpectedMap() throws PackageManager.NameNotFoundException { doReturn("com.fake.package").when(mContext).getPackageName(); doReturn(mMockContext).when(mContext).createPackageContextAsUser( @@ -425,8 +428,7 @@ public final class DatabaseUtilsTest { DatabaseUtils.sFakeSupplier = () -> getMatrixCursor(); final Map> batteryHistMap = - DatabaseUtils.getHistoryMapSinceLastFullCharge( - mContext, /*calendar=*/ null); + DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, 0); assertThat(batteryHistMap).isEmpty(); } @@ -571,6 +573,7 @@ public final class DatabaseUtilsTest { BatteryHistEntry.KEY_PACKAGE_NAME, BatteryHistEntry.KEY_TIMESTAMP, BatteryHistEntry.KEY_UID, - BatteryHistEntry.KEY_CONSUMER_TYPE}); + BatteryHistEntry.KEY_CONSUMER_TYPE, + BatteryHistEntry.KEY_IS_FULL_CHARGE_CYCLE_START}); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBaseTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBaseTest.java index 6ed10cd2b9b..68766e69dac 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBaseTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBaseTest.java @@ -134,11 +134,6 @@ public class PowerUsageBaseTest { return 0; } - @Override - protected boolean isBatteryHistoryNeeded() { - return false; - } - @Override protected void refreshUi(int refreshType) { // Do nothing diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java index 941f444e552..84628670af1 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java @@ -16,6 +16,10 @@ package com.android.settings.fuelgauge.batteryusage.db; +import static com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity.KEY_BATTERY_EVENT_TYPE; +import static com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity.KEY_BATTERY_LEVEL; +import static com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity.KEY_TIMESTAMP; + import static com.google.common.truth.Truth.assertThat; import android.content.Context; @@ -31,9 +35,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import java.util.List; + /** Tests for {@link BatteryEventDao}. */ @RunWith(RobolectricTestRunner.class) public final class BatteryEventDaoTest { + private static final long TIMESTAMP1 = System.currentTimeMillis(); + private static final long TIMESTAMP2 = TIMESTAMP1 + 2; + private Context mContext; private BatteryStateDatabase mDatabase; private BatteryEventDao mBatteryEventDao; @@ -51,8 +60,44 @@ public final class BatteryEventDaoTest { BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null); } + @Test - public void getAllAfter_returnExpectedResult() { + public void getLastFullChargeTimestamp_normalFlow_expectedBehavior() throws Exception { + mBatteryEventDao.insert(BatteryEventEntity.newBuilder() + .setTimestamp(TIMESTAMP1) + .setBatteryEventType(3) + .setBatteryLevel(100) + .build()); + mBatteryEventDao.insert(BatteryEventEntity.newBuilder() + .setTimestamp(TIMESTAMP2) + .setBatteryEventType(4) + .setBatteryLevel(96) + .build()); + + final Cursor cursor = mBatteryEventDao.getLastFullChargeTimestamp(); + assertThat(cursor.getCount()).isEqualTo(1); + cursor.moveToFirst(); + assertThat(cursor.getLong(0)).isEqualTo(TIMESTAMP1); + } + + @Test + public void getLastFullChargeTimestamp_noLastFullChargeTime_returns0() throws Exception { + mBatteryEventDao.clearAll(); + mBatteryEventDao.insert(BatteryEventEntity.newBuilder() + .setTimestamp(TIMESTAMP2) + .setBatteryEventType(4) + .setBatteryLevel(96) + .build()); + + final Cursor cursor = mBatteryEventDao.getLastFullChargeTimestamp(); + + assertThat(cursor.getCount()).isEqualTo(1); + cursor.moveToFirst(); + assertThat(cursor.getLong(0)).isEqualTo(0L); + } + + @Test + public void getAllAfter_normalFlow_returnExpectedResult() { mBatteryEventDao.insert(BatteryEventEntity.newBuilder() .setTimestamp(100L) .setBatteryEventType(1) @@ -64,17 +109,44 @@ public final class BatteryEventDaoTest { .setBatteryLevel(88) .build()); - final Cursor cursor = mBatteryEventDao.getAllAfter(160L); + final Cursor cursor = mBatteryEventDao.getAllAfter(160L, List.of(1, 2)); assertThat(cursor.getCount()).isEqualTo(1); cursor.moveToFirst(); - assertThat(cursor.getLong(cursor.getColumnIndex(BatteryEventEntity.KEY_TIMESTAMP))) + assertThat(cursor.getLong(cursor.getColumnIndex(KEY_TIMESTAMP))) .isEqualTo(200L); - assertThat(cursor.getInt(cursor.getColumnIndex(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE))) + assertThat(cursor.getInt(cursor.getColumnIndex(KEY_BATTERY_EVENT_TYPE))) .isEqualTo(2); - assertThat(cursor.getInt(cursor.getColumnIndex(BatteryEventEntity.KEY_BATTERY_LEVEL))) + assertThat(cursor.getInt(cursor.getColumnIndex(KEY_BATTERY_LEVEL))) .isEqualTo(88); mBatteryEventDao.clearAll(); assertThat(mBatteryEventDao.getAll()).isEmpty(); } + + @Test + public void getAllAfter_filterBatteryTypes_returnExpectedResult() { + mBatteryEventDao.insert(BatteryEventEntity.newBuilder() + .setTimestamp(100L) + .setBatteryEventType(1) + .setBatteryLevel(66) + .build()); + mBatteryEventDao.insert(BatteryEventEntity.newBuilder() + .setTimestamp(200L) + .setBatteryEventType(2) + .setBatteryLevel(88) + .build()); + + final Cursor cursor = mBatteryEventDao.getAllAfter(0L, List.of(1)); + assertThat(cursor.getCount()).isEqualTo(1); + cursor.moveToFirst(); + assertThat(cursor.getLong(cursor.getColumnIndex(KEY_TIMESTAMP))) + .isEqualTo(100L); + assertThat(cursor.getInt(cursor.getColumnIndex(KEY_BATTERY_EVENT_TYPE))) + .isEqualTo(1); + assertThat(cursor.getInt(cursor.getColumnIndex(KEY_BATTERY_LEVEL))) + .isEqualTo(66); + + mBatteryEventDao.clearAll(); + assertThat(mBatteryEventDao.getAll()).isEmpty(); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java index 57cf648c07a..b3dba4e56da 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java @@ -37,9 +37,10 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public final class BatteryStateDaoTest { private static final int CURSOR_COLUMN_SIZE = 9; - private static final long TIMESTAMP1 = System.currentTimeMillis(); - private static final long TIMESTAMP2 = System.currentTimeMillis() + 2; - private static final long TIMESTAMP3 = System.currentTimeMillis() + 4; + private static final long CURRENT = System.currentTimeMillis(); + private static final long TIMESTAMP1 = CURRENT; + private static final long TIMESTAMP2 = CURRENT + 2; + private static final long TIMESTAMP3 = CURRENT + 4; private static final String PACKAGE_NAME1 = "com.android.apps.settings"; private static final String PACKAGE_NAME2 = "com.android.apps.calendar"; private static final String PACKAGE_NAME3 = "com.android.apps.gmail"; @@ -67,7 +68,7 @@ public final class BatteryStateDaoTest { } @Test - public void batteryStateDao_insertAll() throws Exception { + public void insertAll_normalFlow_expectedBehavior() throws Exception { final List states = mBatteryStateDao.getAllAfter(TIMESTAMP1); assertThat(states).hasSize(2); // Verifies the queried battery states. @@ -76,8 +77,26 @@ public final class BatteryStateDaoTest { } @Test - public void batteryStateDao_getCursorSinceLastFullCharge() throws Exception { - final Cursor cursor1 = mBatteryStateDao.getCursorSinceLastFullCharge(TIMESTAMP1); + public void getLatestTimestamp_normalFlow_expectedBehavior() throws Exception { + final Cursor cursor1 = mBatteryStateDao.getLatestTimestampBefore(TIMESTAMP1 - 1); + assertThat(cursor1.getCount()).isEqualTo(1); + cursor1.moveToFirst(); + assertThat(cursor1.getLong(0)).isEqualTo(0L); + + final Cursor cursor2 = mBatteryStateDao.getLatestTimestampBefore(TIMESTAMP2); + assertThat(cursor2.getCount()).isEqualTo(1); + cursor2.moveToFirst(); + assertThat(cursor2.getLong(0)).isEqualTo(TIMESTAMP2); + + final Cursor cursor3 = mBatteryStateDao.getLatestTimestampBefore(TIMESTAMP3 + 1); + assertThat(cursor3.getCount()).isEqualTo(1); + cursor3.moveToFirst(); + assertThat(cursor3.getLong(0)).isEqualTo(TIMESTAMP3); + } + + @Test + public void getBatteryStatesAfter_normalFlow_expectedBehavior() throws Exception { + final Cursor cursor1 = mBatteryStateDao.getBatteryStatesAfter(TIMESTAMP1); assertThat(cursor1.getCount()).isEqualTo(3); assertThat(cursor1.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE); // Verifies the queried first battery state. @@ -90,7 +109,7 @@ public final class BatteryStateDaoTest { cursor1.moveToNext(); assertThat(cursor1.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME3); - final Cursor cursor2 = mBatteryStateDao.getCursorSinceLastFullCharge(TIMESTAMP3); + final Cursor cursor2 = mBatteryStateDao.getBatteryStatesAfter(TIMESTAMP3); assertThat(cursor2.getCount()).isEqualTo(1); assertThat(cursor2.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE); // Verifies the queried first battery state. @@ -99,25 +118,7 @@ public final class BatteryStateDaoTest { } @Test - public void batteryStateDao_getCursorSinceLastFullCharge_noFullChargeData_returnSevenDaysData() - throws Exception { - mBatteryStateDao.clearAll(); - BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP3, PACKAGE_NAME3); - BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP2, PACKAGE_NAME2); - BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP1, PACKAGE_NAME1); - final Cursor cursor = mBatteryStateDao.getCursorSinceLastFullCharge(TIMESTAMP2); - assertThat(cursor.getCount()).isEqualTo(2); - assertThat(cursor.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE); - // Verifies the queried first battery state. - cursor.moveToFirst(); - assertThat(cursor.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME2); - // Verifies the queried third battery state. - cursor.moveToNext(); - assertThat(cursor.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME3); - } - - @Test - public void batteryStateDao_clearAllBefore() throws Exception { + public void clearAllBefore_normalFlow_expectedBehavior() throws Exception { mBatteryStateDao.clearAllBefore(TIMESTAMP2); final List states = mBatteryStateDao.getAllAfter(0); @@ -127,20 +128,20 @@ public final class BatteryStateDaoTest { } @Test - public void batteryStateDao_clearAll() throws Exception { + public void clearAll_normalFlow_expectedBehavior() throws Exception { assertThat(mBatteryStateDao.getAllAfter(0)).hasSize(3); mBatteryStateDao.clearAll(); assertThat(mBatteryStateDao.getAllAfter(0)).isEmpty(); } @Test - public void getInstance_createNewInstance() throws Exception { + public void getInstance_createNewInstance_returnsExpectedResult() throws Exception { BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null); assertThat(BatteryStateDatabase.getInstance(mContext)).isNotNull(); } @Test - public void getDistinctTimestampCount_returnsExpectedResult() { + public void getDistinctTimestampCount_normalFlow_returnsExpectedResult() { assertThat(mBatteryStateDao.getDistinctTimestampCount(/*timestamp=*/ 0)) .isEqualTo(3); assertThat(mBatteryStateDao.getDistinctTimestampCount(TIMESTAMP1)) @@ -148,7 +149,7 @@ public final class BatteryStateDaoTest { } @Test - public void getDistinctTimestamps_returnsExpectedResult() { + public void getDistinctTimestamps_normalFlow_returnsExpectedResult() { final List timestamps = mBatteryStateDao.getDistinctTimestamps(/*timestamp=*/ 0); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDaoTest.java new file mode 100644 index 00000000000..6f739546a5f --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDaoTest.java @@ -0,0 +1,110 @@ +/* + * 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.db; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.database.Cursor; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.BatteryTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.List; + +/** Tests for {@link BatteryUsageSlotDao}. */ +@RunWith(RobolectricTestRunner.class) +public final class BatteryUsageSlotDaoTest { + private static final int CURSOR_COLUMN_SIZE = 3; + private static final long CURRENT = System.currentTimeMillis(); + private static final long TIMESTAMP1 = CURRENT; + private static final long TIMESTAMP2 = CURRENT + 2; + private static final String BATTERY_USAGE_SLOT_STRING1 = "BATTERY_USAGE_SLOT_STRING1"; + private static final String BATTERY_USAGE_SLOT_STRING2 = "BATTERY_USAGE_SLOT_STRING2"; + + private Context mContext; + private BatteryStateDatabase mDatabase; + private BatteryUsageSlotDao mBatteryUsageSlotDao; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext); + mBatteryUsageSlotDao = mDatabase.batteryUsageSlotDao(); + mBatteryUsageSlotDao.insert( + new BatteryUsageSlotEntity(TIMESTAMP1, BATTERY_USAGE_SLOT_STRING1)); + mBatteryUsageSlotDao.insert( + new BatteryUsageSlotEntity(TIMESTAMP2, BATTERY_USAGE_SLOT_STRING2)); + } + + @After + public void closeDb() { + mDatabase.close(); + BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null); + } + + @Test + public void getAll_normalFlow_expectedBehavior() throws Exception { + final List entities = mBatteryUsageSlotDao.getAll(); + assertThat(entities).hasSize(2); + assertThat(entities.get(0).timestamp).isEqualTo(TIMESTAMP1); + assertThat(entities.get(0).batteryUsageSlot).isEqualTo(BATTERY_USAGE_SLOT_STRING1); + assertThat(entities.get(1).timestamp).isEqualTo(TIMESTAMP2); + assertThat(entities.get(1).batteryUsageSlot).isEqualTo(BATTERY_USAGE_SLOT_STRING2); + } + + @Test + public void getAllAfter_normalFlow_expectedBehavior() throws Exception { + final Cursor cursor1 = mBatteryUsageSlotDao.getAllAfter(TIMESTAMP1); + assertThat(cursor1.getCount()).isEqualTo(2); + assertThat(cursor1.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE); + cursor1.moveToFirst(); + assertThat(cursor1.getLong(1 /*timestamp*/)).isEqualTo(TIMESTAMP1); + cursor1.moveToNext(); + assertThat(cursor1.getLong(1 /*timestamp*/)).isEqualTo(TIMESTAMP2); + + final Cursor cursor2 = mBatteryUsageSlotDao.getAllAfter(TIMESTAMP1 + 1); + assertThat(cursor2.getCount()).isEqualTo(1); + assertThat(cursor2.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE); + cursor2.moveToFirst(); + assertThat(cursor2.getLong(1 /*timestamp*/)).isEqualTo(TIMESTAMP2); + } + + @Test + public void clearAllBefore_normalFlow_expectedBehavior() throws Exception { + mBatteryUsageSlotDao.clearAllBefore(TIMESTAMP1); + + final List entities = mBatteryUsageSlotDao.getAll(); + assertThat(entities).hasSize(1); + assertThat(entities.get(0).timestamp).isEqualTo(TIMESTAMP2); + assertThat(entities.get(0).batteryUsageSlot).isEqualTo(BATTERY_USAGE_SLOT_STRING2); + } + + @Test + public void clearAll_normalFlow_expectedBehavior() throws Exception { + mBatteryUsageSlotDao.clearAll(); + + assertThat(mBatteryUsageSlotDao.getAll()).isEmpty(); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntityTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntityTest.java new file mode 100644 index 00000000000..ef276eb186e --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntityTest.java @@ -0,0 +1,44 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link BatteryUsageSlotEntity}. */ +@RunWith(RobolectricTestRunner.class) +public final class BatteryUsageSlotEntityTest { + + @Test + public void testBuilder_returnsExpectedResult() { + final long timestamp = 10001L; + final String batteryUsageSlotString = "batteryUsageSlotString"; + + BatteryUsageSlotEntity entity = BatteryUsageSlotEntity + .newBuilder() + .setTimestamp(timestamp) + .setBatteryUsageSlot(batteryUsageSlotString) + .build(); + + // Verifies the app relative information. + assertThat(entity.timestamp).isEqualTo(timestamp); + assertThat(entity.batteryUsageSlot).isEqualTo(batteryUsageSlotString); + } +}