diff --git a/res/layout/preference_expand_divider.xml b/res/layout/preference_expand_divider.xml deleted file mode 100644 index 9b766881183..00000000000 --- a/res/layout/preference_expand_divider.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - diff --git a/res/layout/preference_tab.xml b/res/layout/preference_tab.xml new file mode 100644 index 00000000000..f9a78819550 --- /dev/null +++ b/res/layout/preference_tab.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 0e64431a288..163b3e1f8ed 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4993,6 +4993,14 @@ Daily battery usage chart Hourly battery usage chart + + Usage proportional breakdown since last full charge + + Usage proportional breakdown for %s + + App + + System diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml index e9274ce3b2c..4371995b71d 100644 --- a/res/xml/power_usage_advanced.xml +++ b/res/xml/power_usage_advanced.xml @@ -22,14 +22,30 @@ settings:keywords="@string/keywords_battery_usage"> + android:key="battery_chart" + settings:controller= + "com.android.settings.fuelgauge.batteryusage.BatteryChartPreferenceController" /> + android:key="battery_usage_breakdown" + settings:controller= + "com.android.settings.fuelgauge.batteryusage.BatteryUsageBreakdownController" + settings:isPreferenceVisible="false"> - + + + + + + + diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 124d4b4296c..7e1bb381224 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -21,30 +21,22 @@ import android.animation.AnimatorListenerAdapter; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.res.Configuration; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.text.TextUtils; import android.text.format.DateFormat; 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; -import androidx.preference.Preference; -import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.SettingsActivity; -import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; -import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -54,73 +46,68 @@ import com.android.settingslib.core.lifecycle.events.OnCreate; import com.android.settingslib.core.lifecycle.events.OnDestroy; import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; -import com.android.settingslib.utils.StringUtil; -import com.android.settingslib.widget.FooterPreference; import java.util.ArrayList; import java.util.Calendar; -import java.util.HashMap; import java.util.List; import java.util.Map; /** Controls the update for chart graph and the list items. */ public class BatteryChartPreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy, - OnSaveInstanceState, OnResume, ExpandDividerPreference.OnExpandListener { + OnSaveInstanceState, OnResume { private static final String TAG = "BatteryChartPreferenceController"; - private static final String KEY_FOOTER_PREF = "battery_graph_footer"; - private static final String PACKAGE_NAME_NONE = "none"; - private static final int ENABLED_ICON_ALPHA = 255; - private static final int DISABLED_ICON_ALPHA = 255 / 3; + private static final String PREFERENCE_KEY = "battery_chart"; private static final long FADE_IN_ANIMATION_DURATION = 400L; private static final long FADE_OUT_ANIMATION_DURATION = 200L; // Keys for bundle instance to restore configurations. - private static final String KEY_EXPAND_SYSTEM_INFO = "expand_system_info"; private static final String KEY_DAILY_CHART_INDEX = "daily_chart_index"; private static final String KEY_HOURLY_CHART_INDEX = "hourly_chart_index"; private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED; - @VisibleForTesting - Map> mBatteryUsageMap; + /** + * 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); + } @VisibleForTesting Context mPrefContext; - @VisibleForTesting - BatteryUtils mBatteryUtils; - @VisibleForTesting - PreferenceGroup mAppListPrefGroup; - @VisibleForTesting - ExpandDividerPreference mExpandDividerPreference; - @VisibleForTesting - boolean mIsExpanded = false; - @VisibleForTesting BatteryChartView mDailyChartView; @VisibleForTesting BatteryChartView mHourlyChartView; - @VisibleForTesting int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; @VisibleForTesting int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; + @VisibleForTesting + Map> mBatteryUsageMap; private boolean mIs24HourFormat; - private boolean mIsFooterPrefAdded = false; private boolean mHourlyChartVisible = true; private View mBatteryChartViewGroup; - private View mCategoryTitleView; - private PreferenceScreen mPreferenceScreen; - private FooterPreference mFooterPreference; private TextView mChartSummaryTextView; private BatteryChartViewModel mDailyViewModel; private List mHourlyViewModels; + private OnBatteryUsageUpdatedListener mOnBatteryUsageUpdatedListener; - private final String mPreferenceKey; private final SettingsActivity mActivity; - private final InstrumentedPreferenceFragment mFragment; private final MetricsFeatureProvider mMetricsFeatureProvider; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final AnimatorListenerAdapter mHourlyChartFadeInAdapter = @@ -135,18 +122,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll final HourlyChartLabelTextGenerator mHourlyChartLabelTextGenerator = new HourlyChartLabelTextGenerator(); - // Preference cache to avoid create new instance each time. - @VisibleForTesting - final Map mPreferenceCache = new HashMap<>(); - public BatteryChartPreferenceController( - Context context, String preferenceKey, - Lifecycle lifecycle, SettingsActivity activity, - InstrumentedPreferenceFragment fragment) { + Context context, Lifecycle lifecycle, SettingsActivity activity) { super(context); mActivity = activity; - mFragment = fragment; - mPreferenceKey = preferenceKey; mIs24HourFormat = DateFormat.is24HourFormat(context); mMetricsFeatureProvider = FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(); @@ -164,10 +143,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll savedInstanceState.getInt(KEY_DAILY_CHART_INDEX, mDailyChartIndex); mHourlyChartIndex = savedInstanceState.getInt(KEY_HOURLY_CHART_INDEX, mHourlyChartIndex); - mIsExpanded = - savedInstanceState.getBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded); - Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d isExpanded=%b", - mDailyChartIndex, mHourlyChartIndex, mIsExpanded)); + Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d", + mDailyChartIndex, mHourlyChartIndex)); } @Override @@ -191,9 +168,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } savedInstance.putInt(KEY_DAILY_CHART_INDEX, mDailyChartIndex); savedInstance.putInt(KEY_HOURLY_CHART_INDEX, mHourlyChartIndex); - savedInstance.putBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded); - Log.d(TAG, String.format("onSaveInstanceState() dailyIndex=%d hourlyIndex=%d isExpanded=%b", - mDailyChartIndex, mHourlyChartIndex, mIsExpanded)); + Log.d(TAG, String.format("onSaveInstanceState() dailyIndex=%d hourlyIndex=%d", + mDailyChartIndex, mHourlyChartIndex)); } @Override @@ -202,25 +178,12 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll BatteryDiffEntry.clearCache(); } mHandler.removeCallbacksAndMessages(/*token=*/ null); - mPreferenceCache.clear(); - if (mAppListPrefGroup != null) { - mAppListPrefGroup.removeAll(); - } } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - mPreferenceScreen = screen; mPrefContext = screen.getContext(); - mAppListPrefGroup = screen.findPreference(mPreferenceKey); - mAppListPrefGroup.setOrderingAsAdded(false); - mAppListPrefGroup.setTitle(""); - mFooterPreference = screen.findPreference(KEY_FOOTER_PREF); - // Removes footer first until usage data is loaded to avoid flashing. - if (mFooterPreference != null) { - screen.removePreference(mFooterPreference); - } } @Override @@ -230,42 +193,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll @Override public String getPreferenceKey() { - return mPreferenceKey; + return PREFERENCE_KEY; } - @Override - public boolean handlePreferenceTreeClick(Preference preference) { - if (!(preference instanceof PowerGaugePreference)) { - return false; - } - 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(); - mMetricsFeatureProvider.action( - /* attribution */ SettingsEnums.OPEN_BATTERY_USAGE, - /* action */ isAppEntry - ? SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM - : SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM, - /* pageId */ SettingsEnums.OPEN_BATTERY_USAGE, - TextUtils.isEmpty(packageName) ? PACKAGE_NAME_NONE : packageName, - (int) Math.round(diffEntry.getPercentOfTotal())); - Log.d(TAG, String.format("handleClick() label=%s key=%s package=%s", - diffEntry.getAppLabel(), histEntry.getKey(), histEntry.mPackageName)); - AdvancedPowerUsageDetail.startBatteryDetailPage( - mActivity, mFragment, diffEntry, powerPref.getPercent(), getSlotInformation()); - return true; - } - - @Override - public void onExpand(boolean isExpanded) { - mIsExpanded = isExpanded; - mMetricsFeatureProvider.action( - mPrefContext, - SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM, - isExpanded); - refreshExpandUi(); + void setOnBatteryUsageUpdatedListener(OnBatteryUsageUpdatedListener listener) { + mOnBatteryUsageUpdatedListener = listener; } void setBatteryHistoryMap( @@ -339,7 +271,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mDailyChartIndex = trapezoidIndex; mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; refreshUi(); - requestAccessibilityFocusForCategoryTitle(mDailyChartView); mMetricsFeatureProvider.action( mPrefContext, trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL @@ -355,7 +286,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex); mHourlyChartIndex = trapezoidIndex; refreshUi(); - requestAccessibilityFocusForCategoryTitle(mHourlyChartView); mMetricsFeatureProvider.action( mPrefContext, trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL @@ -385,14 +315,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll 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))); - }); + + if (mOnBatteryUsageUpdatedListener != null) { + final BatteryDiffData slotUsageData = + mBatteryUsageMap.get(mDailyChartIndex).get(mHourlyChartIndex); + mOnBatteryUsageUpdatedListener.onBatteryUsageUpdated( + slotUsageData, getSlotInformation(), isBatteryUsageMapNullOrEmpty()); + } return true; } @@ -414,9 +343,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mDailyChartView.setVisibility(View.GONE); mHourlyChartView.setVisibility(View.VISIBLE); mHourlyChartView.setViewModel(null); - removeAndCacheAllPrefs(); - addFooterPreferenceIfNeeded(false); - return false; } return true; } @@ -452,154 +378,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll return true; } - private void addAllPreferences() { - final BatteryDiffData batteryDiffData = - mBatteryUsageMap.get(mDailyChartIndex).get(mHourlyChartIndex); - addFooterPreferenceIfNeeded(batteryDiffData != null - && (!batteryDiffData.getAppDiffEntryList().isEmpty() - || !batteryDiffData.getSystemDiffEntryList().isEmpty())); - if (batteryDiffData == null) { - Log.w(TAG, "cannot find BatteryDiffEntry for daily_index: " + mDailyChartIndex - + " hourly_index: " + mHourlyChartIndex); - return; - } - // Adds app entries to the list if it is not empty. - if (!batteryDiffData.getAppDiffEntryList().isEmpty()) { - addPreferenceToScreen(batteryDiffData.getAppDiffEntryList()); - } - // Adds the expandable divider if we have system entries data. - if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) { - if (mExpandDividerPreference == null) { - mExpandDividerPreference = new ExpandDividerPreference(mPrefContext); - mExpandDividerPreference.setOnExpandListener(this); - mExpandDividerPreference.setIsExpanded(mIsExpanded); - } - mExpandDividerPreference.setOrder( - mAppListPrefGroup.getPreferenceCount()); - mAppListPrefGroup.addPreference(mExpandDividerPreference); - } - refreshExpandUi(); - } - - @VisibleForTesting - void addPreferenceToScreen(List entries) { - if (mAppListPrefGroup == null || entries.isEmpty()) { - return; - } - int prefIndex = mAppListPrefGroup.getPreferenceCount(); - for (BatteryDiffEntry entry : entries) { - boolean isAdded = false; - final String appLabel = entry.getAppLabel(); - final Drawable appIcon = entry.getAppIcon(); - if (TextUtils.isEmpty(appLabel) || appIcon == null) { - Log.w(TAG, "cannot find app resource for:" + entry.getPackageName()); - continue; - } - final String prefKey = entry.mBatteryHistEntry.getKey(); - PowerGaugePreference pref = mAppListPrefGroup.findPreference(prefKey); - if (pref != null) { - isAdded = true; - Log.w(TAG, "preference should be removed for:" + entry.getPackageName()); - } else { - pref = (PowerGaugePreference) mPreferenceCache.get(prefKey); - } - // Creates new innstance if cached preference is not found. - if (pref == null) { - pref = new PowerGaugePreference(mPrefContext); - pref.setKey(prefKey); - mPreferenceCache.put(prefKey, pref); - } - pref.setIcon(appIcon); - pref.setTitle(appLabel); - pref.setOrder(prefIndex); - pref.setPercent(entry.getPercentOfTotal()); - pref.setSingleLineTitle(true); - // Sets the BatteryDiffEntry to preference for launching detailed page. - pref.setBatteryDiffEntry(entry); - pref.setEnabled(entry.validForRestriction()); - setPreferenceSummary(pref, entry); - if (!isAdded) { - mAppListPrefGroup.addPreference(pref); - } - appIcon.setAlpha(pref.isEnabled() ? ENABLED_ICON_ALPHA : DISABLED_ICON_ALPHA); - prefIndex++; - } - } - - private void removeAndCacheAllPrefs() { - if (mAppListPrefGroup == null - || mAppListPrefGroup.getPreferenceCount() == 0) { - return; - } - final int prefsCount = mAppListPrefGroup.getPreferenceCount(); - for (int index = 0; index < prefsCount; index++) { - final Preference pref = mAppListPrefGroup.getPreference(index); - if (TextUtils.isEmpty(pref.getKey())) { - continue; - } - mPreferenceCache.put(pref.getKey(), pref); - } - mAppListPrefGroup.removeAll(); - } - - private void refreshExpandUi() { - final List systemEntries = mBatteryUsageMap.get(mDailyChartIndex).get( - mHourlyChartIndex).getSystemDiffEntryList(); - if (mIsExpanded) { - addPreferenceToScreen(systemEntries); - } else { - // Removes and recycles all system entries to hide all of them. - for (BatteryDiffEntry entry : systemEntries) { - final String prefKey = entry.mBatteryHistEntry.getKey(); - final Preference pref = mAppListPrefGroup.findPreference(prefKey); - if (pref != null) { - mAppListPrefGroup.removePreference(pref); - mPreferenceCache.put(pref.getKey(), pref); - } - } - } - } - - @VisibleForTesting - void refreshCategoryTitle() { - final String slotInformation = getSlotInformation(); - Log.d(TAG, String.format("refreshCategoryTitle:%s", slotInformation)); - if (mAppListPrefGroup != null) { - mAppListPrefGroup.setTitle( - getSlotInformation(/*isApp=*/ true, slotInformation)); - } - if (mExpandDividerPreference != null) { - mExpandDividerPreference.setTitle( - getSlotInformation(/*isApp=*/ false, slotInformation)); - } - } - - private void requestAccessibilityFocusForCategoryTitle(View view) { - if (!AccessibilityManager.getInstance(mContext).isEnabled()) { - return; - } - if (mCategoryTitleView == null) { - mCategoryTitleView = view.getRootView().findViewById(com.android.internal.R.id.title); - } - if (mCategoryTitleView != null) { - mCategoryTitleView.requestAccessibilityFocus(); - } - } - - private String getSlotInformation(boolean isApp, String slotInformation) { - // TODO: Updates the right slot information from daily and hourly chart selection. - // Null means we show all information without a specific time slot. - if (slotInformation == null) { - return isApp - ? mPrefContext.getString(R.string.battery_app_usage) - : mPrefContext.getString(R.string.battery_system_usage); - } else { - return isApp - ? mPrefContext.getString(R.string.battery_app_usage_for, slotInformation) - : mPrefContext.getString(R.string.battery_system_usage_for, slotInformation); - } - } - @VisibleForTesting String getSlotInformation() { if (mDailyViewModel == null || mHourlyViewModels == null) { @@ -624,50 +402,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll return String.format("%s %s", selectedDayText, selectedHourText); } - @VisibleForTesting - void setPreferenceSummary( - PowerGaugePreference preference, BatteryDiffEntry entry) { - final long foregroundUsageTimeInMs = entry.mForegroundUsageTimeInMs; - final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs; - final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs; - String usageTimeSummary = null; - // Not shows summary for some system components without usage time. - if (totalUsageTimeInMs == 0) { - preference.setSummary(null); - // Shows background summary only if we don't have foreground usage time. - } else if (foregroundUsageTimeInMs == 0 && backgroundUsageTimeInMs != 0) { - usageTimeSummary = buildUsageTimeInfo(backgroundUsageTimeInMs, true); - // Shows total usage summary only if total usage time is small. - } else if (totalUsageTimeInMs < DateUtils.MINUTE_IN_MILLIS) { - usageTimeSummary = buildUsageTimeInfo(totalUsageTimeInMs, false); - } else { - usageTimeSummary = buildUsageTimeInfo(totalUsageTimeInMs, false); - // Shows background usage time if it is larger than a minute. - if (backgroundUsageTimeInMs > 0) { - usageTimeSummary += - "\n" + buildUsageTimeInfo(backgroundUsageTimeInMs, true); - } - } - preference.setSummary(usageTimeSummary); - } - - private String buildUsageTimeInfo(long usageTimeInMs, boolean isBackground) { - if (usageTimeInMs < DateUtils.MINUTE_IN_MILLIS) { - return mPrefContext.getString( - isBackground - ? R.string.battery_usage_background_less_than_one_minute - : R.string.battery_usage_total_less_than_one_minute); - } - final CharSequence timeSequence = - StringUtil.formatElapsedTime(mPrefContext, usageTimeInMs, - /*withSeconds=*/ false, /*collapseTimeUnit=*/ false); - final int resourceId = - isBackground - ? R.string.battery_usage_for_background_time - : R.string.battery_usage_for_total_time; - return mPrefContext.getString(resourceId, timeSequence); - } - private void animateBatteryChartViewGroup() { if (mBatteryChartViewGroup != null && mBatteryChartViewGroup.getAlpha() == 0) { mBatteryChartViewGroup.animate().alpha(1f).setDuration(FADE_IN_ANIMATION_DURATION) @@ -725,18 +459,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll }; } - private void addFooterPreferenceIfNeeded(boolean containAppItems) { - if (mIsFooterPrefAdded || mFooterPreference == null) { - return; - } - mIsFooterPrefAdded = true; - mFooterPreference.setTitle(mPrefContext.getString( - containAppItems - ? R.string.battery_usage_screen_footer - : R.string.battery_usage_screen_footer_empty)); - mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference)); - } - private boolean isBatteryLevelDataInOneDay() { return mHourlyViewModels != null && mHourlyViewModels.size() == 1; } @@ -747,6 +469,19 @@ 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/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java new file mode 100644 index 00000000000..1452b54989a --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java @@ -0,0 +1,326 @@ +/* + * 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.settings.SettingsEnums; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.Log; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; +import androidx.viewpager2.widget.ViewPager2; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settingslib.utils.StringUtil; +import com.android.settingslib.widget.FooterPreference; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Controller for battery usage breakdown preference group. */ +public class BatteryUsageBreakdownController extends BasePreferenceController + implements LifecycleObserver, OnDestroy { + private static final String TAG = "BatteryUsageBreakdownController"; + private static final String ROOT_PREFERENCE_KEY = "battery_usage_breakdown"; + private static final String FOOTER_PREFERENCE_KEY = "battery_usage_footer"; + private static final String TAB_PREFERENCE_KEY = "battery_usage_tab"; + private static final String APP_LIST_PREFERENCE_KEY = "app_list"; + private static final String PACKAGE_NAME_NONE = "none"; + private static final int ENABLED_ICON_ALPHA = 255; + private static final int DISABLED_ICON_ALPHA = 255 / 3; + + private final SettingsActivity mActivity; + private final InstrumentedPreferenceFragment mFragment; + private final MetricsFeatureProvider mMetricsFeatureProvider; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @VisibleForTesting + final Map mPreferenceCache = new HashMap<>(); + + private int mTabPosition; + private String mSlotTimestamp; + + @VisibleForTesting + Context mPrefContext; + @VisibleForTesting + PreferenceCategory mRootPreference; + @VisibleForTesting + TabPreference mTabPreference; + @VisibleForTesting + PreferenceGroup mAppListPreferenceGroup; + @VisibleForTesting + FooterPreference mFooterPreference; + @VisibleForTesting + BatteryDiffData mBatteryDiffData; + + public BatteryUsageBreakdownController( + Context context, Lifecycle lifecycle, SettingsActivity activity, + InstrumentedPreferenceFragment fragment) { + super(context, ROOT_PREFERENCE_KEY); + mActivity = activity; + mFragment = fragment; + mMetricsFeatureProvider = + FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public void onDestroy() { + mHandler.removeCallbacksAndMessages(/*token=*/ null); + mPreferenceCache.clear(); + mAppListPreferenceGroup.removeAll(); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isSliceable() { + return false; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!(preference instanceof PowerGaugePreference)) { + return false; + } + 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(); + mMetricsFeatureProvider.action( + /* attribution */ SettingsEnums.OPEN_BATTERY_USAGE, + /* action */ isAppEntry + ? SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM + : SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM, + /* pageId */ SettingsEnums.OPEN_BATTERY_USAGE, + TextUtils.isEmpty(packageName) ? PACKAGE_NAME_NONE : packageName, + (int) Math.round(diffEntry.getPercentOfTotal())); + Log.d(TAG, String.format("handleClick() label=%s key=%s package=%s", + diffEntry.getAppLabel(), histEntry.getKey(), histEntry.mPackageName)); + AdvancedPowerUsageDetail.startBatteryDetailPage( + mActivity, mFragment, diffEntry, powerPref.getPercent(), mSlotTimestamp); + return true; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPrefContext = screen.getContext(); + mRootPreference = screen.findPreference(ROOT_PREFERENCE_KEY); + mTabPreference = screen.findPreference(TAB_PREFERENCE_KEY); + mAppListPreferenceGroup = screen.findPreference(APP_LIST_PREFERENCE_KEY); + mFooterPreference = screen.findPreference(FOOTER_PREFERENCE_KEY); + + mAppListPreferenceGroup.setOrderingAsAdded(false); + mTabPreference.initializeTabs(mFragment, new String[]{ + mPrefContext.getString(R.string.battery_usage_app_tab), + mPrefContext.getString(R.string.battery_usage_system_tab) + }); + mTabPreference.setOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + mTabPosition = position; + mHandler.post(() -> { + removeAndCacheAllPreferences(); + addAllPreferences(); + }); + } + }); + } + + /** + * Updates UI when the 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 handleBatteryUsageUpdated( + BatteryDiffData slotUsageData, String slotTimestamp, boolean isAllUsageDataEmpty) { + mBatteryDiffData = slotUsageData; + mSlotTimestamp = slotTimestamp; + + showCategoryTitle(slotTimestamp); + showTabAndAppList(); + showFooterPreference(isAllUsageDataEmpty); + } + + // TODO: request accessibility focus on category title when slot selection updated. + private void showCategoryTitle(String slotTimestamp) { + mRootPreference.setTitle(slotTimestamp == null + ? mPrefContext.getString( + R.string.battery_usage_breakdown_title_since_last_full_charge) + : mPrefContext.getString( + R.string.battery_usage_breakdown_title_for_slot, slotTimestamp)); + mRootPreference.setVisible(true); + } + + private void showFooterPreference(boolean isAllBatteryUsageEmpty) { + mFooterPreference.setTitle(mPrefContext.getString( + isAllBatteryUsageEmpty + ? R.string.battery_usage_screen_footer_empty + : R.string.battery_usage_screen_footer)); + mFooterPreference.setVisible(true); + } + + private void showTabAndAppList() { + removeAndCacheAllPreferences(); + if (mBatteryDiffData == null) { + return; + } + mTabPreference.setVisible(true); + mAppListPreferenceGroup.setVisible(true); + mHandler.post(() -> { + addAllPreferences(); + }); + } + + @VisibleForTesting + void addAllPreferences() { + if (mBatteryDiffData == null) { + return; + } + final long start = System.currentTimeMillis(); + final List entries = mTabPosition == 0 + ? mBatteryDiffData.getAppDiffEntryList() + : mBatteryDiffData.getSystemDiffEntryList(); + int prefIndex = mAppListPreferenceGroup.getPreferenceCount(); + for (BatteryDiffEntry entry : entries) { + boolean isAdded = false; + final String appLabel = entry.getAppLabel(); + final Drawable appIcon = entry.getAppIcon(); + if (TextUtils.isEmpty(appLabel) || appIcon == null) { + Log.w(TAG, "cannot find app resource for:" + entry.getPackageName()); + continue; + } + final String prefKey = entry.mBatteryHistEntry.getKey(); + PowerGaugePreference pref = mAppListPreferenceGroup.findPreference(prefKey); + if (pref != null) { + isAdded = true; + Log.w(TAG, "preference should be removed for:" + entry.getPackageName()); + } else { + pref = (PowerGaugePreference) mPreferenceCache.get(prefKey); + } + // Creates new innstance if cached preference is not found. + if (pref == null) { + pref = new PowerGaugePreference(mPrefContext); + pref.setKey(prefKey); + mPreferenceCache.put(prefKey, pref); + } + pref.setIcon(appIcon); + pref.setTitle(appLabel); + pref.setOrder(prefIndex); + pref.setPercent(entry.getPercentOfTotal()); + pref.setSingleLineTitle(true); + // Sets the BatteryDiffEntry to preference for launching detailed page. + pref.setBatteryDiffEntry(entry); + pref.setEnabled(entry.validForRestriction()); + setPreferenceSummary(pref, entry); + if (!isAdded) { + mAppListPreferenceGroup.addPreference(pref); + } + appIcon.setAlpha(pref.isEnabled() ? ENABLED_ICON_ALPHA : DISABLED_ICON_ALPHA); + prefIndex++; + } + Log.d(TAG, String.format("addAllPreferences() is finished in %d/ms", + (System.currentTimeMillis() - start))); + } + + @VisibleForTesting + void removeAndCacheAllPreferences() { + final int prefsCount = mAppListPreferenceGroup.getPreferenceCount(); + for (int index = 0; index < prefsCount; index++) { + final Preference pref = mAppListPreferenceGroup.getPreference(index); + if (TextUtils.isEmpty(pref.getKey())) { + continue; + } + mPreferenceCache.put(pref.getKey(), pref); + } + mAppListPreferenceGroup.removeAll(); + } + + @VisibleForTesting + void setPreferenceSummary( + PowerGaugePreference preference, BatteryDiffEntry entry) { + final long foregroundUsageTimeInMs = entry.mForegroundUsageTimeInMs; + final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs; + final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs; + String usageTimeSummary = null; + // Not shows summary for some system components without usage time. + if (totalUsageTimeInMs == 0) { + preference.setSummary(null); + // Shows background summary only if we don't have foreground usage time. + } else if (foregroundUsageTimeInMs == 0 && backgroundUsageTimeInMs != 0) { + usageTimeSummary = buildUsageTimeInfo(backgroundUsageTimeInMs, true); + // Shows total usage summary only if total usage time is small. + } else if (totalUsageTimeInMs < DateUtils.MINUTE_IN_MILLIS) { + usageTimeSummary = buildUsageTimeInfo(totalUsageTimeInMs, false); + } else { + usageTimeSummary = buildUsageTimeInfo(totalUsageTimeInMs, false); + // Shows background usage time if it is larger than a minute. + if (backgroundUsageTimeInMs > 0) { + usageTimeSummary += + "\n" + buildUsageTimeInfo(backgroundUsageTimeInMs, true); + } + } + preference.setSummary(usageTimeSummary); + } + + private String buildUsageTimeInfo(long usageTimeInMs, boolean isBackground) { + if (usageTimeInMs < DateUtils.MINUTE_IN_MILLIS) { + return mPrefContext.getString( + isBackground + ? R.string.battery_usage_background_less_than_one_minute + : R.string.battery_usage_total_less_than_one_minute); + } + final CharSequence timeSequence = + StringUtil.formatElapsedTime(mPrefContext, (double) usageTimeInMs, + /*withSeconds=*/ false, /*collapseTimeUnit=*/ false); + final int resourceId = + isBackground + ? R.string.battery_usage_for_background_time + : R.string.battery_usage_for_total_time; + return mPrefContext.getString(resourceId, timeSequence); + } +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/ExpandDividerPreference.java b/src/com/android/settings/fuelgauge/batteryusage/ExpandDividerPreference.java deleted file mode 100644 index 8af842bc9c2..00000000000 --- a/src/com/android/settings/fuelgauge/batteryusage/ExpandDividerPreference.java +++ /dev/null @@ -1,100 +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 android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import com.android.settings.R; - -/** A preference for expandable section divider. */ -public class ExpandDividerPreference extends Preference { - private static final String TAG = "ExpandDividerPreference"; - @VisibleForTesting - static final String PREFERENCE_KEY = "expandable_divider"; - - @VisibleForTesting - TextView mTextView; - @VisibleForTesting - ImageView mImageView; - private OnExpandListener mOnExpandListener; - - private boolean mIsExpanded = false; - private String mTitleContent = null; - - /** A callback listener for expand state is changed by users. */ - public interface OnExpandListener { - /** Callback function for expand state is changed by users. */ - void onExpand(boolean isExpanded); - } - - public ExpandDividerPreference(Context context) { - this(context, /*attrs=*/ null); - } - - public ExpandDividerPreference(Context context, AttributeSet attrs) { - super(context, attrs); - setLayoutResource(R.layout.preference_expand_divider); - setKey(PREFERENCE_KEY); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - mTextView = (TextView) view.findViewById(R.id.expand_title); - mImageView = (ImageView) view.findViewById(R.id.expand_icon); - refreshState(); - } - - @Override - public void onClick() { - setIsExpanded(!mIsExpanded); - if (mOnExpandListener != null) { - mOnExpandListener.onExpand(mIsExpanded); - } - } - - void setTitle(final String titleContent) { - mTitleContent = titleContent; - refreshState(); - } - - void setIsExpanded(boolean isExpanded) { - mIsExpanded = isExpanded; - refreshState(); - } - - void setOnExpandListener(OnExpandListener listener) { - mOnExpandListener = listener; - } - - private void refreshState() { - if (mImageView != null) { - mImageView.setImageResource(mIsExpanded - ? R.drawable.ic_settings_expand_less - : R.drawable.ic_settings_expand_more); - } - if (mTextView != null) { - mTextView.setText(mTitleContent); - } - } -} diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java index 2dba3c2ccf2..805821f9102 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java @@ -50,8 +50,7 @@ import java.util.Map; public class PowerUsageAdvanced extends PowerUsageBase { private static final String TAG = "AdvancedBatteryUsage"; private static final String KEY_REFRESH_TYPE = "refresh_type"; - private static final String KEY_BATTERY_GRAPH = "battery_graph"; - private static final String KEY_APP_LIST = "app_list"; + private static final String KEY_BATTERY_CHART = "battery_chart"; @VisibleForTesting BatteryHistoryPreference mHistPref; @@ -81,7 +80,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { super.onCreate(icicle); final Context context = getContext(); refreshFeatureFlag(context); - mHistPref = (BatteryHistoryPreference) findPreference(KEY_BATTERY_GRAPH); + mHistPref = (BatteryHistoryPreference) findPreference(KEY_BATTERY_CHART); setBatteryChartPreferenceController(); } @@ -134,9 +133,17 @@ public class PowerUsageAdvanced extends PowerUsageBase { refreshFeatureFlag(context); final List controllers = new ArrayList<>(); mBatteryChartPreferenceController = - new BatteryChartPreferenceController(context, KEY_APP_LIST, - getSettingsLifecycle(), (SettingsActivity) getActivity(), this); + new BatteryChartPreferenceController( + context, getSettingsLifecycle(), (SettingsActivity) getActivity()); + BatteryUsageBreakdownController batteryUsageBreakdownController = + new BatteryUsageBreakdownController( + context, getSettingsLifecycle(), (SettingsActivity) getActivity(), this); + + mBatteryChartPreferenceController.setOnBatteryUsageUpdatedListener( + batteryUsageBreakdownController::handleBatteryUsageUpdated); + controllers.add(mBatteryChartPreferenceController); + controllers.add(batteryUsageBreakdownController); setBatteryChartPreferenceController(); return controllers; } @@ -196,8 +203,10 @@ public class PowerUsageAdvanced extends PowerUsageBase { public List createPreferenceControllers( Context context) { final List controllers = new ArrayList<>(); - controllers.add(new BatteryChartPreferenceController(context, - KEY_APP_LIST, null /* lifecycle */, null /* activity */, + controllers.add(new BatteryChartPreferenceController( + context, null /* lifecycle */, null /* activity */)); + controllers.add(new BatteryUsageBreakdownController( + context, null /* lifecycle */, null /* activity */, null /* fragment */)); return controllers; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/TabPreference.java b/src/com/android/settings/fuelgauge/batteryusage/TabPreference.java new file mode 100644 index 00000000000..517e01cf565 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/TabPreference.java @@ -0,0 +1,152 @@ +/* + * 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 android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +/** A preference which contains a tab selection. */ +public class TabPreference extends Preference { + private static final String TAG = "TabPreference"; + + private Fragment mRootFragment; + private ViewPager2 mViewPager; + private ViewPager2.OnPageChangeCallback mOnPageChangeCallback; + + @VisibleForTesting + String[] mTabTitles; + @VisibleForTesting + int mSavedTabPosition; + @VisibleForTesting + TabLayout mTabLayout; + + public TabPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.preference_tab); + } + + void initializeTabs(Fragment rootFragment, String[] tabTitles) { + mRootFragment = rootFragment; + mTabTitles = tabTitles; + } + + void setOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback) { + mOnPageChangeCallback = callback; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + if (mViewPager != null && mTabLayout != null) { + return; + } + + mViewPager = (ViewPager2) view.findViewById(R.id.view_pager); + mViewPager.setAdapter(new FragmentAdapter(mRootFragment, mTabTitles.length)); + mViewPager.setUserInputEnabled(false); + if (mOnPageChangeCallback != null) { + mViewPager.registerOnPageChangeCallback(mOnPageChangeCallback); + } + + mTabLayout = (TabLayout) view.findViewById(R.id.tabs); + new TabLayoutMediator( + mTabLayout, mViewPager, /* autoRefresh= */ true, /* smoothScroll= */ false, + (tab, position) -> tab.setText(mTabTitles[position])).attach(); + mTabLayout.getTabAt(mSavedTabPosition).select(); + } + + @Override + public void onDetached() { + super.onDetached(); + if (mOnPageChangeCallback != null) { + mViewPager.unregisterOnPageChangeCallback(mOnPageChangeCallback); + } + } + + @Override + protected Parcelable onSaveInstanceState() { + Log.d(TAG, "onSaveInstanceState() tabPosition=" + mTabLayout.getSelectedTabPosition()); + return new SavedState(super.onSaveInstanceState(), mTabLayout.getSelectedTabPosition()); + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + super.onRestoreInstanceState(state); + return; + } + SavedState savedState = (SavedState) state; + super.onRestoreInstanceState(savedState.getSuperState()); + mSavedTabPosition = savedState.getTabPosition(); + Log.d(TAG, "onRestoreInstanceState() tabPosition=" + savedState.getTabPosition()); + } + + @VisibleForTesting + static class SavedState extends BaseSavedState { + private int mTabPosition; + + SavedState(Parcelable superState, int tabPosition) { + super(superState); + mTabPosition = tabPosition; + } + + int getTabPosition() { + return mTabPosition; + } + } + + private static class FragmentAdapter extends FragmentStateAdapter { + private final int mItemCount; + private final Fragment[] mItemFragments; + + FragmentAdapter(@NonNull Fragment rootFragment, int itemCount) { + super(rootFragment); + mItemCount = itemCount; + mItemFragments = new Fragment[mItemCount]; + for (int i = 0; i < mItemCount; i++) { + // Empty tab pages. + mItemFragments[i] = new Fragment(); + } + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return mItemFragments[position]; + } + + @Override + public int getItemCount() { + return mItemCount; + } + } +} 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 b7984421cf2..42eb1c080ba 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java @@ -24,16 +24,13 @@ 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; -import android.app.settings.SettingsEnums; import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.LocaleList; import android.text.format.DateUtils; @@ -41,27 +38,17 @@ import android.view.View; import android.view.ViewPropertyAnimator; import android.widget.LinearLayout; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceGroup; - import com.android.settings.SettingsActivity; -import com.android.settings.core.InstrumentedPreferenceFragment; -import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.testutils.FakeFeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -70,18 +57,9 @@ import java.util.TimeZone; @RunWith(RobolectricTestRunner.class) public final class BatteryChartPreferenceControllerTest { - private static final String PREF_KEY = "pref_key"; - private static final String PREF_SUMMARY = "fake preference summary"; - - @Mock - private InstrumentedPreferenceFragment mFragment; @Mock private SettingsActivity mSettingsActivity; @Mock - private PreferenceGroup mAppListGroup; - @Mock - private Drawable mDrawable; - @Mock private BatteryHistEntry mBatteryHistEntry; @Mock private BatteryChartView mDailyChartView; @@ -90,16 +68,11 @@ public final class BatteryChartPreferenceControllerTest { @Mock private ViewPropertyAnimator mViewPropertyAnimator; @Mock - private PowerGaugePreference mPowerGaugePreference; - @Mock - private BatteryUtils mBatteryUtils; - @Mock private LinearLayout.LayoutParams mLayoutParams; private Context mContext; private FakeFeatureFactory mFeatureFactory; private BatteryDiffEntry mBatteryDiffEntry; - private MetricsFeatureProvider mMetricsFeatureProvider; private BatteryChartPreferenceController mBatteryChartPreferenceController; @Before @@ -109,7 +82,6 @@ public final class BatteryChartPreferenceControllerTest { org.robolectric.shadows.ShadowSettings.set24HourTimeFormat(false); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); mFeatureFactory = FakeFeatureFactory.setupForTest(); - mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider; mContext = spy(RuntimeEnvironment.application); final Resources resources = spy(mContext.getResources()); resources.getConfiguration().setLocales(new LocaleList(new Locale("en_US"))); @@ -121,7 +93,6 @@ public final class BatteryChartPreferenceControllerTest { setupHourlyChartViewAnimationMock(); mBatteryChartPreferenceController = createController(); mBatteryChartPreferenceController.mPrefContext = mContext; - mBatteryChartPreferenceController.mAppListPrefGroup = mAppListGroup; mBatteryChartPreferenceController.mDailyChartView = mDailyChartView; mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView; mBatteryDiffEntry = new BatteryDiffEntry( @@ -161,24 +132,6 @@ public final class BatteryChartPreferenceControllerTest { assertThat(BatteryDiffEntry.sResourceCache).isNotEmpty(); } - @Test - public void onDestroy_clearPreferenceCache() { - // Ensures the testing environment is correct. - mBatteryChartPreferenceController.mPreferenceCache.put( - PREF_KEY, mPowerGaugePreference); - assertThat(mBatteryChartPreferenceController.mPreferenceCache).hasSize(1); - - mBatteryChartPreferenceController.onDestroy(); - // Verifies the result after onDestroy. - assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty(); - } - - @Test - public void onDestroy_removeAllPreferenceFromPreferenceGroup() { - mBatteryChartPreferenceController.onDestroy(); - verify(mAppListGroup).removeAll(); - } - @Test public void setBatteryChartViewModel_6Hours() { reset(mDailyChartView); @@ -313,9 +266,9 @@ public final class BatteryChartPreferenceControllerTest { } @Test - public void refreshUi_batteryIndexedMapIsNull_ignoreRefresh() { + public void refreshUi_batteryIndexedMapIsNull_returnTrue() { mBatteryChartPreferenceController.setBatteryHistoryMap(null); - assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse(); + assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue(); } @Test @@ -330,261 +283,6 @@ public final class BatteryChartPreferenceControllerTest { assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse(); } - @Test - public void removeAndCacheAllPrefs_emptyContent_ignoreRemoveAll() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); - mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap(); - doReturn(0).when(mAppListGroup).getPreferenceCount(); - - mBatteryChartPreferenceController.refreshUi(); - verify(mAppListGroup, never()).removeAll(); - } - - @Test - public void removeAndCacheAllPrefs_buildCacheAndRemoveAllPreference() { - mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6)); - mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap(); - doReturn(1).when(mAppListGroup).getPreferenceCount(); - doReturn(mPowerGaugePreference).when(mAppListGroup).getPreference(0); - doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); - doReturn(PREF_KEY).when(mPowerGaugePreference).getKey(); - doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY); - // Ensures the testing data is correct. - assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty(); - - mBatteryChartPreferenceController.refreshUi(); - - assertThat(mBatteryChartPreferenceController.mPreferenceCache.get(PREF_KEY)) - .isEqualTo(mPowerGaugePreference); - verify(mAppListGroup).removeAll(); - } - - @Test - public void addPreferenceToScreen_emptyContent_ignoreAddPreference() { - mBatteryChartPreferenceController.addPreferenceToScreen( - new ArrayList()); - verify(mAppListGroup, never()).addPreference(any()); - } - - @Test - public void addPreferenceToScreen_addPreferenceIntoScreen() { - final String appLabel = "fake app label"; - doReturn(1).when(mAppListGroup).getPreferenceCount(); - doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon(); - doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel(); - doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); - doReturn(null).when(mAppListGroup).findPreference(PREF_KEY); - doReturn(false).when(mBatteryDiffEntry).validForRestriction(); - - mBatteryChartPreferenceController.addPreferenceToScreen( - Arrays.asList(mBatteryDiffEntry)); - - // Verifies the preference cache. - final PowerGaugePreference pref = - (PowerGaugePreference) mBatteryChartPreferenceController.mPreferenceCache - .get(PREF_KEY); - assertThat(pref).isNotNull(); - // Verifies the added preference configuration. - verify(mAppListGroup).addPreference(pref); - assertThat(pref.getKey()).isEqualTo(PREF_KEY); - assertThat(pref.getTitle()).isEqualTo(appLabel); - assertThat(pref.getIcon()).isEqualTo(mDrawable); - assertThat(pref.getOrder()).isEqualTo(1); - assertThat(pref.getBatteryDiffEntry()).isSameInstanceAs(mBatteryDiffEntry); - assertThat(pref.isSingleLineTitle()).isTrue(); - assertThat(pref.isEnabled()).isFalse(); - } - - @Test - public void addPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() { - final String appLabel = "fake app label"; - doReturn(1).when(mAppListGroup).getPreferenceCount(); - doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon(); - doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel(); - doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); - doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY); - - mBatteryChartPreferenceController.addPreferenceToScreen( - Arrays.asList(mBatteryDiffEntry)); - - verify(mAppListGroup, never()).addPreference(any()); - } - - @Test - public void handlePreferenceTreeClick_notPowerGaugePreference_returnFalse() { - assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick(mAppListGroup)) - .isFalse(); - - verify(mMetricsFeatureProvider, never()) - .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM); - verify(mMetricsFeatureProvider, never()) - .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM); - } - - @Test - public void handlePreferenceTreeClick_forAppEntry_returnTrue() { - doReturn(false).when(mBatteryHistEntry).isAppEntry(); - doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry(); - - assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick( - mPowerGaugePreference)).isTrue(); - verify(mMetricsFeatureProvider) - .action( - SettingsEnums.OPEN_BATTERY_USAGE, - SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM, - SettingsEnums.OPEN_BATTERY_USAGE, - /* package name */ "none", - /* percentage of total */ 0); - } - - @Test - public void handlePreferenceTreeClick_forSystemEntry_returnTrue() { - mBatteryChartPreferenceController.mBatteryUtils = mBatteryUtils; - doReturn(true).when(mBatteryHistEntry).isAppEntry(); - doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry(); - - assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick( - mPowerGaugePreference)).isTrue(); - verify(mMetricsFeatureProvider) - .action( - SettingsEnums.OPEN_BATTERY_USAGE, - SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM, - SettingsEnums.OPEN_BATTERY_USAGE, - /* package name */ "none", - /* percentage of total */ 0); - } - - @Test - public void setPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() { - final PowerGaugePreference pref = new PowerGaugePreference(mContext); - pref.setSummary(PREF_SUMMARY); - - mBatteryChartPreferenceController.setPreferenceSummary( - pref, createBatteryDiffEntry( - /*foregroundUsageTimeInMs=*/ 0, - /*backgroundUsageTimeInMs=*/ 0)); - assertThat(pref.getSummary()).isNull(); - } - - @Test - public void setPreferenceSummary_setBackgroundUsageTimeOnly() { - final PowerGaugePreference pref = new PowerGaugePreference(mContext); - pref.setSummary(PREF_SUMMARY); - - mBatteryChartPreferenceController.setPreferenceSummary( - pref, createBatteryDiffEntry( - /*foregroundUsageTimeInMs=*/ 0, - /*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS)); - assertThat(pref.getSummary()).isEqualTo("Background: 1 min"); - } - - @Test - public void setPreferenceSummary_setTotalUsageTimeLessThanAMinute() { - final PowerGaugePreference pref = new PowerGaugePreference(mContext); - pref.setSummary(PREF_SUMMARY); - - mBatteryChartPreferenceController.setPreferenceSummary( - pref, createBatteryDiffEntry( - /*foregroundUsageTimeInMs=*/ 100, - /*backgroundUsageTimeInMs=*/ 200)); - assertThat(pref.getSummary()).isEqualTo("Total: less than a min"); - } - - @Test - public void setPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() { - final PowerGaugePreference pref = new PowerGaugePreference(mContext); - pref.setSummary(PREF_SUMMARY); - - mBatteryChartPreferenceController.setPreferenceSummary( - pref, createBatteryDiffEntry( - /*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS, - /*backgroundUsageTimeInMs=*/ 200)); - assertThat(pref.getSummary()) - .isEqualTo("Total: 1 min\nBackground: less than a min"); - } - - @Test - public void setPreferenceSummary_setTotalAndBackgroundUsageTime() { - final PowerGaugePreference pref = new PowerGaugePreference(mContext); - pref.setSummary(PREF_SUMMARY); - - mBatteryChartPreferenceController.setPreferenceSummary( - pref, createBatteryDiffEntry( - /*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS, - /*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS)); - assertThat(pref.getSummary()).isEqualTo("Total: 2 min\nBackground: 1 min"); - } - - @Test - public void onExpand_expandedIsTrue_addSystemEntriesToPreferenceGroup() { - doReturn(1).when(mAppListGroup).getPreferenceCount(); - mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap(); - doReturn("label").when(mBatteryDiffEntry).getAppLabel(); - doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon(); - doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); - - mBatteryChartPreferenceController.onExpand(/*isExpanded=*/ true); - - final ArgumentCaptor captor = ArgumentCaptor.forClass(Preference.class); - verify(mAppListGroup).addPreference(captor.capture()); - // Verifies the added preference. - assertThat(captor.getValue().getKey()).isEqualTo(PREF_KEY); - verify(mMetricsFeatureProvider) - .action( - mContext, - SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM, - true /*isExpanded*/); - } - - @Test - public void onExpand_expandedIsFalse_removeSystemEntriesFromPreferenceGroup() { - doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); - doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY); - mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap(); - // Verifies the cache is empty first. - assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty(); - - mBatteryChartPreferenceController.onExpand(/*isExpanded=*/ false); - - verify(mAppListGroup).findPreference(PREF_KEY); - verify(mAppListGroup).removePreference(mPowerGaugePreference); - assertThat(mBatteryChartPreferenceController.mPreferenceCache).hasSize(1); - verify(mMetricsFeatureProvider) - .action( - mContext, - SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM, - false /*isExpanded*/); - } - - @Test - public void refreshCategoryTitle_setLastFullChargeIntoBothTitleTextView() { - mBatteryChartPreferenceController = createController(); - mBatteryChartPreferenceController.mAppListPrefGroup = - spy(new PreferenceCategory(mContext)); - mBatteryChartPreferenceController.mExpandDividerPreference = - spy(new ExpandDividerPreference(mContext)); - // Simulates select all condition. - mBatteryChartPreferenceController.mDailyChartIndex = - BatteryChartViewModel.SELECTED_INDEX_ALL; - mBatteryChartPreferenceController.mHourlyChartIndex = - BatteryChartViewModel.SELECTED_INDEX_ALL; - - mBatteryChartPreferenceController.refreshCategoryTitle(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - // Verifies the title in the preference group. - verify(mBatteryChartPreferenceController.mAppListPrefGroup) - .setTitle(captor.capture()); - assertThat(captor.getValue()) - .isEqualTo("App usage since last full charge"); - // Verifies the title in the expandable divider. - captor = ArgumentCaptor.forClass(String.class); - verify(mBatteryChartPreferenceController.mExpandDividerPreference) - .setTitle(captor.capture()); - assertThat(captor.getValue()) - .isEqualTo("System usage since last full charge"); - } - @Test public void selectedSlotText_selectAllDaysAllHours_returnNull() { mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60)); @@ -640,16 +338,13 @@ public final class BatteryChartPreferenceControllerTest { public void onSaveInstanceState_restoreSelectedIndexAndExpandState() { final int expectedDailyIndex = 1; final int expectedHourlyIndex = 2; - final boolean isExpanded = true; final Bundle bundle = new Bundle(); mBatteryChartPreferenceController.mDailyChartIndex = expectedDailyIndex; mBatteryChartPreferenceController.mHourlyChartIndex = expectedHourlyIndex; - mBatteryChartPreferenceController.mIsExpanded = isExpanded; mBatteryChartPreferenceController.onSaveInstanceState(bundle); // Replaces the original controller with other values. mBatteryChartPreferenceController.mDailyChartIndex = -1; mBatteryChartPreferenceController.mHourlyChartIndex = -1; - mBatteryChartPreferenceController.mIsExpanded = false; mBatteryChartPreferenceController.onCreate(bundle); mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(25)); @@ -658,7 +353,6 @@ public final class BatteryChartPreferenceControllerTest { .isEqualTo(expectedDailyIndex); assertThat(mBatteryChartPreferenceController.mHourlyChartIndex) .isEqualTo(expectedHourlyIndex); - assertThat(mBatteryChartPreferenceController.mIsExpanded).isTrue(); } @Test @@ -704,36 +398,10 @@ public final class BatteryChartPreferenceControllerTest { return batteryHistoryMap; } - private Map> createBatteryUsageMap() { - final int selectedAll = BatteryChartViewModel.SELECTED_INDEX_ALL; - return Map.of( - selectedAll, Map.of( - selectedAll, new BatteryDiffData( - Arrays.asList(mBatteryDiffEntry), - Arrays.asList(mBatteryDiffEntry))), - 0, Map.of( - selectedAll, new BatteryDiffData( - Arrays.asList(mBatteryDiffEntry), - Arrays.asList(mBatteryDiffEntry)), - 0, new BatteryDiffData( - Arrays.asList(mBatteryDiffEntry), - Arrays.asList(mBatteryDiffEntry)))); - } - - private BatteryDiffEntry createBatteryDiffEntry( - long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) { - return new BatteryDiffEntry( - mContext, foregroundUsageTimeInMs, backgroundUsageTimeInMs, /*consumePower=*/ 0, - /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0, - /*backgroundUsageConsumePower=*/ 0, /*cachedUsageConsumePower=*/ 0, - mBatteryHistEntry); - } - private BatteryChartPreferenceController createController() { final BatteryChartPreferenceController controller = new BatteryChartPreferenceController( - mContext, "app_list", /*lifecycle=*/ null, - mSettingsActivity, mFragment); + mContext, /*lifecycle=*/ null, mSettingsActivity); controller.mPrefContext = mContext; return controller; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java new file mode 100644 index 00000000000..d6b5a3eaafd --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java @@ -0,0 +1,310 @@ +/* + * 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.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.LocaleList; +import android.text.format.DateUtils; + +import androidx.preference.PreferenceGroup; + +import com.android.settings.SettingsActivity; +import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +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.Arrays; +import java.util.Locale; +import java.util.TimeZone; + +@RunWith(RobolectricTestRunner.class) +public final class BatteryUsageBreakdownControllerTest { + private static final String PREF_KEY = "pref_key"; + private static final String PREF_SUMMARY = "fake preference summary"; + + @Mock + private InstrumentedPreferenceFragment mFragment; + @Mock + private SettingsActivity mSettingsActivity; + @Mock + private PreferenceGroup mAppListPreferenceGroup; + @Mock + private Drawable mDrawable; + @Mock + private BatteryHistEntry mBatteryHistEntry; + @Mock + private PowerGaugePreference mPowerGaugePreference; + + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + private BatteryDiffEntry mBatteryDiffEntry; + private MetricsFeatureProvider mMetricsFeatureProvider; + private BatteryUsageBreakdownController mBatteryUsageBreakdownController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Locale.setDefault(new Locale("en_US")); + org.robolectric.shadows.ShadowSettings.set24HourTimeFormat(false); + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider; + mContext = spy(RuntimeEnvironment.application); + final Resources resources = spy(mContext.getResources()); + resources.getConfiguration().setLocales(new LocaleList(new Locale("en_US"))); + doReturn(resources).when(mContext).getResources(); + doReturn(new String[]{"com.android.gms.persistent"}) + .when(mFeatureFactory.powerUsageFeatureProvider) + .getHideApplicationEntries(mContext); + mBatteryUsageBreakdownController = createController(); + mBatteryUsageBreakdownController.mAppListPreferenceGroup = mAppListPreferenceGroup; + mBatteryDiffEntry = new BatteryDiffEntry( + mContext, + /*foregroundUsageTimeInMs=*/ 1, + /*backgroundUsageTimeInMs=*/ 2, + /*consumePower=*/ 3, + /*foregroundUsageConsumePower=*/ 0, + /*foregroundServiceUsageConsumePower=*/ 1, + /*backgroundUsageConsumePower=*/ 2, + /*cachedUsageConsumePower=*/ 0, + mBatteryHistEntry); + mBatteryDiffEntry = spy(mBatteryDiffEntry); + mBatteryUsageBreakdownController.mBatteryDiffData = + new BatteryDiffData(Arrays.asList(mBatteryDiffEntry), Arrays.asList()); + // Adds fake testing data. + BatteryDiffEntry.sResourceCache.put( + "fakeBatteryDiffEntryKey", + new BatteryEntry.NameAndIcon("fakeName", /*icon=*/ null, /*iconId=*/ 1)); + } + + @Test + public void onDestroy_clearPreferenceCacheAndPreferenceGroupRemoveAll() { + // Ensures the testing environment is correct. + mBatteryUsageBreakdownController.mPreferenceCache.put( + PREF_KEY, mPowerGaugePreference); + assertThat(mBatteryUsageBreakdownController.mPreferenceCache).hasSize(1); + + mBatteryUsageBreakdownController.onDestroy(); + + assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty(); + } + + @Test + public void onDestroy_removeAllPreferenceFromPreferenceGroup() { + mBatteryUsageBreakdownController.onDestroy(); + verify(mAppListPreferenceGroup).removeAll(); + } + + @Test + public void addAllPreferences_addAllPreferences() { + final String appLabel = "fake app label"; + doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount(); + doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon(); + doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel(); + doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); + doReturn(null).when(mAppListPreferenceGroup).findPreference(PREF_KEY); + doReturn(false).when(mBatteryDiffEntry).validForRestriction(); + + mBatteryUsageBreakdownController.addAllPreferences(); + + // Verifies the preference cache. + final PowerGaugePreference pref = + (PowerGaugePreference) mBatteryUsageBreakdownController.mPreferenceCache + .get(PREF_KEY); + assertThat(pref).isNotNull(); + // Verifies the added preference configuration. + verify(mAppListPreferenceGroup).addPreference(pref); + assertThat(pref.getKey()).isEqualTo(PREF_KEY); + assertThat(pref.getTitle().toString()).isEqualTo(appLabel); + assertThat(pref.getIcon()).isEqualTo(mDrawable); + assertThat(pref.getOrder()).isEqualTo(1); + assertThat(pref.getBatteryDiffEntry()).isSameInstanceAs(mBatteryDiffEntry); + assertThat(pref.isSingleLineTitle()).isTrue(); + assertThat(pref.isEnabled()).isFalse(); + } + + @Test + public void addPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() { + final String appLabel = "fake app label"; + doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount(); + doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon(); + doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel(); + doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); + doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY); + + mBatteryUsageBreakdownController.addAllPreferences(); + + verify(mAppListPreferenceGroup, never()).addPreference(any()); + } + + @Test + public void removeAndCacheAllPreferences_buildCacheAndRemoveAllPreference() { + doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount(); + doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0); + doReturn(PREF_KEY).when(mBatteryHistEntry).getKey(); + doReturn(PREF_KEY).when(mPowerGaugePreference).getKey(); + doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY); + // Ensures the testing data is correct. + assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty(); + + mBatteryUsageBreakdownController.removeAndCacheAllPreferences(); + + assertThat(mBatteryUsageBreakdownController.mPreferenceCache.get(PREF_KEY)) + .isEqualTo(mPowerGaugePreference); + verify(mAppListPreferenceGroup).removeAll(); + } + + @Test + public void handlePreferenceTreeClick_notPowerGaugePreference_returnFalse() { + assertThat(mBatteryUsageBreakdownController + .handlePreferenceTreeClick(mAppListPreferenceGroup)).isFalse(); + + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM); + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM); + } + + @Test + public void handlePreferenceTreeClick_forAppEntry_returnTrue() { + doReturn(false).when(mBatteryHistEntry).isAppEntry(); + doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry(); + + assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick( + mPowerGaugePreference)).isTrue(); + verify(mMetricsFeatureProvider) + .action( + SettingsEnums.OPEN_BATTERY_USAGE, + SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM, + SettingsEnums.OPEN_BATTERY_USAGE, + /* package name */ "none", + /* percentage of total */ 0); + } + + @Test + public void handlePreferenceTreeClick_forSystemEntry_returnTrue() { + doReturn(true).when(mBatteryHistEntry).isAppEntry(); + doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry(); + + assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick( + mPowerGaugePreference)).isTrue(); + verify(mMetricsFeatureProvider) + .action( + SettingsEnums.OPEN_BATTERY_USAGE, + SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM, + SettingsEnums.OPEN_BATTERY_USAGE, + /* package name */ "none", + /* percentage of total */ 0); + } + + @Test + public void setPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() { + final PowerGaugePreference pref = new PowerGaugePreference(mContext); + pref.setSummary(PREF_SUMMARY); + + mBatteryUsageBreakdownController.setPreferenceSummary( + pref, createBatteryDiffEntry( + /*foregroundUsageTimeInMs=*/ 0, + /*backgroundUsageTimeInMs=*/ 0)); + assertThat(pref.getSummary()).isNull(); + } + + @Test + public void setPreferenceSummary_setBackgroundUsageTimeOnly() { + final PowerGaugePreference pref = new PowerGaugePreference(mContext); + pref.setSummary(PREF_SUMMARY); + + mBatteryUsageBreakdownController.setPreferenceSummary( + pref, createBatteryDiffEntry( + /*foregroundUsageTimeInMs=*/ 0, + /*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS)); + assertThat(pref.getSummary().toString()).isEqualTo("Background: 1 min"); + } + + @Test + public void setPreferenceSummary_setTotalUsageTimeLessThanAMinute() { + final PowerGaugePreference pref = new PowerGaugePreference(mContext); + pref.setSummary(PREF_SUMMARY); + + mBatteryUsageBreakdownController.setPreferenceSummary( + pref, createBatteryDiffEntry( + /*foregroundUsageTimeInMs=*/ 100, + /*backgroundUsageTimeInMs=*/ 200)); + assertThat(pref.getSummary().toString()).isEqualTo("Total: less than a min"); + } + + @Test + public void setPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() { + final PowerGaugePreference pref = new PowerGaugePreference(mContext); + pref.setSummary(PREF_SUMMARY); + + mBatteryUsageBreakdownController.setPreferenceSummary( + pref, createBatteryDiffEntry( + /*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS, + /*backgroundUsageTimeInMs=*/ 200)); + assertThat(pref.getSummary().toString()) + .isEqualTo("Total: 1 min\nBackground: less than a min"); + } + + @Test + public void setPreferenceSummary_setTotalAndBackgroundUsageTime() { + final PowerGaugePreference pref = new PowerGaugePreference(mContext); + pref.setSummary(PREF_SUMMARY); + + mBatteryUsageBreakdownController.setPreferenceSummary( + pref, createBatteryDiffEntry( + /*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS, + /*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS)); + assertThat(pref.getSummary().toString()).isEqualTo("Total: 2 min\nBackground: 1 min"); + } + + private BatteryDiffEntry createBatteryDiffEntry( + long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) { + return new BatteryDiffEntry( + mContext, foregroundUsageTimeInMs, backgroundUsageTimeInMs, /*consumePower=*/ 0, + /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0, + /*backgroundUsageConsumePower=*/ 0, /*cachedUsageConsumePower=*/ 0, + mBatteryHistEntry); + } + + private BatteryUsageBreakdownController createController() { + final BatteryUsageBreakdownController controller = + new BatteryUsageBreakdownController( + mContext, /*lifecycle=*/ null, mSettingsActivity, mFragment); + controller.mPrefContext = mContext; + return controller; + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ExpandDividerPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ExpandDividerPreferenceTest.java deleted file mode 100644 index e36f94854e7..00000000000 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ExpandDividerPreferenceTest.java +++ /dev/null @@ -1,106 +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 com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.settings.R; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -public final class ExpandDividerPreferenceTest { - - private Context mContext; - private ExpandDividerPreference mExpandDividerPreference; - - private ImageView mImageView; - private TextView mTextView; - - @Before - public void setUp() { - mContext = spy(RuntimeEnvironment.application); - mImageView = spy(new ImageView(mContext)); - mTextView = spy(new TextView(mContext)); - mExpandDividerPreference = new ExpandDividerPreference(mContext); - doReturn(R.id.expand_title).when(mTextView).getId(); - doReturn(R.id.expand_icon).when(mImageView).getId(); - } - - @Test - public void testConstructor_returnExpectedResult() { - assertThat(mExpandDividerPreference.getKey()) - .isEqualTo(ExpandDividerPreference.PREFERENCE_KEY); - assertThat(mExpandDividerPreference.getLayoutResource()) - .isEqualTo(R.layout.preference_expand_divider); - } - - @Test - public void testSetTitle_setTitleContentIntoTextView() { - final String titleContent = "title content"; - mExpandDividerPreference.mTextView = mTextView; - - mExpandDividerPreference.setTitle(titleContent); - - verify(mTextView).setText(titleContent); - } - - @Test - public void testOnClick_switchExpandStateAndInvokeCallback() { - final boolean[] isExpandedArray = new boolean[]{false}; - mExpandDividerPreference.mImageView = mImageView; - mExpandDividerPreference.setOnExpandListener( - isExpanded -> isExpandedArray[0] = isExpanded); - - // Click the item first time from false -> true. - mExpandDividerPreference.onClick(); - // Verifies the first time click result. - verify(mImageView).setImageResource(R.drawable.ic_settings_expand_less); - assertThat(isExpandedArray[0]).isTrue(); - - // Clicks the item second time from true -> false. - mExpandDividerPreference.onClick(); - // Verifies the second time click result. - verify(mImageView).setImageResource(R.drawable.ic_settings_expand_more); - assertThat(isExpandedArray[0]).isFalse(); - } - - @Test - public void testSetIsExpanded_updateStateButNotInvokeCallback() { - final boolean[] isExpandedArray = new boolean[]{false}; - mExpandDividerPreference.mImageView = mImageView; - mExpandDividerPreference.setOnExpandListener( - isExpanded -> isExpandedArray[0] = isExpanded); - - mExpandDividerPreference.setIsExpanded(true); - - verify(mImageView).setImageResource(R.drawable.ic_settings_expand_less); - assertThat(isExpandedArray[0]).isFalse(); - } -} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/TabPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/TabPreferenceTest.java new file mode 100644 index 00000000000..c106b48d457 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/TabPreferenceTest.java @@ -0,0 +1,88 @@ +/* + * 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.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import androidx.fragment.app.Fragment; +import androidx.preference.Preference; + +import com.android.settings.R; + +import com.google.android.material.tabs.TabLayout; + +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; + +@RunWith(RobolectricTestRunner.class) +public final class TabPreferenceTest { + + private Context mContext; + private TabPreference mTabPreference; + + @Mock + private Fragment mMockFragment; + @Mock + private TabLayout mMockTabLayout; + + private final String[] mTabTitles = new String[]{"tab1", "tab2"}; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mTabPreference = new TabPreference(mContext, /*attrs=*/ null); + } + + @Test + public void constructor_returnExpectedResult() { + assertThat(mTabPreference.getLayoutResource()).isEqualTo(R.layout.preference_tab); + } + + @Test + public void initializeTabs_returnExpectedResult() { + mTabPreference.initializeTabs(mMockFragment, mTabTitles); + assertThat(mTabPreference.mTabTitles).isEqualTo(mTabTitles); + } + + @Test + public void onSaveInstanceState_returnExpectedResult() { + doReturn(1).when(mMockTabLayout).getSelectedTabPosition(); + mTabPreference.mTabLayout = mMockTabLayout; + TabPreference.SavedState savedState = + (TabPreference.SavedState) mTabPreference.onSaveInstanceState(); + assertThat(savedState.getTabPosition()).isEqualTo(1); + } + + @Test + public void onRestoreInstanceState_returnExpectedResult() { + TabPreference.SavedState savedState = + new TabPreference.SavedState(Preference.BaseSavedState.EMPTY_STATE, 2); + mTabPreference.onRestoreInstanceState(savedState); + assertThat(mTabPreference.mSavedTabPosition).isEqualTo(2); + } +}