From f5ea50e20e17ed95620193ef079aa10e3b338b35 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Mon, 28 Aug 2023 16:30:21 +0800 Subject: [PATCH] Refactor battery usage page contollers interaction logic (1) Move controllers interaction logic from BatteryChartPreferenceController to main page PowerUsageAdvanced. (2) Move query power anomaly logic to DataProcessManager async job. Bug: 284893240 Test: manual Change-Id: Ib23b338fe3946e68ff73a372342ec5d86494c566 Merged-In: Ib23b338fe3946e68ff73a372342ec5d86494c566 --- .../BatteryChartPreferenceController.java | 252 +++--------------- .../batteryusage/PowerUsageAdvanced.java | 162 +++++++++-- .../BatteryChartPreferenceControllerTest.java | 128 +++++---- .../batteryusage/PowerUsageAdvancedTest.java | 92 +++++++ 4 files changed, 328 insertions(+), 306 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 19454d774f3..eba13f497f1 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -36,7 +36,6 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -44,7 +43,6 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnCreate; import com.android.settingslib.core.lifecycle.events.OnDestroy; -import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; @@ -52,17 +50,12 @@ import com.google.common.base.Objects; import java.util.ArrayList; import java.util.Calendar; -import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; /** Controls the update for chart graph and the list items. */ public class BatteryChartPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy, OnPause, + implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy, OnSaveInstanceState, OnResume { private static final String TAG = "BatteryChartPreferenceController"; private static final String PREFERENCE_KEY = "battery_chart"; @@ -74,53 +67,17 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private static final String KEY_DAILY_CHART_INDEX = "daily_chart_index"; private static final String KEY_HOURLY_CHART_INDEX = "hourly_chart_index"; - /** - * A callback listener for battery usage is updated. - * This happens when battery usage data is ready or the selected index is changed. - */ - public interface OnBatteryUsageUpdatedListener { - /** - * The callback function for battery usage is updated. - * @param slotUsageData The battery usage diff data for the selected slot. This is used in - * the app list. - * @param slotTimestamp The selected slot timestamp information. This is used in the battery - * usage breakdown category. - * @param isAllUsageDataEmpty Whether all the battery usage data is null or empty. This is - * used when showing the footer. - */ - void onBatteryUsageUpdated( - BatteryDiffData slotUsageData, String slotTimestamp, boolean isAllUsageDataEmpty); + /** A callback listener for the selected index is updated. */ + interface OnSelectedIndexUpdatedListener { + /** The callback function for the selected index is updated. */ + void onSelectedIndexUpdated(); } - /** - * A callback listener for the device screen on time is updated. - * This happens when screen on time data is ready or the selected index is changed. - */ - public interface OnScreenOnTimeUpdatedListener { - /** - * The callback function for the device screen on time is updated. - * @param screenOnTime The selected slot device screen on time. - * @param slotTimestamp The selected slot timestamp information. - */ - void onScreenOnTimeUpdated(Long screenOnTime, String slotTimestamp); - } - - /** - * A callback listener for the battery tips card is updated. - * This happens when battery tips card is ready. - */ - public interface OnBatteryTipsUpdatedListener { - /** - * The callback function for the battery tips card is updated. - * @param powerAnomalyEvent the power anomaly event with highest score - */ - void onBatteryTipsUpdated(PowerAnomalyEvent powerAnomalyEvent); - } - - @VisibleForTesting Context mPrefContext; @VisibleForTesting + TextView mChartSummaryTextView; + @VisibleForTesting BatteryChartView mDailyChartView; @VisibleForTesting BatteryChartView mHourlyChartView; @@ -128,28 +85,20 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; @VisibleForTesting int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; - @VisibleForTesting - Map> mBatteryUsageMap; private boolean mIs24HourFormat; private View mBatteryChartViewGroup; - private TextView mChartSummaryTextView; private BatteryChartViewModel mDailyViewModel; private List mHourlyViewModels; - private OnBatteryUsageUpdatedListener mOnBatteryUsageUpdatedListener; - private OnScreenOnTimeUpdatedListener mOnScreenOnTimeUpdatedListener; - private OnBatteryTipsUpdatedListener mOnBatteryTipsUpdatedListener; - private AtomicBoolean mIsAppResume = new AtomicBoolean(false); + private OnSelectedIndexUpdatedListener mOnSelectedIndexUpdatedListener; private final SettingsActivity mActivity; private final MetricsFeatureProvider mMetricsFeatureProvider; - private final PowerUsageFeatureProvider mPowerUsageFeatureProvider; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final AnimatorListenerAdapter mHourlyChartFadeInAdapter = createHourlyChartAnimatorListenerAdapter(/*visible=*/ true); private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter = createHourlyChartAnimatorListenerAdapter(/*visible=*/ false); - private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); @VisibleForTesting final DailyChartLabelTextGenerator mDailyChartLabelTextGenerator = @@ -165,8 +114,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mIs24HourFormat = DateFormat.is24HourFormat(context); mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); - mPowerUsageFeatureProvider = - FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); if (lifecycle != null) { lifecycle.addObserver(this); } @@ -184,15 +131,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d", mDailyChartIndex, mHourlyChartIndex)); } - @Override - public void onPause() { - mIsAppResume.compareAndSet(/* expect= */ true, /* update= */ false); - } - @Override public void onResume() { - mIsAppResume.compareAndSet(/* expect= */ false, /* update= */ true); mIs24HourFormat = DateFormat.is24HourFormat(mContext); mMetricsFeatureProvider.action(mPrefContext, SettingsEnums.OPEN_BATTERY_USAGE); } @@ -232,16 +173,16 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll return PREFERENCE_KEY; } - void setOnBatteryUsageUpdatedListener(OnBatteryUsageUpdatedListener listener) { - mOnBatteryUsageUpdatedListener = listener; + int getDailyChartIndex() { + return mDailyChartIndex; } - void setOnScreenOnTimeUpdatedListener(OnScreenOnTimeUpdatedListener listener) { - mOnScreenOnTimeUpdatedListener = listener; + int getHourlyChartIndex() { + return mHourlyChartIndex; } - void setOnBatteryTipsUpdatedListener(OnBatteryTipsUpdatedListener listener) { - mOnBatteryTipsUpdatedListener = listener; + void setOnSelectedIndexUpdatedListener(OnSelectedIndexUpdatedListener listener) { + mOnSelectedIndexUpdatedListener = listener; } void onBatteryLevelDataUpdate(final BatteryLevelData batteryLevelData) { @@ -276,13 +217,6 @@ 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(); @@ -319,6 +253,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll ? SettingsEnums.ACTION_BATTERY_USAGE_DAILY_SHOW_ALL : SettingsEnums.ACTION_BATTERY_USAGE_DAILY_TIME_SLOT, mDailyChartIndex); + if (mOnSelectedIndexUpdatedListener != null) { + mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated(); + } }); mHourlyChartView = hourlyChartView; mHourlyChartView.setOnSelectListener(trapezoidIndex -> { @@ -340,102 +277,37 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT, mHourlyChartIndex); + if (mOnSelectedIndexUpdatedListener != null) { + mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated(); + } }); refreshUi(); } + // Show empty hourly chart view only if there is no valid battery usage data. + void showEmptyChart() { + setChartSummaryVisible(true); + mDailyChartView.setVisibility(View.GONE); + mHourlyChartView.setVisibility(View.VISIBLE); + mHourlyChartView.setViewModel(null); + } + @VisibleForTesting - boolean refreshUi() { + void refreshUi() { if (mDailyChartView == null || mHourlyChartView == null) { // Chart views are not initialized. - return false; + return; } - // 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; - } - - if (mOnBatteryUsageUpdatedListener != null && mBatteryUsageMap != null - && mBatteryUsageMap.get(mDailyChartIndex) != null) { - final BatteryDiffData slotUsageData = - mBatteryUsageMap.get(mDailyChartIndex).get(mHourlyChartIndex); - if (slotUsageData != null) { - mOnScreenOnTimeUpdatedListener.onScreenOnTimeUpdated( - slotUsageData.getScreenOnTime(), - getSlotInformation()); - } - mOnBatteryUsageUpdatedListener.onBatteryUsageUpdated( - slotUsageData, getSlotInformation(), isBatteryUsageMapNullOrEmpty()); - if (mOnBatteryTipsUpdatedListener != null) { - mExecutor.execute(() -> { - final PowerAnomalyEventList anomalyEventList = mPowerUsageFeatureProvider - .detectSettingsAnomaly(mContext, /* displayDrain= */ 0); - Log.d(TAG, "anomalyEventList = " + anomalyEventList); - final PowerAnomalyEvent displayEvent = - getHighestScoreAnomalyEvent(anomalyEventList); - mHandler.post(() -> { - if (mIsAppResume.get()) { - mOnBatteryTipsUpdatedListener - .onBatteryTipsUpdated(displayEvent); - } - } - ); - }); - } - } - return true; - } - - @VisibleForTesting - PowerAnomalyEvent getHighestScoreAnomalyEvent(PowerAnomalyEventList anomalyEventList) { - if (anomalyEventList == null || anomalyEventList.getPowerAnomalyEventsCount() == 0) { - return null; - } - final Set dismissedPowerAnomalyKeys = - DatabaseUtils.getDismissedPowerAnomalyKeys(mContext); - Log.d(TAG, "dismissedPowerAnomalyKeys = " + dismissedPowerAnomalyKeys); - - final PowerAnomalyEvent highestScoreEvent = anomalyEventList.getPowerAnomalyEventsList() - .stream() - .filter(event -> event.hasKey() - && !dismissedPowerAnomalyKeys.contains(event.getKey().name())) - .max(Comparator.comparing(PowerAnomalyEvent::getScore)) - .orElse(null); - Log.d(TAG, "highestScoreAnomalyEvent = " + highestScoreEvent); - return highestScoreEvent; - } - - 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. + if (mDailyViewModel == null || mHourlyViewModels == null) { + setChartSummaryVisible(false); 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); + return; } - return true; - } - private boolean refreshUiWithLevelDataCase() { setChartSummaryVisible(true); // Gets valid battery level data. if (isBatteryLevelDataInOneDay()) { @@ -464,15 +336,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll hourlyViewModel.setSelectedIndex(mHourlyChartIndex); mHourlyChartView.setViewModel(hourlyViewModel); } - - if (mBatteryUsageMap == null) { - // Battery usage data is not ready, wait for data ready to refresh UI. - return false; - } - return true; } - @VisibleForTesting String getSlotInformation() { if (mDailyViewModel == null || mHourlyViewModels == null) { // No data @@ -563,44 +428,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll }; } - private void logScreenUsageTime() { - if (mBatteryUsageMap == null) { - return; - } - final BatteryDiffData allBatteryDiffData = mBatteryUsageMap.get( - BatteryChartViewModel.SELECTED_INDEX_ALL).get( - BatteryChartViewModel.SELECTED_INDEX_ALL); - if (allBatteryDiffData == null) { - return; - } - mMetricsFeatureProvider.action( - mPrefContext, - SettingsEnums.ACTION_BATTERY_USAGE_SCREEN_ON_TIME, - (int) allBatteryDiffData.getScreenOnTime()); - mMetricsFeatureProvider.action( - mPrefContext, - SettingsEnums.ACTION_BATTERY_USAGE_FOREGROUND_USAGE_TIME, - (int) getTotalForegroundUsageTime()); - } - - private long getTotalForegroundUsageTime() { - if (mBatteryUsageMap == null) { - return 0; - } - final BatteryDiffData totalBatteryUsageDiffData = - mBatteryUsageMap - .get(BatteryChartViewModel.SELECTED_INDEX_ALL) - .get(BatteryChartViewModel.SELECTED_INDEX_ALL); - if (totalBatteryUsageDiffData == null) { - return 0; - } - long totalValue = 0; - for (final BatteryDiffEntry entry : totalBatteryUsageDiffData.getAppDiffEntryList()) { - totalValue += entry.mForegroundUsageTimeInMs; - } - return totalValue; - } - private boolean isBatteryLevelDataInOneDay() { return mHourlyViewModels != null && mHourlyViewModels.size() == 1; } @@ -611,19 +438,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll && mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL; } - private boolean isBatteryUsageMapNullOrEmpty() { - if (mBatteryUsageMap == null) { - return true; - } - BatteryDiffData allBatteryDiffData = mBatteryUsageMap - .get(BatteryChartViewModel.SELECTED_INDEX_ALL) - .get(BatteryChartViewModel.SELECTED_INDEX_ALL); - // If all data is null or empty, each slot must be null or empty. - return allBatteryDiffData == null - || (allBatteryDiffData.getAppDiffEntryList().isEmpty() - && allBatteryDiffData.getSystemDiffEntryList().isEmpty()); - } - @VisibleForTesting static int getTotalHours(final BatteryLevelData batteryLevelData) { if (batteryLevelData == null) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java index ae74689eef7..a5449aa93d5 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java @@ -35,6 +35,8 @@ import androidx.loader.content.Loader; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.fuelgauge.BatteryBroadcastReceiver; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; @@ -42,9 +44,13 @@ import com.android.settingslib.utils.AsyncLoaderCompat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** Advanced power usage. */ @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) @@ -61,9 +67,14 @@ public class PowerUsageAdvanced extends PowerUsageBase { private boolean mIsChartDataLoaded = false; private long mResumeTimestamp; + private BatteryTipsController mBatteryTipsController; private BatteryChartPreferenceController mBatteryChartPreferenceController; + private ScreenOnTimeController mScreenOnTimeController; + private BatteryUsageBreakdownController mBatteryUsageBreakdownController; private Optional mBatteryLevelData; + private Map> mBatteryUsageMap; + private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); private final Handler mHandler = new Handler(Looper.getMainLooper()); private final ContentObserver mBatteryObserver = new ContentObserver(mHandler) { @@ -90,6 +101,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { if (getActivity().isChangingConfigurations()) { BatteryEntry.clearUidCache(); } + mExecutor.shutdown(); } @Override @@ -112,7 +124,6 @@ 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); @@ -133,28 +144,25 @@ public class PowerUsageAdvanced extends PowerUsageBase { @Override protected List createPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); + mBatteryTipsController = new BatteryTipsController(context); mBatteryChartPreferenceController = new BatteryChartPreferenceController( context, getSettingsLifecycle(), (SettingsActivity) getActivity()); - final ScreenOnTimeController screenOnTimeController = new ScreenOnTimeController(context); - final BatteryUsageBreakdownController batteryUsageBreakdownController = + mScreenOnTimeController = new ScreenOnTimeController(context); + mBatteryUsageBreakdownController = new BatteryUsageBreakdownController( context, getSettingsLifecycle(), (SettingsActivity) getActivity(), this); - final BatteryTipsController batteryTipsController = new BatteryTipsController(context); - - mBatteryChartPreferenceController.setOnScreenOnTimeUpdatedListener( - screenOnTimeController::handleSceenOnTimeUpdated); - mBatteryChartPreferenceController.setOnBatteryUsageUpdatedListener( - batteryUsageBreakdownController::handleBatteryUsageUpdated); - mBatteryChartPreferenceController.setOnBatteryTipsUpdatedListener( - batteryTipsController::handleBatteryTipsCardUpdated); + controllers.add(mBatteryTipsController); controllers.add(mBatteryChartPreferenceController); - controllers.add(screenOnTimeController); - controllers.add(batteryUsageBreakdownController); - controllers.add(batteryTipsController); + controllers.add(mScreenOnTimeController); + controllers.add(mBatteryUsageBreakdownController); setBatteryChartPreferenceController(); + mBatteryChartPreferenceController.setOnSelectedIndexUpdatedListener( + this::onSelectedSlotDataUpdated); + // Force UI refresh if battery usage data was loaded before UI initialization. + onSelectedSlotDataUpdated(); return controllers; } @@ -169,12 +177,17 @@ public class PowerUsageAdvanced extends PowerUsageBase { bundle.putInt(KEY_REFRESH_TYPE, refreshType); if (!mIsChartDataLoaded) { mIsChartDataLoaded = true; + mBatteryLevelData = null; + mBatteryUsageMap = null; restartLoader(LoaderIndex.BATTERY_LEVEL_DATA_LOADER, bundle, mBatteryLevelDataLoaderCallbacks); } } private void onBatteryLevelDataUpdate(BatteryLevelData batteryLevelData) { + if (!isResumed()) { + return; + } mBatteryLevelData = Optional.ofNullable(batteryLevelData); if (mBatteryChartPreferenceController != null) { mBatteryChartPreferenceController.onBatteryLevelDataUpdate(batteryLevelData); @@ -184,23 +197,130 @@ public class PowerUsageAdvanced extends PowerUsageBase { } 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); + if (!isResumed() || mBatteryLevelData == null) { + return; } + mBatteryUsageMap = DataProcessor.generateBatteryUsageMap( + getContext(), batteryDiffDataMap, mBatteryLevelData.orElse(null)); + Log.d(TAG, "onBatteryDiffDataMapUpdate: " + mBatteryUsageMap); + DataProcessor.loadLabelAndIcon(mBatteryUsageMap); + onSelectedSlotDataUpdated(); + detectAnomaly(); + logScreenUsageTime(); + if (mBatteryChartPreferenceController != null + && mBatteryLevelData.isEmpty() && isBatteryUsageMapNullOrEmpty()) { + // No available battery usage and battery level data. + mBatteryChartPreferenceController.showEmptyChart(); + } + } + + private void onSelectedSlotDataUpdated() { + if (mBatteryChartPreferenceController == null + || mScreenOnTimeController == null + || mBatteryUsageBreakdownController == null + || mBatteryUsageMap == null) { + return; + } + final int dailyIndex = mBatteryChartPreferenceController.getDailyChartIndex(); + final int hourlyIndex = mBatteryChartPreferenceController.getHourlyChartIndex(); + final String slotInformation = mBatteryChartPreferenceController.getSlotInformation(); + final BatteryDiffData slotUsageData = mBatteryUsageMap.get(dailyIndex).get(hourlyIndex); + if (slotUsageData != null) { + mScreenOnTimeController.handleSceenOnTimeUpdated( + slotUsageData.getScreenOnTime(), slotInformation); + } + mBatteryUsageBreakdownController.handleBatteryUsageUpdated( + slotUsageData, slotInformation, isBatteryUsageMapNullOrEmpty()); Log.d(TAG, String.format("Battery usage list shows in %d millis", System.currentTimeMillis() - mResumeTimestamp)); } + private void detectAnomaly() { + mExecutor.execute(() -> { + final PowerUsageFeatureProvider powerUsageFeatureProvider = + FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); + final PowerAnomalyEventList anomalyEventList = + powerUsageFeatureProvider.detectSettingsAnomaly( + getContext(), /* displayDrain= */ 0); + mHandler.post(() -> onAnomalyDetected(anomalyEventList)); + }); + } + + private void onAnomalyDetected(PowerAnomalyEventList anomalyEventList) { + if (!isResumed() || anomalyEventList == null) { + return; + } + Log.d(TAG, "anomalyEventList = " + anomalyEventList); + final PowerAnomalyEvent displayEvent = + getHighestScoreAnomalyEvent(getContext(), anomalyEventList); + if (displayEvent == null) { + return; + } + if (mBatteryTipsController != null) { + mBatteryTipsController.handleBatteryTipsCardUpdated(displayEvent); + } + } + private void setBatteryChartPreferenceController() { if (mHistPref != null && mBatteryChartPreferenceController != null) { mHistPref.setChartPreferenceController(mBatteryChartPreferenceController); } } + private boolean isBatteryUsageMapNullOrEmpty() { + final BatteryDiffData allBatteryDiffData = getAllBatteryDiffData(mBatteryUsageMap); + // If all data is null or empty, each slot must be null or empty. + return allBatteryDiffData == null + || (allBatteryDiffData.getAppDiffEntryList().isEmpty() + && allBatteryDiffData.getSystemDiffEntryList().isEmpty()); + } + + private void logScreenUsageTime() { + final BatteryDiffData allBatteryDiffData = getAllBatteryDiffData(mBatteryUsageMap); + if (allBatteryDiffData == null) { + return; + } + long totalForegroundUsageTime = 0; + for (final BatteryDiffEntry entry : allBatteryDiffData.getAppDiffEntryList()) { + totalForegroundUsageTime += entry.mForegroundUsageTimeInMs; + } + mMetricsFeatureProvider.action( + getContext(), + SettingsEnums.ACTION_BATTERY_USAGE_SCREEN_ON_TIME, + (int) allBatteryDiffData.getScreenOnTime()); + mMetricsFeatureProvider.action( + getContext(), + SettingsEnums.ACTION_BATTERY_USAGE_FOREGROUND_USAGE_TIME, + (int) totalForegroundUsageTime); + } + + @VisibleForTesting + static PowerAnomalyEvent getHighestScoreAnomalyEvent( + Context context, PowerAnomalyEventList anomalyEventList) { + if (anomalyEventList == null || anomalyEventList.getPowerAnomalyEventsCount() == 0) { + return null; + } + final Set dismissedPowerAnomalyKeys = + DatabaseUtils.getDismissedPowerAnomalyKeys(context); + Log.d(TAG, "dismissedPowerAnomalyKeys = " + dismissedPowerAnomalyKeys); + + final PowerAnomalyEvent highestScoreEvent = anomalyEventList.getPowerAnomalyEventsList() + .stream() + .filter(event -> event.hasKey() + && !dismissedPowerAnomalyKeys.contains(event.getKey().name())) + .max(Comparator.comparing(PowerAnomalyEvent::getScore)) + .orElse(null); + Log.d(TAG, "highestScoreAnomalyEvent = " + highestScoreEvent); + return highestScoreEvent; + } + + private static BatteryDiffData getAllBatteryDiffData( + Map> batteryUsageMap) { + return batteryUsageMap == null ? null : batteryUsageMap + .get(BatteryChartViewModel.SELECTED_INDEX_ALL) + .get(BatteryChartViewModel.SELECTED_INDEX_ALL); + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override @@ -237,7 +357,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { public BatteryLevelData loadInBackground() { return DataProcessManager.getBatteryLevelData( getContext(), mHandler, /*isFromPeriodJob=*/ false, - map -> PowerUsageAdvanced.this.onBatteryDiffDataMapUpdate(map)); + PowerUsageAdvanced.this::onBatteryDiffDataMapUpdate); } }; } 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 786a529814d..cd4e599546d 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -44,9 +45,9 @@ import android.util.ArrayMap; import android.view.View; import android.view.ViewPropertyAnimator; import android.widget.LinearLayout; +import android.widget.TextView; import com.android.settings.SettingsActivity; -import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; @@ -72,6 +73,8 @@ public final class BatteryChartPreferenceControllerTest { @Mock private SettingsActivity mSettingsActivity; @Mock + private TextView mChartSummaryTextView; + @Mock private BatteryChartView mDailyChartView; @Mock private BatteryChartView mHourlyChartView; @@ -112,6 +115,7 @@ public final class BatteryChartPreferenceControllerTest { setupHourlyChartViewAnimationMock(); mBatteryChartPreferenceController = createController(); mBatteryChartPreferenceController.mPrefContext = mContext; + mBatteryChartPreferenceController.mChartSummaryTextView = mChartSummaryTextView; mBatteryChartPreferenceController.mDailyChartView = mDailyChartView; mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView; BatteryDiffEntry.clearCache(); @@ -180,7 +184,6 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mDailyChartLabelTextGenerator); mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); - mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE); verify(mViewPropertyAnimator, atLeastOnce()).alpha(0f); @@ -275,29 +278,78 @@ public final class BatteryChartPreferenceControllerTest { } @Test - public void refreshUi_normalCase_returnTrue() { + public void onBatteryLevelDataUpdate_oneDay_showHourlyChartOnly() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); - mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); - assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue(); + + verify(mChartSummaryTextView).setVisibility(View.VISIBLE); + verify(mDailyChartView).setVisibility(View.GONE); + verify(mHourlyChartView).setVisibility(View.VISIBLE); } @Test - public void refreshUi_batteryIndexedMapIsNull_returnTrue() { + public void onBatteryLevelDataUpdate_selectAllForMultipleDays_showDailyChartOnly() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + + mBatteryChartPreferenceController.mDailyChartIndex = SELECTED_INDEX_ALL; + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + + verify(mChartSummaryTextView).setVisibility(View.VISIBLE); + verify(mDailyChartView).setVisibility(View.VISIBLE); + verify(mHourlyChartView, never()).setVisibility(View.VISIBLE); + } + + @Test + public void onBatteryLevelDataUpdate_selectOneDayForMultipleDays_showBothCharts() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + + mBatteryChartPreferenceController.mDailyChartIndex = 0; + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + + verify(mChartSummaryTextView).setVisibility(View.VISIBLE); + verify(mDailyChartView).setVisibility(View.VISIBLE); + verify(mHourlyChartView).setVisibility(View.VISIBLE); + } + + @Test + public void onBatteryLevelDataUpdate_batteryLevelDataIsNull_showNoChart() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate(null); - mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap()); - assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue(); + + verify(mChartSummaryTextView).setVisibility(View.GONE); + verify(mDailyChartView).setVisibility(View.GONE); + verify(mHourlyChartView).setVisibility(View.GONE); + } + + @Test + public void showEmptyChart_normalCase_showEmptyChart() { + doReturn(View.GONE).when(mHourlyChartView).getVisibility(); + + mBatteryChartPreferenceController.showEmptyChart(); + + verify(mChartSummaryTextView).setVisibility(View.VISIBLE); + verify(mDailyChartView).setVisibility(View.GONE); + verify(mHourlyChartView).setVisibility(View.VISIBLE); } @Test public void refreshUi_dailyChartViewIsNull_ignoreRefresh() { mBatteryChartPreferenceController.mDailyChartView = null; - assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse(); + + mBatteryChartPreferenceController.refreshUi(); + + verify(mChartSummaryTextView, never()).setVisibility(anyInt()); } @Test public void refreshUi_hourlyChartViewIsNull_ignoreRefresh() { mBatteryChartPreferenceController.mHourlyChartView = null; - assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse(); + + mBatteryChartPreferenceController.refreshUi(); + + verify(mChartSummaryTextView, never()).setVisibility(anyInt()); } @Test @@ -408,57 +460,6 @@ public final class BatteryChartPreferenceControllerTest { assertThat(totalHour).isEqualTo(59); } - @Test - public void getHighestScoreAnomalyEvent_withEmptyOrNullList_getNull() { - assertThat(mBatteryChartPreferenceController.getHighestScoreAnomalyEvent(null)) - .isEqualTo(null); - assertThat(mBatteryChartPreferenceController.getHighestScoreAnomalyEvent( - BatteryTestUtils.createEmptyPowerAnomalyEventList())) - .isEqualTo(null); - } - - @Test - public void getHighestScoreAnomalyEvent_withoutDismissed_getHighestScoreEvent() { - final PowerAnomalyEventList eventList = - BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); - - final PowerAnomalyEvent highestScoreEvent = - mBatteryChartPreferenceController.getHighestScoreAnomalyEvent(eventList); - - assertThat(highestScoreEvent) - .isEqualTo(BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent()); - } - - @Test - public void getHighestScoreAnomalyEvent_withBrightnessDismissed_getScreenTimeout() { - final PowerAnomalyEventList eventList = - BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); - DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); - DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, PowerAnomalyKey.KEY_BRIGHTNESS.name()); - - final PowerAnomalyEvent highestScoreEvent = - mBatteryChartPreferenceController.getHighestScoreAnomalyEvent(eventList); - - assertThat(highestScoreEvent) - .isEqualTo(BatteryTestUtils.createScreenTimeoutAnomalyEvent()); - } - - @Test - public void getHighestScoreAnomalyEvent_withAllDismissed_getNull() { - final PowerAnomalyEventList eventList = - BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); - DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); - for (PowerAnomalyKey key : PowerAnomalyKey.values()) { - DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, key.name()); - } - - final PowerAnomalyEvent highestScoreEvent = - mBatteryChartPreferenceController.getHighestScoreAnomalyEvent(eventList); - - assertThat(highestScoreEvent).isEqualTo(null); - } - - private static Long generateTimestamp(int index) { // "2021-04-23 07:00:00 UTC" + index hours return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS; @@ -481,11 +482,6 @@ public final class BatteryChartPreferenceControllerTest { 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() { final BatteryChartPreferenceController controller = new BatteryChartPreferenceController( diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java new file mode 100644 index 00000000000..ee2a8b2c9a3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java @@ -0,0 +1,92 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import com.android.settings.testutils.BatteryTestUtils; +import com.android.settings.testutils.shadow.ShadowDashboardFragment; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowDashboardFragment.class) +public final class PowerUsageAdvancedTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + } + + @Test + public void getHighestScoreAnomalyEvent_withEmptyOrNullList_getNull() { + assertThat(PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, null)).isNull(); + assertThat(PowerUsageAdvanced.getHighestScoreAnomalyEvent( + mContext, BatteryTestUtils.createEmptyPowerAnomalyEventList())).isNull(); + } + + @Test + public void getHighestScoreAnomalyEvent_withoutDismissed_getHighestScoreEvent() { + final PowerAnomalyEventList powerAnomalyEventList = + BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); + + final PowerAnomalyEvent highestScoreEvent = + PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList); + + assertThat(highestScoreEvent) + .isEqualTo(BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent()); + } + + @Test + public void getHighestScoreAnomalyEvent_withBrightnessDismissed_getScreenTimeout() { + final PowerAnomalyEventList powerAnomalyEventList = + BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); + DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); + DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, PowerAnomalyKey.KEY_BRIGHTNESS.name()); + + final PowerAnomalyEvent highestScoreEvent = + PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList); + + assertThat(highestScoreEvent) + .isEqualTo(BatteryTestUtils.createScreenTimeoutAnomalyEvent()); + } + + @Test + public void getHighestScoreAnomalyEvent_withAllDismissed_getNull() { + final PowerAnomalyEventList powerAnomalyEventList = + BatteryTestUtils.createNonEmptyPowerAnomalyEventList(); + DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext); + for (PowerAnomalyKey key : PowerAnomalyKey.values()) { + DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, key.name()); + } + + final PowerAnomalyEvent highestScoreEvent = + PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList); + + assertThat(highestScoreEvent).isEqualTo(null); + } +}