Loading src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +70 −20 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.text.format.DateUtils; import android.util.Log; import android.view.View; import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; Loading Loading @@ -111,6 +112,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private View mCategoryTitleView; private PreferenceScreen mPreferenceScreen; private FooterPreference mFooterPreference; private TextView mChartSummaryTextView; private BatteryChartViewModel mDailyViewModel; private List<BatteryChartViewModel> mHourlyViewModels; Loading @@ -121,9 +123,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private final MetricsFeatureProvider mMetricsFeatureProvider; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final AnimatorListenerAdapter mHourlyChartFadeInAdapter = createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ true); createHourlyChartAnimatorListenerAdapter(/*visible=*/ true); private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter = createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ false); createHourlyChartAnimatorListenerAdapter(/*visible=*/ false); @VisibleForTesting final DailyChartLabelTextGenerator mDailyChartLabelTextGenerator = Loading Loading @@ -289,6 +291,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll getTotalHours(batteryLevelData)); if (batteryLevelData == null) { mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; mDailyViewModel = null; mHourlyViewModels = null; refreshUi(); Loading Loading @@ -321,6 +325,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView)); animateBatteryChartViewGroup(); } if (mBatteryChartViewGroup != null) { final View grandparentView = (View) mBatteryChartViewGroup.getParent(); mChartSummaryTextView = grandparentView != null ? grandparentView.findViewById(R.id.chart_summary) : null; } } private void setBatteryChartViewInner(@NonNull final BatteryChartView dailyChartView, Loading Loading @@ -367,8 +376,45 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll // Chart views are not initialized. return false; } if (mDailyViewModel == null || mHourlyViewModels == null) { // Fail to get battery level data, show an empty hourly chart view. // When mDailyViewModel or mHourlyViewModels is null, there is no battery level data. // This is mainly in 2 cases: // 1) battery data is within 2 hours // 2) no battery data in the latest 7 days (power off >= 7 days) final boolean refreshUiResult = mDailyViewModel == null || mHourlyViewModels == null ? refreshUiWithNoLevelDataCase() : refreshUiWithLevelDataCase(); if (!refreshUiResult) { return false; } mHandler.post(() -> { final long start = System.currentTimeMillis(); removeAndCacheAllPrefs(); addAllPreferences(); refreshCategoryTitle(); Log.d(TAG, String.format("refreshUi is finished in %d/ms", (System.currentTimeMillis() - start))); }); return true; } private boolean refreshUiWithNoLevelDataCase() { setChartSummaryVisible(false); if (mBatteryUsageMap == null) { // There is no battery level data and battery usage data is not ready, wait for data // ready to refresh UI. Show nothing temporarily. mDailyChartView.setVisibility(View.GONE); mHourlyChartView.setVisibility(View.GONE); mDailyChartView.setViewModel(null); mHourlyChartView.setViewModel(null); return false; } else if (mBatteryUsageMap .get(BatteryChartViewModel.SELECTED_INDEX_ALL) .get(BatteryChartViewModel.SELECTED_INDEX_ALL) == null) { // There is no battery level data and battery usage data, show an empty hourly chart // view. mDailyChartView.setVisibility(View.GONE); mHourlyChartView.setVisibility(View.VISIBLE); mHourlyChartView.setViewModel(null); Loading @@ -376,7 +422,12 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll addFooterPreferenceIfNeeded(false); return false; } return true; } private boolean refreshUiWithLevelDataCase() { setChartSummaryVisible(true); // Gets valid battery level data. if (isBatteryLevelDataInOneDay()) { // Only 1 day data, hide the daily chart view. mDailyChartView.setVisibility(View.GONE); Loading @@ -389,10 +440,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) { // Multiple days are selected, hide the hourly chart view. animateBatteryHourlyChartView(/*isToShow=*/ false); animateBatteryHourlyChartView(/*visible=*/ false); } else { animateBatteryHourlyChartView(/*isToShow=*/ true); final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex); animateBatteryHourlyChartView(/*visible=*/ true); final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex); hourlyViewModel.setSelectedIndex(mHourlyChartIndex); mHourlyChartView.setViewModel(hourlyViewModel); } Loading @@ -401,14 +453,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll // Battery usage data is not ready, wait for data ready to refresh UI. return false; } mHandler.post(() -> { final long start = System.currentTimeMillis(); removeAndCacheAllPrefs(); addAllPreferences(); refreshCategoryTitle(); Log.d(TAG, String.format("refreshUi is finished in %d/ms", (System.currentTimeMillis() - start))); }); return true; } Loading @@ -427,7 +471,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll if (!batteryDiffData.getAppDiffEntryList().isEmpty()) { addPreferenceToScreen(batteryDiffData.getAppDiffEntryList()); } // Adds the expabable divider if we have system entries data. // Adds the expandable divider if we have system entries data. if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) { if (mExpandDividerPreference == null) { mExpandDividerPreference = new ExpandDividerPreference(mPrefContext); Loading Loading @@ -645,12 +689,12 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } } private void animateBatteryHourlyChartView(final boolean isToShow) { private void animateBatteryHourlyChartView(final boolean visible) { if (mHourlyChartView == null) { return; } if (isToShow) { if (visible) { mHourlyChartView.setAlpha(0f); mHourlyChartView.setVisibility(View.VISIBLE); mHourlyChartView.animate() Loading @@ -667,9 +711,15 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } } private void setChartSummaryVisible(final boolean visible) { if (mChartSummaryTextView != null) { mChartSummaryTextView.setVisibility(visible ? View.VISIBLE : View.GONE); } } private AnimatorListenerAdapter createHourlyChartAnimatorListenerAdapter( final boolean isToShow) { final int visibility = isToShow ? View.VISIBLE : View.GONE; final boolean visible) { final int visibility = visible ? View.VISIBLE : View.GONE; return new AnimatorListenerAdapter() { @Override Loading src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java +16 −6 Original line number Diff line number Diff line Loading @@ -55,11 +55,6 @@ public final class ConvertUtils { // Maximum total time value for each slot cumulative data at most 2 hours. private static final float TOTAL_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2; // Keys for metric metadata. static final int METRIC_KEY_PACKAGE = 1; static final int METRIC_KEY_BATTERY_LEVEL = 2; static final int METRIC_KEY_BATTERY_USAGE = 3; @VisibleForTesting static double PERCENTAGE_OF_TOTAL_THRESHOLD = 1f; Loading Loading @@ -87,7 +82,7 @@ public final class ConvertUtils { } /** Converts to content values */ public static ContentValues convert( public static ContentValues convertToContentValues( BatteryEntry entry, BatteryUsageStats batteryUsageStats, int batteryLevel, Loading Loading @@ -130,6 +125,21 @@ public final class ConvertUtils { return values; } /** Converts to {@link BatteryHistEntry} */ public static BatteryHistEntry convertToBatteryHistEntry( BatteryEntry entry, BatteryUsageStats batteryUsageStats) { return new BatteryHistEntry( convertToContentValues( entry, batteryUsageStats, /*batteryLevel=*/ 0, /*batteryStatus=*/ 0, /*batteryHealth=*/ 0, /*bootTimestamp=*/ 0, /*timestamp=*/ 0)); } /** Converts UTC timestamp to human readable local time string. */ public static String utcToLocalTime(Context context, long timestamp) { final Locale locale = getLocale(context); Loading src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +241 −33 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import android.app.settings.SettingsEnums; import android.content.ContentValues; import android.content.Context; import android.os.AsyncTask; import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; Loading Loading @@ -50,6 +53,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * A utility class to process data loaded from database and make the data easy to use for battery Loading Loading @@ -97,7 +101,9 @@ public final class DataProcessor { @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap, final UsageMapAsyncResponse asyncResponseDelegate) { if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { Log.d(TAG, "getBatteryLevelData() returns null"); Log.d(TAG, "batteryHistoryMap is null in getBatteryLevelData()"); loadBatteryUsageDataFromBatteryStatsService( context, handler, asyncResponseDelegate); return null; } handler = handler != null ? handler : new Handler(Looper.getMainLooper()); Loading @@ -107,16 +113,20 @@ public final class DataProcessor { // Wrap and processed history map into easy-to-use format for UI rendering. final BatteryLevelData batteryLevelData = getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap); if (batteryLevelData == null) { loadBatteryUsageDataFromBatteryStatsService( context, handler, asyncResponseDelegate); Log.d(TAG, "getBatteryLevelData() returns null"); return null; } // Start the async task to compute diff usage data and load labels and icons. if (batteryLevelData != null) { new ComputeUsageMapAndLoadItemsTask( context, handler, asyncResponseDelegate, batteryLevelData.getHourlyBatteryLevelsPerDay(), processedBatteryHistoryMap).execute(); } return batteryLevelData; } Loading Loading @@ -365,19 +375,165 @@ public final class DataProcessor { return null; } final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); metricsFeatureProvider.action( logAppCountMetrics(context, countOfAppBeforePurge, countOfAppAfterPurge); return resultMap; } @VisibleForTesting @Nullable static BatteryDiffData generateBatteryDiffData( final Context context, @Nullable final List<BatteryEntry> batteryEntryList, final BatteryUsageStats batteryUsageStats) { final List<BatteryHistEntry> batteryHistEntryList = convertToBatteryHistEntry(batteryEntryList, batteryUsageStats); if (batteryHistEntryList == null || batteryHistEntryList.isEmpty()) { Log.w(TAG, "batteryHistEntryList is null or empty in generateBatteryDiffData()"); return null; } final int currentUserId = context.getUserId(); final UserHandle userHandle = Utils.getManagedProfile(context.getSystemService(UserManager.class)); final int workProfileUserId = userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE; final List<BatteryDiffEntry> appEntries = new ArrayList<>(); final List<BatteryDiffEntry> systemEntries = new ArrayList<>(); double totalConsumePower = 0f; double consumePowerFromOtherUsers = 0f; for (BatteryHistEntry entry : batteryHistEntryList) { final boolean isFromOtherUsers = isConsumedFromOtherUsers( currentUserId, workProfileUserId, entry); totalConsumePower += entry.mConsumePower; if (isFromOtherUsers) { consumePowerFromOtherUsers += entry.mConsumePower; } else { final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry( context, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, countOfAppAfterPurge); metricsFeatureProvider.action( entry.mForegroundUsageTimeInMs, entry.mBackgroundUsageTimeInMs, entry.mConsumePower, entry); if (currentBatteryDiffEntry.isSystemEntry()) { systemEntries.add(currentBatteryDiffEntry); } else { appEntries.add(currentBatteryDiffEntry); } } } if (consumePowerFromOtherUsers != 0) { systemEntries.add(createOtherUsersEntry(context, consumePowerFromOtherUsers)); } // If there is no data, return null instead of empty item. if (appEntries.isEmpty() && systemEntries.isEmpty()) { return null; } return new BatteryDiffData(appEntries, systemEntries, totalConsumePower); } /** * Starts the async task to load battery diff usage data and load app labels + icons. */ private static void loadBatteryUsageDataFromBatteryStatsService( Context context, @Nullable Handler handler, final UsageMapAsyncResponse asyncResponseDelegate) { new LoadUsageMapFromBatteryStatsServiceTask( context, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, countOfAppBeforePurge - countOfAppAfterPurge); handler, asyncResponseDelegate).execute(); } /** * @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] */ @Nullable private static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMapFromStatsService( final Context context) { final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new HashMap<>(); final Map<Integer, BatteryDiffData> allUsageMap = new HashMap<>(); // Always construct the map whether the value is null or not. allUsageMap.put(SELECTED_INDEX_ALL, getBatteryDiffDataFromBatteryStatsService(context)); resultMap.put(SELECTED_INDEX_ALL, allUsageMap); // Compute the apps number before purge. Must put before purgeLowPercentageAndFakeData. final int countOfAppBeforePurge = getCountOfApps(resultMap); purgeLowPercentageAndFakeData(context, resultMap); // Compute the apps number after purge. Must put after purgeLowPercentageAndFakeData. final int countOfAppAfterPurge = getCountOfApps(resultMap); logAppCountMetrics(context, countOfAppBeforePurge, countOfAppAfterPurge); return resultMap; } @Nullable private static BatteryDiffData getBatteryDiffDataFromBatteryStatsService( final Context context) { BatteryDiffData batteryDiffData = null; try { final BatteryUsageStatsQuery batteryUsageStatsQuery = new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build(); final BatteryUsageStats batteryUsageStats = context.getSystemService(BatteryStatsManager.class) .getBatteryUsageStats(batteryUsageStatsQuery); if (batteryUsageStats == null) { Log.w(TAG, "batteryUsageStats is null content"); return null; } final List<BatteryEntry> batteryEntryList = generateBatteryEntryListFromBatteryUsageStats(context, batteryUsageStats); batteryDiffData = generateBatteryDiffData(context, batteryEntryList, batteryUsageStats); } catch (RuntimeException e) { Log.e(TAG, "load batteryUsageStats:" + e); } return batteryDiffData; } @Nullable private static List<BatteryEntry> generateBatteryEntryListFromBatteryUsageStats( final Context context, final BatteryUsageStats batteryUsageStats) { // Loads the battery consuming data. final BatteryAppListPreferenceController controller = new BatteryAppListPreferenceController( context, /*preferenceKey=*/ null, /*lifecycle=*/ null, /*activity*=*/ null, /*fragment=*/ null); return controller.getBatteryEntryList(batteryUsageStats, /*showAllApps=*/ true); } @Nullable private static List<BatteryHistEntry> convertToBatteryHistEntry( @Nullable final List<BatteryEntry> batteryEntryList, final BatteryUsageStats batteryUsageStats) { if (batteryEntryList == null || batteryEntryList.isEmpty()) { Log.w(TAG, "batteryEntryList is null or empty in convertToBatteryHistEntry()"); return null; } return batteryEntryList.stream() .filter(entry -> { final long foregroundMs = entry.getTimeInForegroundMs(); final long backgroundMs = entry.getTimeInBackgroundMs(); return entry.getConsumedPower() > 0 || (entry.getConsumedPower() == 0 && (foregroundMs != 0 || backgroundMs != 0)); }) .map(entry -> ConvertUtils.convertToBatteryHistEntry( entry, batteryUsageStats)) .collect(Collectors.toList()); } /** * Interpolates history map based on expected timestamp slots and processes the corner case when * the expected start timestamp is earlier than what we have. Loading Loading @@ -940,6 +1096,22 @@ public final class DataProcessor { return true; } private static void loadLabelAndIcon( @Nullable final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) { if (batteryUsageMap == null) { return; } // Pre-loads each BatteryDiffEntry relative icon and label for all slots. final BatteryDiffData batteryUsageMapForAll = batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL); if (batteryUsageMapForAll != null) { batteryUsageMapForAll.getAppDiffEntryList().forEach( entry -> entry.loadLabelAndIcon()); batteryUsageMapForAll.getSystemDiffEntryList().forEach( entry -> entry.loadLabelAndIcon()); } } private static long getTimestampWithDayDiff(final long timestamp, final int dayDiff) { final Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timestamp); Loading Loading @@ -1006,6 +1178,21 @@ public final class DataProcessor { return batteryDiffEntry; } private static void logAppCountMetrics( Context context, final int countOfAppBeforePurge, final int countOfAppAfterPurge) { context = context.getApplicationContext(); final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); metricsFeatureProvider.action( context, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, countOfAppAfterPurge); metricsFeatureProvider.action( context, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, countOfAppBeforePurge - countOfAppAfterPurge); } private static void log(Context context, final String content, final long timestamp, final BatteryHistEntry entry) { if (DEBUG) { Loading @@ -1015,12 +1202,12 @@ public final class DataProcessor { } // Compute diff map and loads all items (icon and label) in the background. private static final class ComputeUsageMapAndLoadItemsTask private static class ComputeUsageMapAndLoadItemsTask extends AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>> { private Context mApplicationContext; private Handler mHandler; private UsageMapAsyncResponse mAsyncResponseDelegate; Context mApplicationContext; final Handler mHandler; final UsageMapAsyncResponse mAsyncResponseDelegate; private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap; Loading Loading @@ -1051,17 +1238,7 @@ public final class DataProcessor { final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap = getBatteryUsageMap( mApplicationContext, mHourlyBatteryLevelsPerDay, mBatteryHistoryMap); if (batteryUsageMap != null) { // Pre-loads each BatteryDiffEntry relative icon and label for all slots. final BatteryDiffData batteryUsageMapForAll = batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL); if (batteryUsageMapForAll != null) { batteryUsageMapForAll.getAppDiffEntryList().forEach( entry -> entry.loadLabelAndIcon()); batteryUsageMapForAll.getSystemDiffEntryList().forEach( entry -> entry.loadLabelAndIcon()); } } loadLabelAndIcon(batteryUsageMap); Log.d(TAG, String.format("execute ComputeUsageMapAndLoadItemsTask in %d/ms", (System.currentTimeMillis() - startTime))); return batteryUsageMap; Loading @@ -1081,4 +1258,35 @@ public final class DataProcessor { } } } // Loads battery usage data from battery stats service directly and loads all items (icon and // label) in the background. private static final class LoadUsageMapFromBatteryStatsServiceTask extends ComputeUsageMapAndLoadItemsTask { private LoadUsageMapFromBatteryStatsServiceTask( Context context, Handler handler, final UsageMapAsyncResponse asyncResponseDelegate) { super(context, handler, asyncResponseDelegate, /*hourlyBatteryLevelsPerDay=*/ null, /*batteryHistoryMap=*/ null); } @Override protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) { if (mApplicationContext == null || mHandler == null || mAsyncResponseDelegate == null) { Log.e(TAG, "invalid input for ComputeUsageMapAndLoadItemsTask()"); return null; } final long startTime = System.currentTimeMillis(); final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap = getBatteryUsageMapFromStatsService(mApplicationContext); loadLabelAndIcon(batteryUsageMap); Log.d(TAG, String.format("execute LoadUsageMapFromBatteryStatsServiceTask in %d/ms", (System.currentTimeMillis() - startTime))); return batteryUsageMap; } } } tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -64,7 +64,7 @@ public final class BatteryHistEntryTest { when(mMockBatteryEntry.getConsumerType()) .thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY); final ContentValues values = ConvertUtils.convert( ConvertUtils.convertToContentValues( mMockBatteryEntry, mBatteryUsageStats, /*batteryLevel=*/ 12, Loading tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java +74 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +70 −20 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.text.format.DateUtils; import android.util.Log; import android.view.View; import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; Loading Loading @@ -111,6 +112,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private View mCategoryTitleView; private PreferenceScreen mPreferenceScreen; private FooterPreference mFooterPreference; private TextView mChartSummaryTextView; private BatteryChartViewModel mDailyViewModel; private List<BatteryChartViewModel> mHourlyViewModels; Loading @@ -121,9 +123,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private final MetricsFeatureProvider mMetricsFeatureProvider; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final AnimatorListenerAdapter mHourlyChartFadeInAdapter = createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ true); createHourlyChartAnimatorListenerAdapter(/*visible=*/ true); private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter = createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ false); createHourlyChartAnimatorListenerAdapter(/*visible=*/ false); @VisibleForTesting final DailyChartLabelTextGenerator mDailyChartLabelTextGenerator = Loading Loading @@ -289,6 +291,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll getTotalHours(batteryLevelData)); if (batteryLevelData == null) { mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; mDailyViewModel = null; mHourlyViewModels = null; refreshUi(); Loading Loading @@ -321,6 +325,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView)); animateBatteryChartViewGroup(); } if (mBatteryChartViewGroup != null) { final View grandparentView = (View) mBatteryChartViewGroup.getParent(); mChartSummaryTextView = grandparentView != null ? grandparentView.findViewById(R.id.chart_summary) : null; } } private void setBatteryChartViewInner(@NonNull final BatteryChartView dailyChartView, Loading Loading @@ -367,8 +376,45 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll // Chart views are not initialized. return false; } if (mDailyViewModel == null || mHourlyViewModels == null) { // Fail to get battery level data, show an empty hourly chart view. // When mDailyViewModel or mHourlyViewModels is null, there is no battery level data. // This is mainly in 2 cases: // 1) battery data is within 2 hours // 2) no battery data in the latest 7 days (power off >= 7 days) final boolean refreshUiResult = mDailyViewModel == null || mHourlyViewModels == null ? refreshUiWithNoLevelDataCase() : refreshUiWithLevelDataCase(); if (!refreshUiResult) { return false; } mHandler.post(() -> { final long start = System.currentTimeMillis(); removeAndCacheAllPrefs(); addAllPreferences(); refreshCategoryTitle(); Log.d(TAG, String.format("refreshUi is finished in %d/ms", (System.currentTimeMillis() - start))); }); return true; } private boolean refreshUiWithNoLevelDataCase() { setChartSummaryVisible(false); if (mBatteryUsageMap == null) { // There is no battery level data and battery usage data is not ready, wait for data // ready to refresh UI. Show nothing temporarily. mDailyChartView.setVisibility(View.GONE); mHourlyChartView.setVisibility(View.GONE); mDailyChartView.setViewModel(null); mHourlyChartView.setViewModel(null); return false; } else if (mBatteryUsageMap .get(BatteryChartViewModel.SELECTED_INDEX_ALL) .get(BatteryChartViewModel.SELECTED_INDEX_ALL) == null) { // There is no battery level data and battery usage data, show an empty hourly chart // view. mDailyChartView.setVisibility(View.GONE); mHourlyChartView.setVisibility(View.VISIBLE); mHourlyChartView.setViewModel(null); Loading @@ -376,7 +422,12 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll addFooterPreferenceIfNeeded(false); return false; } return true; } private boolean refreshUiWithLevelDataCase() { setChartSummaryVisible(true); // Gets valid battery level data. if (isBatteryLevelDataInOneDay()) { // Only 1 day data, hide the daily chart view. mDailyChartView.setVisibility(View.GONE); Loading @@ -389,10 +440,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) { // Multiple days are selected, hide the hourly chart view. animateBatteryHourlyChartView(/*isToShow=*/ false); animateBatteryHourlyChartView(/*visible=*/ false); } else { animateBatteryHourlyChartView(/*isToShow=*/ true); final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex); animateBatteryHourlyChartView(/*visible=*/ true); final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex); hourlyViewModel.setSelectedIndex(mHourlyChartIndex); mHourlyChartView.setViewModel(hourlyViewModel); } Loading @@ -401,14 +453,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll // Battery usage data is not ready, wait for data ready to refresh UI. return false; } mHandler.post(() -> { final long start = System.currentTimeMillis(); removeAndCacheAllPrefs(); addAllPreferences(); refreshCategoryTitle(); Log.d(TAG, String.format("refreshUi is finished in %d/ms", (System.currentTimeMillis() - start))); }); return true; } Loading @@ -427,7 +471,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll if (!batteryDiffData.getAppDiffEntryList().isEmpty()) { addPreferenceToScreen(batteryDiffData.getAppDiffEntryList()); } // Adds the expabable divider if we have system entries data. // Adds the expandable divider if we have system entries data. if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) { if (mExpandDividerPreference == null) { mExpandDividerPreference = new ExpandDividerPreference(mPrefContext); Loading Loading @@ -645,12 +689,12 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } } private void animateBatteryHourlyChartView(final boolean isToShow) { private void animateBatteryHourlyChartView(final boolean visible) { if (mHourlyChartView == null) { return; } if (isToShow) { if (visible) { mHourlyChartView.setAlpha(0f); mHourlyChartView.setVisibility(View.VISIBLE); mHourlyChartView.animate() Loading @@ -667,9 +711,15 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } } private void setChartSummaryVisible(final boolean visible) { if (mChartSummaryTextView != null) { mChartSummaryTextView.setVisibility(visible ? View.VISIBLE : View.GONE); } } private AnimatorListenerAdapter createHourlyChartAnimatorListenerAdapter( final boolean isToShow) { final int visibility = isToShow ? View.VISIBLE : View.GONE; final boolean visible) { final int visibility = visible ? View.VISIBLE : View.GONE; return new AnimatorListenerAdapter() { @Override Loading
src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java +16 −6 Original line number Diff line number Diff line Loading @@ -55,11 +55,6 @@ public final class ConvertUtils { // Maximum total time value for each slot cumulative data at most 2 hours. private static final float TOTAL_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2; // Keys for metric metadata. static final int METRIC_KEY_PACKAGE = 1; static final int METRIC_KEY_BATTERY_LEVEL = 2; static final int METRIC_KEY_BATTERY_USAGE = 3; @VisibleForTesting static double PERCENTAGE_OF_TOTAL_THRESHOLD = 1f; Loading Loading @@ -87,7 +82,7 @@ public final class ConvertUtils { } /** Converts to content values */ public static ContentValues convert( public static ContentValues convertToContentValues( BatteryEntry entry, BatteryUsageStats batteryUsageStats, int batteryLevel, Loading Loading @@ -130,6 +125,21 @@ public final class ConvertUtils { return values; } /** Converts to {@link BatteryHistEntry} */ public static BatteryHistEntry convertToBatteryHistEntry( BatteryEntry entry, BatteryUsageStats batteryUsageStats) { return new BatteryHistEntry( convertToContentValues( entry, batteryUsageStats, /*batteryLevel=*/ 0, /*batteryStatus=*/ 0, /*batteryHealth=*/ 0, /*bootTimestamp=*/ 0, /*timestamp=*/ 0)); } /** Converts UTC timestamp to human readable local time string. */ public static String utcToLocalTime(Context context, long timestamp) { final Locale locale = getLocale(context); Loading
src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +241 −33 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import android.app.settings.SettingsEnums; import android.content.ContentValues; import android.content.Context; import android.os.AsyncTask; import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; Loading Loading @@ -50,6 +53,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * A utility class to process data loaded from database and make the data easy to use for battery Loading Loading @@ -97,7 +101,9 @@ public final class DataProcessor { @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap, final UsageMapAsyncResponse asyncResponseDelegate) { if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { Log.d(TAG, "getBatteryLevelData() returns null"); Log.d(TAG, "batteryHistoryMap is null in getBatteryLevelData()"); loadBatteryUsageDataFromBatteryStatsService( context, handler, asyncResponseDelegate); return null; } handler = handler != null ? handler : new Handler(Looper.getMainLooper()); Loading @@ -107,16 +113,20 @@ public final class DataProcessor { // Wrap and processed history map into easy-to-use format for UI rendering. final BatteryLevelData batteryLevelData = getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap); if (batteryLevelData == null) { loadBatteryUsageDataFromBatteryStatsService( context, handler, asyncResponseDelegate); Log.d(TAG, "getBatteryLevelData() returns null"); return null; } // Start the async task to compute diff usage data and load labels and icons. if (batteryLevelData != null) { new ComputeUsageMapAndLoadItemsTask( context, handler, asyncResponseDelegate, batteryLevelData.getHourlyBatteryLevelsPerDay(), processedBatteryHistoryMap).execute(); } return batteryLevelData; } Loading Loading @@ -365,19 +375,165 @@ public final class DataProcessor { return null; } final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); metricsFeatureProvider.action( logAppCountMetrics(context, countOfAppBeforePurge, countOfAppAfterPurge); return resultMap; } @VisibleForTesting @Nullable static BatteryDiffData generateBatteryDiffData( final Context context, @Nullable final List<BatteryEntry> batteryEntryList, final BatteryUsageStats batteryUsageStats) { final List<BatteryHistEntry> batteryHistEntryList = convertToBatteryHistEntry(batteryEntryList, batteryUsageStats); if (batteryHistEntryList == null || batteryHistEntryList.isEmpty()) { Log.w(TAG, "batteryHistEntryList is null or empty in generateBatteryDiffData()"); return null; } final int currentUserId = context.getUserId(); final UserHandle userHandle = Utils.getManagedProfile(context.getSystemService(UserManager.class)); final int workProfileUserId = userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE; final List<BatteryDiffEntry> appEntries = new ArrayList<>(); final List<BatteryDiffEntry> systemEntries = new ArrayList<>(); double totalConsumePower = 0f; double consumePowerFromOtherUsers = 0f; for (BatteryHistEntry entry : batteryHistEntryList) { final boolean isFromOtherUsers = isConsumedFromOtherUsers( currentUserId, workProfileUserId, entry); totalConsumePower += entry.mConsumePower; if (isFromOtherUsers) { consumePowerFromOtherUsers += entry.mConsumePower; } else { final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry( context, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, countOfAppAfterPurge); metricsFeatureProvider.action( entry.mForegroundUsageTimeInMs, entry.mBackgroundUsageTimeInMs, entry.mConsumePower, entry); if (currentBatteryDiffEntry.isSystemEntry()) { systemEntries.add(currentBatteryDiffEntry); } else { appEntries.add(currentBatteryDiffEntry); } } } if (consumePowerFromOtherUsers != 0) { systemEntries.add(createOtherUsersEntry(context, consumePowerFromOtherUsers)); } // If there is no data, return null instead of empty item. if (appEntries.isEmpty() && systemEntries.isEmpty()) { return null; } return new BatteryDiffData(appEntries, systemEntries, totalConsumePower); } /** * Starts the async task to load battery diff usage data and load app labels + icons. */ private static void loadBatteryUsageDataFromBatteryStatsService( Context context, @Nullable Handler handler, final UsageMapAsyncResponse asyncResponseDelegate) { new LoadUsageMapFromBatteryStatsServiceTask( context, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, countOfAppBeforePurge - countOfAppAfterPurge); handler, asyncResponseDelegate).execute(); } /** * @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] */ @Nullable private static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMapFromStatsService( final Context context) { final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new HashMap<>(); final Map<Integer, BatteryDiffData> allUsageMap = new HashMap<>(); // Always construct the map whether the value is null or not. allUsageMap.put(SELECTED_INDEX_ALL, getBatteryDiffDataFromBatteryStatsService(context)); resultMap.put(SELECTED_INDEX_ALL, allUsageMap); // Compute the apps number before purge. Must put before purgeLowPercentageAndFakeData. final int countOfAppBeforePurge = getCountOfApps(resultMap); purgeLowPercentageAndFakeData(context, resultMap); // Compute the apps number after purge. Must put after purgeLowPercentageAndFakeData. final int countOfAppAfterPurge = getCountOfApps(resultMap); logAppCountMetrics(context, countOfAppBeforePurge, countOfAppAfterPurge); return resultMap; } @Nullable private static BatteryDiffData getBatteryDiffDataFromBatteryStatsService( final Context context) { BatteryDiffData batteryDiffData = null; try { final BatteryUsageStatsQuery batteryUsageStatsQuery = new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build(); final BatteryUsageStats batteryUsageStats = context.getSystemService(BatteryStatsManager.class) .getBatteryUsageStats(batteryUsageStatsQuery); if (batteryUsageStats == null) { Log.w(TAG, "batteryUsageStats is null content"); return null; } final List<BatteryEntry> batteryEntryList = generateBatteryEntryListFromBatteryUsageStats(context, batteryUsageStats); batteryDiffData = generateBatteryDiffData(context, batteryEntryList, batteryUsageStats); } catch (RuntimeException e) { Log.e(TAG, "load batteryUsageStats:" + e); } return batteryDiffData; } @Nullable private static List<BatteryEntry> generateBatteryEntryListFromBatteryUsageStats( final Context context, final BatteryUsageStats batteryUsageStats) { // Loads the battery consuming data. final BatteryAppListPreferenceController controller = new BatteryAppListPreferenceController( context, /*preferenceKey=*/ null, /*lifecycle=*/ null, /*activity*=*/ null, /*fragment=*/ null); return controller.getBatteryEntryList(batteryUsageStats, /*showAllApps=*/ true); } @Nullable private static List<BatteryHistEntry> convertToBatteryHistEntry( @Nullable final List<BatteryEntry> batteryEntryList, final BatteryUsageStats batteryUsageStats) { if (batteryEntryList == null || batteryEntryList.isEmpty()) { Log.w(TAG, "batteryEntryList is null or empty in convertToBatteryHistEntry()"); return null; } return batteryEntryList.stream() .filter(entry -> { final long foregroundMs = entry.getTimeInForegroundMs(); final long backgroundMs = entry.getTimeInBackgroundMs(); return entry.getConsumedPower() > 0 || (entry.getConsumedPower() == 0 && (foregroundMs != 0 || backgroundMs != 0)); }) .map(entry -> ConvertUtils.convertToBatteryHistEntry( entry, batteryUsageStats)) .collect(Collectors.toList()); } /** * Interpolates history map based on expected timestamp slots and processes the corner case when * the expected start timestamp is earlier than what we have. Loading Loading @@ -940,6 +1096,22 @@ public final class DataProcessor { return true; } private static void loadLabelAndIcon( @Nullable final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) { if (batteryUsageMap == null) { return; } // Pre-loads each BatteryDiffEntry relative icon and label for all slots. final BatteryDiffData batteryUsageMapForAll = batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL); if (batteryUsageMapForAll != null) { batteryUsageMapForAll.getAppDiffEntryList().forEach( entry -> entry.loadLabelAndIcon()); batteryUsageMapForAll.getSystemDiffEntryList().forEach( entry -> entry.loadLabelAndIcon()); } } private static long getTimestampWithDayDiff(final long timestamp, final int dayDiff) { final Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timestamp); Loading Loading @@ -1006,6 +1178,21 @@ public final class DataProcessor { return batteryDiffEntry; } private static void logAppCountMetrics( Context context, final int countOfAppBeforePurge, final int countOfAppAfterPurge) { context = context.getApplicationContext(); final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); metricsFeatureProvider.action( context, SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT, countOfAppAfterPurge); metricsFeatureProvider.action( context, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, countOfAppBeforePurge - countOfAppAfterPurge); } private static void log(Context context, final String content, final long timestamp, final BatteryHistEntry entry) { if (DEBUG) { Loading @@ -1015,12 +1202,12 @@ public final class DataProcessor { } // Compute diff map and loads all items (icon and label) in the background. private static final class ComputeUsageMapAndLoadItemsTask private static class ComputeUsageMapAndLoadItemsTask extends AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>> { private Context mApplicationContext; private Handler mHandler; private UsageMapAsyncResponse mAsyncResponseDelegate; Context mApplicationContext; final Handler mHandler; final UsageMapAsyncResponse mAsyncResponseDelegate; private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap; Loading Loading @@ -1051,17 +1238,7 @@ public final class DataProcessor { final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap = getBatteryUsageMap( mApplicationContext, mHourlyBatteryLevelsPerDay, mBatteryHistoryMap); if (batteryUsageMap != null) { // Pre-loads each BatteryDiffEntry relative icon and label for all slots. final BatteryDiffData batteryUsageMapForAll = batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL); if (batteryUsageMapForAll != null) { batteryUsageMapForAll.getAppDiffEntryList().forEach( entry -> entry.loadLabelAndIcon()); batteryUsageMapForAll.getSystemDiffEntryList().forEach( entry -> entry.loadLabelAndIcon()); } } loadLabelAndIcon(batteryUsageMap); Log.d(TAG, String.format("execute ComputeUsageMapAndLoadItemsTask in %d/ms", (System.currentTimeMillis() - startTime))); return batteryUsageMap; Loading @@ -1081,4 +1258,35 @@ public final class DataProcessor { } } } // Loads battery usage data from battery stats service directly and loads all items (icon and // label) in the background. private static final class LoadUsageMapFromBatteryStatsServiceTask extends ComputeUsageMapAndLoadItemsTask { private LoadUsageMapFromBatteryStatsServiceTask( Context context, Handler handler, final UsageMapAsyncResponse asyncResponseDelegate) { super(context, handler, asyncResponseDelegate, /*hourlyBatteryLevelsPerDay=*/ null, /*batteryHistoryMap=*/ null); } @Override protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) { if (mApplicationContext == null || mHandler == null || mAsyncResponseDelegate == null) { Log.e(TAG, "invalid input for ComputeUsageMapAndLoadItemsTask()"); return null; } final long startTime = System.currentTimeMillis(); final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap = getBatteryUsageMapFromStatsService(mApplicationContext); loadLabelAndIcon(batteryUsageMap); Log.d(TAG, String.format("execute LoadUsageMapFromBatteryStatsServiceTask in %d/ms", (System.currentTimeMillis() - startTime))); return batteryUsageMap; } } }
tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -64,7 +64,7 @@ public final class BatteryHistEntryTest { when(mMockBatteryEntry.getConsumerType()) .thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY); final ContentValues values = ConvertUtils.convert( ConvertUtils.convertToContentValues( mMockBatteryEntry, mBatteryUsageStats, /*batteryLevel=*/ 12, Loading
tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java +74 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes