diff --git a/Android.bp b/Android.bp index 44f83f38fdf..c6a62a7eed9 100644 --- a/Android.bp +++ b/Android.bp @@ -85,6 +85,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 b443773b6cd..4ec1f7b6b21 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 d2ca306480f..660d5e341bf 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -128,7 +128,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll Map> mBatteryUsageMap; private boolean mIs24HourFormat; - private boolean mHourlyChartVisible = true; private View mBatteryChartViewGroup; private TextView mChartSummaryTextView; private BatteryChartViewModel mDailyViewModel; @@ -234,20 +233,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, @@ -278,6 +265,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(); @@ -498,10 +492,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); @@ -658,10 +652,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 90a5f19d18b..f25e16f9578 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 a3b73df3aec..d51485a87d1 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 71ed46f1162..32cd1b9da5e 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 5a96fb40abc..f13f34c906e 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 a0c5843988a..e93fb624186 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() { mPowerFeatureProvider = FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); 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); + } +}