Snap for 8969739 from 4a0bc2c34b to tm-qpr1-release

Change-Id: I1d83b8b480ccf95df5a171f228f20b6081277455
This commit is contained in:
Android Build Coastguard Worker
2022-08-20 01:25:56 +00:00
39 changed files with 4023 additions and 850 deletions

View File

@@ -29,14 +29,24 @@
android:layout_marginVertical="16dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:text="@string/battery_usage_chart_graph_hint" />
android:text="@string/battery_usage_chart_graph_hint_last_full_charge" />
<com.android.settings.fuelgauge.batteryusage.BatteryChartView
android:id="@+id/battery_chart"
android:id="@+id/daily_battery_chart"
android:layout_width="match_parent"
android:layout_height="170dp"
android:layout_marginBottom="6dp"
android:visibility="invisible"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:contentDescription="@string/battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" />
<com.android.settings.fuelgauge.batteryusage.BatteryChartView
android:id="@+id/hourly_battery_chart"
android:layout_width="match_parent"
android:layout_height="170dp"
android:layout_marginBottom="16dp"
android:visibility="visible"
android:contentDescription="@string/battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" />

View File

@@ -57,6 +57,30 @@
android:maxLines="10"
style="@style/PreferenceSummaryTextStyle"/>
<RelativeLayout
android:id="@+id/card_preference_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_below="@android:id/summary"
android:visibility="gone">
<Button
android:id="@android:id/button1"
style="@style/CardPreferencePrimaryButton"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_toStartOf="@android:id/button2"
android:visibility="gone"/>
<Button
android:id="@android:id/button2"
style="@style/CardPreferenceBorderlessButton"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_alignParentEnd="true"
android:visibility="gone"/>
</RelativeLayout>
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->

View File

@@ -622,6 +622,9 @@
<!-- Whether to put the apps with system UID into system component bucket or not -->
<bool name="config_battery_combine_system_components">false</bool>
<!-- The extra value for battery tip -->
<integer name="config_battery_extra_tip_value">12</integer>
<!-- Whether to enable the advanced vpn feature. The default is not to. -->
<bool name="config_advanced_vpn_enabled">false</bool>

View File

@@ -6281,6 +6281,8 @@
<string name="battery_tip_limited_temporarily_title">Charging temporarily limited</string>
<!-- Summary for the battery limited temporarily tip [CHAR LIMIT=NONE] -->
<string name="battery_tip_limited_temporarily_summary">To preserve your battery. Learn more.</string>
<!-- Summary for the battery limited temporarily extra tip [CHAR LIMIT=NONE] -->
<string name="battery_tip_limited_temporarily_extra_summary"><xliff:g id="percent" example="10%">%1$s</xliff:g></string>
<!-- Text of battery limited temporarily tip resume charge button. [CHAR LIMIT=NONE] -->
<string name="battery_tip_limited_temporarily_dialog_resume_charge">Resume charging</string>
<!-- Message of battery limited temporarily tip. [CHAR LIMIT=NONE] -->
@@ -6337,6 +6339,8 @@
<string name="battery_tip_unrestrict_app_dialog_ok">Remove</string>
<!-- CANCEL button for dialog to remove restriction for app [CHAR LIMIT=NONE] -->
<string name="battery_tip_unrestrict_app_dialog_cancel">Cancel</string>
<!-- Charge to full button for battery defender tips [CHAR LIMIT=NONE] -->
<string name="battery_tip_charge_to_full_button">Charge to full</string>
<!-- Message for battery tip dialog to show the battery summary -->
<string name="battery_tip_dialog_summary_message" product="default">Your apps are using a normal amount of battery. If apps use too much battery, your phone will suggest actions you can take.\n\nYou can always turn on Battery Saver if you\u2019re running low on battery.</string>
@@ -6749,10 +6753,16 @@
<!-- [CHAR_LIMIT=NONE] Battery percentage: Description for preference -->
<string name="battery_percentage_description">Show battery percentage in status bar</string>
<!-- [CHAR_LIMIT=NONE] Battery usage main screen chart graph hint since last full charge -->
<string name="battery_usage_chart_graph_hint_last_full_charge">Battery level since last full charge</string>
<!-- [CHAR_LIMIT=NONE] Battery usage main screen chart graph hint -->
<string name="battery_usage_chart_graph_hint">Battery level for past 24 hr</string>
<!-- [CHAR_LIMIT=NONE] Battery app usage section header since last full charge -->
<string name="battery_app_usage">App usage since last full charge</string>
<!-- [CHAR_LIMIT=NONE] Battery app usage section header for past 24 hour -->
<string name="battery_app_usage_for_past_24">App usage for past 24 hr</string>
<!-- [CHAR_LIMIT=NONE] Battery system usage section header since last full charge -->
<string name="battery_system_usage">System usage since last full charge</string>
<!-- [CHAR_LIMIT=NONE] Battery system usage section header for past 24 hour -->
<string name="battery_system_usage_for_past_24">System usage for past 24 hr</string>
<!-- [CHAR_LIMIT=NONE] Battery system usage section header -->

View File

@@ -969,4 +969,19 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textDirection">locale</item>
</style>
<style name="CardPreferencePrimaryButton" parent="@style/ActionPrimaryButton">
<item name="android:fontFamily">google-sans-medium</item>
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">false</item>
<item name="android:singleLine">true</item>
</style>
<style name="CardPreferenceBorderlessButton"
parent="@style/Widget.AppCompat.Button.Borderless.Colored">
<item name="android:fontFamily">google-sans-medium</item>
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">false</item>
<item name="android:singleLine">true</item>
</style>
</resources>

View File

@@ -179,7 +179,7 @@ public class AppBatteryPreferenceController extends BasePreferenceController
return null;
}
final BatteryDiffEntry entry =
BatteryChartPreferenceController.getBatteryLast24HrUsageData(
BatteryChartPreferenceController.getAppBatteryUsageData(
mContext, mPackageName, mUserId);
Log.d(TAG, "loadBatteryDiffEntries():\n" + entry);
return entry;
@@ -200,10 +200,10 @@ public class AppBatteryPreferenceController extends BasePreferenceController
mBatteryPercent = Utils.formatPercentage(
mBatteryDiffEntry.getPercentOfTotal(), /* round */ true);
mPreference.setSummary(mContext.getString(
R.string.battery_summary_24hr, mBatteryPercent));
R.string.battery_summary, mBatteryPercent));
} else {
mPreference.setSummary(
mContext.getString(R.string.no_battery_summary_24hr));
mContext.getString(R.string.no_battery_summary));
}
}

View File

@@ -539,16 +539,13 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
return null;
}
if (totalTimeMs == 0) {
final int batteryWithoutUsageTime = consumedPower > 0
? R.string.battery_usage_without_time : R.string.battery_not_usage_24hr;
usageTimeSummary = getText(isChartGraphEnabled
? batteryWithoutUsageTime : R.string.battery_not_usage);
usageTimeSummary = getText(
isChartGraphEnabled && consumedPower > 0 ? R.string.battery_usage_without_time
: R.string.battery_not_usage);
} else if (slotTime == null) {
// Shows summary text with past 24 hr or full charge if slot time is null.
usageTimeSummary = isChartGraphEnabled
? getAppPast24HrActiveSummary(foregroundTimeMs, backgroundTimeMs, totalTimeMs)
: getAppFullChargeActiveSummary(
foregroundTimeMs, backgroundTimeMs, totalTimeMs);
// Shows summary text with last full charge if slot time is null.
usageTimeSummary = getAppFullChargeActiveSummary(
foregroundTimeMs, backgroundTimeMs, totalTimeMs);
} else {
// Shows summary text with slot time.
usageTimeSummary = getAppActiveSummaryWithSlotTime(

View File

@@ -139,6 +139,11 @@ public interface PowerUsageFeatureProvider {
*/
boolean isAdaptiveChargingSupported();
/**
* Returns {@code true} if current defender mode is extra defend
*/
boolean isExtraDefend();
/**
* Gets a intent for one time bypass charge limited to resume charging.
*/

View File

@@ -160,6 +160,11 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider
return null;
}
@Override
public boolean isExtraDefend() {
return false;
}
@Override
public Map<Long, Map<String, BatteryHistEntry>> getBatteryHistory(Context context) {
return null;

View File

@@ -31,6 +31,7 @@ import com.android.settings.fuelgauge.batterytip.detectors.SmartBatteryDetector;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.SummaryTip;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.fuelgauge.EstimateKt;
import com.android.settingslib.utils.AsyncLoaderCompat;
@@ -66,13 +67,16 @@ public class BatteryTipLoader extends AsyncLoaderCompat<List<BatteryTip>> {
final BatteryTipPolicy policy = new BatteryTipPolicy(getContext());
final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(TAG);
final Context context = getContext();
final boolean extraDefend = FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.isExtraDefend();
tips.add(new LowBatteryDetector(context, policy, batteryInfo).detect());
tips.add(new HighUsageDetector(context, policy, mBatteryUsageStats, batteryInfo).detect());
tips.add(new SmartBatteryDetector(
context, policy, batteryInfo, context.getContentResolver()).detect());
tips.add(new EarlyWarningDetector(policy, context).detect());
tips.add(new BatteryDefenderDetector(batteryInfo).detect());
tips.add(new BatteryDefenderDetector(batteryInfo, extraDefend).detect());
Collections.sort(tips);
return tips;
}

View File

@@ -24,10 +24,12 @@ import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
* Detect whether the battery is overheated
*/
public class BatteryDefenderDetector implements BatteryTipDetector {
private BatteryInfo mBatteryInfo;
private final BatteryInfo mBatteryInfo;
private final boolean mExtraDefend;
public BatteryDefenderDetector(BatteryInfo batteryInfo) {
public BatteryDefenderDetector(BatteryInfo batteryInfo, boolean extraDefend) {
mBatteryInfo = batteryInfo;
mExtraDefend = extraDefend;
}
@Override
@@ -36,6 +38,6 @@ public class BatteryDefenderDetector implements BatteryTipDetector {
mBatteryInfo.isOverheated
? BatteryTip.StateType.NEW
: BatteryTip.StateType.INVISIBLE;
return new BatteryDefenderTip(state);
return new BatteryDefenderTip(state, mExtraDefend);
}
}

View File

@@ -18,18 +18,36 @@ package com.android.settings.fuelgauge.batterytip.tips;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Parcel;
import android.util.Log;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.CardPreference;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import java.text.NumberFormat;
/**
* Tip to show current battery is overheated
*/
public class BatteryDefenderTip extends BatteryTip {
private static final String TAG = "BatteryDefenderTip";
private boolean mExtraDefend = false;
public BatteryDefenderTip(@StateType int state) {
this(state, false);
}
public BatteryDefenderTip(@StateType int state, boolean extraDefend) {
super(TipType.BATTERY_DEFENDER, state, true /* showDialog */);
mExtraDefend = extraDefend;
}
private BatteryDefenderTip(Parcel in) {
@@ -43,6 +61,14 @@ public class BatteryDefenderTip extends BatteryTip {
@Override
public CharSequence getSummary(Context context) {
if (mExtraDefend) {
final int extraValue = context.getResources()
.getInteger(R.integer.config_battery_extra_tip_value);
final String extraPercentage = NumberFormat.getPercentInstance()
.format(extraValue * 0.01f);
return context.getString(
R.string.battery_tip_limited_temporarily_extra_summary, extraPercentage);
}
return context.getString(R.string.battery_tip_limited_temporarily_summary);
}
@@ -62,6 +88,55 @@ public class BatteryDefenderTip extends BatteryTip {
mState);
}
@Override
public void updatePreference(Preference preference) {
super.updatePreference(preference);
final Context context = preference.getContext();
CardPreference cardPreference = castToCardPreferenceSafely(preference);
if (cardPreference == null) {
Log.e(TAG, "cast Preference to CardPreference failed");
return;
}
cardPreference.setPrimaryButtonText(
context.getString(R.string.battery_tip_charge_to_full_button));
cardPreference.setPrimaryButtonClickListener(
unused -> {
resumeCharging(context);
preference.setVisible(false);
});
cardPreference.setPrimaryButtonVisible(isPluggedIn(context));
cardPreference.setSecondaryButtonText(context.getString(R.string.see_more));
cardPreference.setSecondaryButtonClickListener(unused -> cardPreference.performClick());
cardPreference.setSecondaryButtonVisible(true);
}
private CardPreference castToCardPreferenceSafely(Preference preference) {
return preference instanceof CardPreference ? (CardPreference) preference : null;
}
private void resumeCharging(Context context) {
final Intent intent =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getResumeChargeIntent();
if (intent != null) {
context.sendBroadcast(intent);
}
Log.i(TAG, "send resume charging broadcast intent=" + intent);
}
private boolean isPluggedIn(Context context) {
final Intent batteryIntent =
context.registerReceiver(
/* receiver= */ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
return batteryIntent != null
&& batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
}
public static final Creator CREATOR = new Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new BatteryDefenderTip(in);

View File

@@ -20,7 +20,6 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -28,7 +27,9 @@ import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
@@ -53,8 +54,6 @@ import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.FooterPreference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -62,27 +61,23 @@ 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, BatteryChartView.OnSelectListener, OnResume,
ExpandDividerPreference.OnExpandListener {
OnSaveInstanceState, OnResume, ExpandDividerPreference.OnExpandListener {
private static final String TAG = "BatteryChartPreferenceController";
private static final String KEY_FOOTER_PREF = "battery_graph_footer";
private static final String PACKAGE_NAME_NONE = "none";
/** Desired battery history size for timestamp slots. */
public static final int DESIRED_HISTORY_SIZE = 25;
private static final int CHART_LEVEL_ARRAY_SIZE = 13;
private static final int CHART_KEY_ARRAY_SIZE = DESIRED_HISTORY_SIZE;
private static final long VALID_USAGE_TIME_DURATION = DateUtils.HOUR_IN_MILLIS * 2;
private static final long VALID_DIFF_DURATION = DateUtils.MINUTE_IN_MILLIS * 3;
// Keys for bundle instance to restore configurations.
private static final String KEY_EXPAND_SYSTEM_INFO = "expand_system_info";
private static final String KEY_CURRENT_TIME_SLOT = "current_time_slot";
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<Integer, List<BatteryDiffEntry>> mBatteryIndexedMap;
Map<Integer, Map<Integer, BatteryDiffData>> mBatteryUsageMap;
@VisibleForTesting
Context mPrefContext;
@@ -91,28 +86,34 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@VisibleForTesting
PreferenceGroup mAppListPrefGroup;
@VisibleForTesting
BatteryChartView mBatteryChartView;
@VisibleForTesting
ExpandDividerPreference mExpandDividerPreference;
@VisibleForTesting
boolean mIsExpanded = false;
@VisibleForTesting
int[] mBatteryHistoryLevels;
@VisibleForTesting
long[] mBatteryHistoryKeys;
@VisibleForTesting
int mTrapezoidIndex = BatteryChartView.SELECTED_INDEX_INVALID;
private boolean mIs24HourFormat = false;
@VisibleForTesting
BatteryChartView mDailyChartView;
@VisibleForTesting
BatteryChartView mHourlyChartView;
@VisibleForTesting
int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
@VisibleForTesting
int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
private boolean mIs24HourFormat;
private boolean mIsFooterPrefAdded = false;
private PreferenceScreen mPreferenceScreen;
private FooterPreference mFooterPreference;
// Daily view model only saves abbreviated day of week texts (e.g. MON). This field saves the
// full day of week texts (e.g. Monday), which is used in category title and battery detail
// page.
private List<String> mDailyTimestampFullTexts;
private BatteryChartViewModel mDailyViewModel;
private List<BatteryChartViewModel> mHourlyViewModels;
private final String mPreferenceKey;
private final SettingsActivity mActivity;
private final InstrumentedPreferenceFragment mFragment;
private final CharSequence[] mNotAllowShowEntryPackages;
private final CharSequence[] mNotAllowShowSummaryPackages;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -120,8 +121,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
// Preference cache to avoid create new instance each time.
@VisibleForTesting
final Map<String, Preference> mPreferenceCache = new HashMap<>();
@VisibleForTesting
final List<BatteryDiffEntry> mSystemEntries = new ArrayList<>();
public BatteryChartPreferenceController(
Context context, String preferenceKey,
@@ -134,10 +133,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mIs24HourFormat = DateFormat.is24HourFormat(context);
mMetricsFeatureProvider =
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
mNotAllowShowEntryPackages =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getHideApplicationEntries(context);
mNotAllowShowSummaryPackages =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
@@ -152,12 +147,14 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
if (savedInstanceState == null) {
return;
}
mTrapezoidIndex =
savedInstanceState.getInt(KEY_CURRENT_TIME_SLOT, mTrapezoidIndex);
mDailyChartIndex =
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() slotIndex=%d isExpanded=%b",
mTrapezoidIndex, mIsExpanded));
Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
}
@Override
@@ -179,10 +176,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
if (savedInstance == null) {
return;
}
savedInstance.putInt(KEY_CURRENT_TIME_SLOT, mTrapezoidIndex);
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() slotIndex=%d isExpanded=%b",
mTrapezoidIndex, mIsExpanded));
Log.d(TAG, String.format("onSaveInstanceState() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
}
@Override
@@ -204,8 +202,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mPrefContext = screen.getContext();
mAppListPrefGroup = screen.findPreference(mPreferenceKey);
mAppListPrefGroup.setOrderingAsAdded(false);
mAppListPrefGroup.setTitle(
mPrefContext.getString(R.string.battery_app_usage_for_past_24));
mAppListPrefGroup.setTitle(mPrefContext.getString(R.string.battery_app_usage));
mFooterPreference = screen.findPreference(KEY_FOOTER_PREF);
// Removes footer first until usage data is loaded to avoid flashing.
if (mFooterPreference != null) {
@@ -249,17 +246,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
return true;
}
@Override
public void onSelect(int trapezoidIndex) {
Log.d(TAG, "onChartSelect:" + trapezoidIndex);
refreshUi(trapezoidIndex, /*isForce=*/ false);
mMetricsFeatureProvider.action(
mPrefContext,
trapezoidIndex == BatteryChartView.SELECTED_INDEX_ALL
? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
: SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
}
@Override
public void onExpand(boolean isExpanded) {
mIsExpanded = isExpanded;
@@ -272,81 +258,120 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
void setBatteryHistoryMap(
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
// Resets all battery history data relative variables.
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
mBatteryIndexedMap = null;
mBatteryHistoryKeys = null;
mBatteryHistoryLevels = null;
addFooterPreferenceIfNeeded(false);
Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null"
: ("size=" + batteryHistoryMap.size())));
final BatteryLevelData batteryLevelData =
DataProcessor.getBatteryLevelData(mContext, mHandler, batteryHistoryMap,
batteryUsageMap -> {
mBatteryUsageMap = batteryUsageMap;
refreshUi();
});
Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
if (batteryLevelData == null) {
mDailyTimestampFullTexts = null;
mDailyViewModel = null;
mHourlyViewModels = null;
refreshUi();
return;
}
mBatteryHistoryKeys = getBatteryHistoryKeys(batteryHistoryMap);
mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE];
for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) {
final long timestamp = mBatteryHistoryKeys[index * 2];
final Map<String, BatteryHistEntry> entryMap = batteryHistoryMap.get(timestamp);
if (entryMap == null || entryMap.isEmpty()) {
Log.e(TAG, "abnormal entry list in the timestamp:"
+ ConvertUtils.utcToLocalTime(mPrefContext, timestamp));
continue;
mDailyTimestampFullTexts = generateTimestampDayOfWeekTexts(
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
/* isAbbreviation= */ false);
mDailyViewModel = new BatteryChartViewModel(
batteryLevelData.getDailyBatteryLevels().getLevels(),
generateTimestampDayOfWeekTexts(
mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
/* isAbbreviation= */ true),
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
mHourlyViewModels = new ArrayList<>();
for (BatteryLevelData.PeriodBatteryLevelData hourlyBatteryLevelsPerDay :
batteryLevelData.getHourlyBatteryLevelsPerDay()) {
mHourlyViewModels.add(new BatteryChartViewModel(
hourlyBatteryLevelsPerDay.getLevels(),
generateTimestampHourTexts(
mContext, hourlyBatteryLevelsPerDay.getTimestamps()),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
refreshUi();
}
void setBatteryChartView(@NonNull final BatteryChartView dailyChartView,
@NonNull final BatteryChartView hourlyChartView) {
if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) {
mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView));
}
}
private void setBatteryChartViewInner(@NonNull final BatteryChartView dailyChartView,
@NonNull final BatteryChartView hourlyChartView) {
mDailyChartView = dailyChartView;
mDailyChartView.setOnSelectListener(trapezoidIndex -> {
if (mDailyChartIndex == trapezoidIndex) {
return;
}
// Averages the battery level in each time slot to avoid corner conditions.
float batteryLevelCounter = 0;
for (BatteryHistEntry entry : entryMap.values()) {
batteryLevelCounter += entry.mBatteryLevel;
Log.d(TAG, "onDailyChartSelect:" + trapezoidIndex);
mDailyChartIndex = trapezoidIndex;
mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
refreshUi();
// TODO: Change to log daily data.
});
mHourlyChartView = hourlyChartView;
mHourlyChartView.setOnSelectListener(trapezoidIndex -> {
if (mHourlyChartIndex == trapezoidIndex) {
return;
}
mBatteryHistoryLevels[index] =
Math.round(batteryLevelCounter / entryMap.size());
}
forceRefreshUi();
Log.d(TAG, String.format(
"setBatteryHistoryMap() size=%d key=%s\nlevels=%s",
batteryHistoryMap.size(),
ConvertUtils.utcToLocalTime(mPrefContext,
mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1]),
Arrays.toString(mBatteryHistoryLevels)));
// Loads item icon and label in the background.
new LoadAllItemsInfoTask(batteryHistoryMap).execute();
}
void setBatteryChartView(final BatteryChartView batteryChartView) {
if (mBatteryChartView != batteryChartView) {
mHandler.post(() -> setBatteryChartViewInner(batteryChartView));
}
}
private void setBatteryChartViewInner(final BatteryChartView batteryChartView) {
mBatteryChartView = batteryChartView;
mBatteryChartView.setOnSelectListener(this);
forceRefreshUi();
}
private void forceRefreshUi() {
final int refreshIndex =
mTrapezoidIndex == BatteryChartView.SELECTED_INDEX_INVALID
? BatteryChartView.SELECTED_INDEX_ALL
: mTrapezoidIndex;
if (mBatteryChartView != null) {
mBatteryChartView.setLevels(mBatteryHistoryLevels);
mBatteryChartView.setSelectedIndex(refreshIndex);
setTimestampLabel();
}
refreshUi(refreshIndex, /*isForce=*/ true);
Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex);
mHourlyChartIndex = trapezoidIndex;
refreshUi();
mMetricsFeatureProvider.action(
mPrefContext,
trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
: SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
});
refreshUi();
}
@VisibleForTesting
boolean refreshUi(int trapezoidIndex, boolean isForce) {
// Invalid refresh condition.
if (mBatteryIndexedMap == null
|| mBatteryChartView == null
|| (mTrapezoidIndex == trapezoidIndex && !isForce)) {
boolean refreshUi() {
if (mDailyChartView == null || mHourlyChartView == null) {
// Chart views are not initialized.
return false;
}
if (mDailyViewModel == null || mHourlyViewModels == null) {
// Fail to get battery level data, show an empty hourly chart view.
mDailyChartView.setVisibility(View.GONE);
mHourlyChartView.setVisibility(View.VISIBLE);
mHourlyChartView.setViewModel(null);
removeAndCacheAllPrefs();
addFooterPreferenceIfNeeded(false);
return false;
}
if (mBatteryUsageMap == null) {
// Battery usage data is not ready, wait for data ready to refresh UI.
return false;
}
Log.d(TAG, String.format("refreshUi: index=%d size=%d isForce:%b",
trapezoidIndex, mBatteryIndexedMap.size(), isForce));
mTrapezoidIndex = trapezoidIndex;
if (isBatteryLevelDataInOneDay()) {
// Only 1 day data, hide the daily chart view.
mDailyChartView.setVisibility(View.GONE);
mDailyChartIndex = 0;
} else {
mDailyChartView.setVisibility(View.VISIBLE);
mDailyViewModel.setSelectedIndex(mDailyChartIndex);
mDailyChartView.setViewModel(mDailyViewModel);
}
if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
// Multiple days are selected, hide the hourly chart view.
mHourlyChartView.setVisibility(View.GONE);
} else {
mHourlyChartView.setVisibility(View.VISIBLE);
final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex);
hourlyViewModel.setSelectedIndex(mHourlyChartIndex);
mHourlyChartView.setViewModel(hourlyViewModel);
}
mHandler.post(() -> {
final long start = System.currentTimeMillis();
removeAndCacheAllPrefs();
@@ -359,43 +384,22 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
private void addAllPreferences() {
final List<BatteryDiffEntry> entries =
mBatteryIndexedMap.get(Integer.valueOf(mTrapezoidIndex));
addFooterPreferenceIfNeeded(entries != null && !entries.isEmpty());
if (entries == null) {
Log.w(TAG, "cannot find BatteryDiffEntry for:" + mTrapezoidIndex);
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;
}
// Separates data into two groups and sort them individually.
final List<BatteryDiffEntry> appEntries = new ArrayList<>();
mSystemEntries.clear();
entries.forEach(entry -> {
final String packageName = entry.getPackageName();
if (!isValidToShowEntry(packageName)) {
Log.w(TAG, "ignore showing item:" + packageName);
return;
}
if (entry.isSystemEntry()) {
mSystemEntries.add(entry);
} else {
appEntries.add(entry);
}
// Validates the usage time if users click a specific slot.
if (mTrapezoidIndex >= 0) {
validateUsageTime(entry);
}
});
Collections.sort(appEntries, BatteryDiffEntry.COMPARATOR);
Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
Log.d(TAG, String.format("addAllPreferences() app=%d system=%d",
appEntries.size(), mSystemEntries.size()));
// Adds app entries to the list if it is not empty.
if (!appEntries.isEmpty()) {
addPreferenceToScreen(appEntries);
if (!batteryDiffData.getAppDiffEntryList().isEmpty()) {
addPreferenceToScreen(batteryDiffData.getAppDiffEntryList());
}
// Adds the expabable divider if we have system entries data.
if (!mSystemEntries.isEmpty()) {
if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) {
if (mExpandDividerPreference == null) {
mExpandDividerPreference = new ExpandDividerPreference(mPrefContext);
mExpandDividerPreference.setOnExpandListener(this);
@@ -469,11 +473,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
private void refreshExpandUi() {
final List<BatteryDiffEntry> systemEntries = mBatteryUsageMap.get(mDailyChartIndex).get(
mHourlyChartIndex).getSystemDiffEntryList();
if (mIsExpanded) {
addPreferenceToScreen(mSystemEntries);
addPreferenceToScreen(systemEntries);
} else {
// Removes and recycles all system entries to hide all of them.
for (BatteryDiffEntry entry : mSystemEntries) {
for (BatteryDiffEntry entry : systemEntries) {
final String prefKey = entry.mBatteryHistEntry.getKey();
final Preference pref = mAppListPrefGroup.findPreference(prefKey);
if (pref != null) {
@@ -499,11 +505,12 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
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_for_past_24)
: mPrefContext.getString(R.string.battery_system_usage_for_past_24);
? 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)
@@ -511,17 +518,33 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
}
private String getSlotInformation() {
if (mTrapezoidIndex < 0) {
@VisibleForTesting
String getSlotInformation() {
if (mDailyTimestampFullTexts == null || mDailyViewModel == null
|| mHourlyViewModels == null) {
// No data
return null;
}
final String fromHour = ConvertUtils.utcToLocalTimeHour(mPrefContext,
mBatteryHistoryKeys[mTrapezoidIndex * 2], mIs24HourFormat);
final String toHour = ConvertUtils.utcToLocalTimeHour(mPrefContext,
mBatteryHistoryKeys[(mTrapezoidIndex + 1) * 2], mIs24HourFormat);
return mIs24HourFormat
? String.format("%s%s", fromHour, toHour)
: String.format("%s %s", fromHour, toHour);
if (isAllSelected()) {
return null;
}
final String selectedDayText = mDailyTimestampFullTexts.get(mDailyChartIndex);
if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
return selectedDayText;
}
final String fromHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
mHourlyChartIndex);
final String toHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
mHourlyChartIndex + 1);
final String selectedHourText =
String.format("%s%s%s", fromHourText, mIs24HourFormat ? "-" : " - ", toHourText);
if (isBatteryLevelDataInOneDay()) {
return selectedHourText;
}
return String.format("%s %s", selectedDayText, selectedHourText);
}
@VisibleForTesting
@@ -575,22 +598,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@VisibleForTesting
boolean isValidToShowSummary(String packageName) {
return !contains(packageName, mNotAllowShowSummaryPackages);
}
@VisibleForTesting
boolean isValidToShowEntry(String packageName) {
return !contains(packageName, mNotAllowShowEntryPackages);
}
@VisibleForTesting
void setTimestampLabel() {
if (mBatteryChartView == null || mBatteryHistoryKeys == null) {
return;
}
final long latestTimestamp =
mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1];
mBatteryChartView.setLatestTimestamp(latestTimestamp);
return !DataProcessor.contains(packageName, mNotAllowShowSummaryPackages);
}
private void addFooterPreferenceIfNeeded(boolean containAppItems) {
@@ -605,60 +613,65 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference));
}
private static boolean contains(String target, CharSequence[] packageNames) {
if (target != null && packageNames != null) {
for (CharSequence packageName : packageNames) {
if (TextUtils.equals(target, packageName)) {
return true;
}
}
}
return false;
private boolean isBatteryLevelDataInOneDay() {
return mHourlyViewModels != null && mHourlyViewModels.size() == 1;
}
@VisibleForTesting
static boolean validateUsageTime(BatteryDiffEntry entry) {
final long foregroundUsageTimeInMs = entry.mForegroundUsageTimeInMs;
final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs;
final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs;
if (foregroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
|| backgroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
|| totalUsageTimeInMs > VALID_USAGE_TIME_DURATION) {
Log.e(TAG, "validateUsageTime() fail for\n" + entry);
return false;
private boolean isAllSelected() {
return (isBatteryLevelDataInOneDay()
|| mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL)
&& mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL;
}
private static List<String> generateTimestampDayOfWeekTexts(@NonNull final Context context,
@NonNull final List<Long> timestamps, final boolean isAbbreviation) {
final ArrayList<String> texts = new ArrayList<>();
for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeDayOfWeek(context, timestamp, isAbbreviation));
}
return true;
return texts;
}
private static List<String> generateTimestampHourTexts(
@NonNull final Context context, @NonNull final List<Long> timestamps) {
final boolean is24HourFormat = DateFormat.is24HourFormat(context);
final ArrayList<String> texts = new ArrayList<>();
for (Long timestamp : timestamps) {
texts.add(ConvertUtils.utcToLocalTimeHour(context, timestamp, is24HourFormat));
}
return texts;
}
/** Used for {@link AppBatteryPreferenceController}. */
public static List<BatteryDiffEntry> getBatteryLast24HrUsageData(Context context) {
public static List<BatteryDiffEntry> getAppBatteryUsageData(Context context) {
final long start = System.currentTimeMillis();
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
FeatureFactory.getFactory(context)
.getPowerUsageFeatureProvider(context)
.getBatteryHistory(context);
.getBatteryHistorySinceLastFullCharge(context);
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
return null;
}
Log.d(TAG, String.format("getBatteryLast24HrData() size=%d time=&d/ms",
Log.d(TAG, String.format("getBatterySinceLastFullChargeUsageData() size=%d time=%d/ms",
batteryHistoryMap.size(), (System.currentTimeMillis() - start)));
final Map<Integer, List<BatteryDiffEntry>> batteryIndexedMap =
ConvertUtils.getIndexedUsageMap(
context,
/*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1,
getBatteryHistoryKeys(batteryHistoryMap),
batteryHistoryMap,
/*purgeLowPercentageAndFakeData=*/ true);
return batteryIndexedMap.get(BatteryChartView.SELECTED_INDEX_ALL);
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageData =
DataProcessor.getBatteryUsageData(context, batteryHistoryMap);
return batteryUsageData == null
? null
: batteryUsageData
.get(BatteryChartViewModel.SELECTED_INDEX_ALL)
.get(BatteryChartViewModel.SELECTED_INDEX_ALL)
.getAppDiffEntryList();
}
/** Used for {@link AppBatteryPreferenceController}. */
public static BatteryDiffEntry getBatteryLast24HrUsageData(
public static BatteryDiffEntry getAppBatteryUsageData(
Context context, String packageName, int userId) {
if (packageName == null) {
return null;
}
final List<BatteryDiffEntry> entries = getBatteryLast24HrUsageData(context);
final List<BatteryDiffEntry> entries = getAppBatteryUsageData(context);
if (entries == null) {
return null;
}
@@ -673,65 +686,4 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
}
return null;
}
private static long[] getBatteryHistoryKeys(
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
final List<Long> batteryHistoryKeyList =
new ArrayList<>(batteryHistoryMap.keySet());
Collections.sort(batteryHistoryKeyList);
final long[] batteryHistoryKeys = new long[CHART_KEY_ARRAY_SIZE];
for (int index = 0; index < CHART_KEY_ARRAY_SIZE; index++) {
batteryHistoryKeys[index] = batteryHistoryKeyList.get(index);
}
return batteryHistoryKeys;
}
// Loads all items icon and label in the background.
private final class LoadAllItemsInfoTask
extends AsyncTask<Void, Void, Map<Integer, List<BatteryDiffEntry>>> {
private long[] mBatteryHistoryKeysCache;
private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
private LoadAllItemsInfoTask(
Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
this.mBatteryHistoryMap = batteryHistoryMap;
this.mBatteryHistoryKeysCache = mBatteryHistoryKeys;
}
@Override
protected Map<Integer, List<BatteryDiffEntry>> doInBackground(Void... voids) {
if (mPrefContext == null || mBatteryHistoryKeysCache == null) {
return null;
}
final long startTime = System.currentTimeMillis();
final Map<Integer, List<BatteryDiffEntry>> indexedUsageMap =
ConvertUtils.getIndexedUsageMap(
mPrefContext, /*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1,
mBatteryHistoryKeysCache, mBatteryHistoryMap,
/*purgeLowPercentageAndFakeData=*/ true);
// Pre-loads each BatteryDiffEntry relative icon and label for all slots.
for (List<BatteryDiffEntry> entries : indexedUsageMap.values()) {
entries.forEach(entry -> entry.loadLabelAndIcon());
}
Log.d(TAG, String.format("execute LoadAllItemsInfoTask in %d/ms",
(System.currentTimeMillis() - startTime)));
return indexedUsageMap;
}
@Override
protected void onPostExecute(
Map<Integer, List<BatteryDiffEntry>> indexedUsageMap) {
mBatteryHistoryMap = null;
mBatteryHistoryKeysCache = null;
if (indexedUsageMap == null) {
return;
}
// Posts results back to main thread to refresh UI.
mHandler.post(() -> {
mBatteryIndexedMap = indexedUsageMap;
forceRefreshUi();
});
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.Utils.formatPercentage;
import static java.lang.Math.round;
import static java.util.Objects.requireNonNull;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
@@ -29,8 +30,6 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Handler;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.HapticFeedbackConstants;
@@ -39,6 +38,7 @@ import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.widget.AppCompatImageView;
@@ -46,7 +46,7 @@ import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -58,36 +58,26 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
private static final int DEFAULT_TRAPEZOID_COUNT = 12;
private static final int DEFAULT_TIMESTAMP_COUNT = 4;
private static final int TIMESTAMP_GAPS_COUNT = DEFAULT_TIMESTAMP_COUNT - 1;
private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
private static final long UPDATE_STATE_DELAYED_TIME = 500L;
/** Selects all trapezoid shapes. */
public static final int SELECTED_INDEX_ALL = -1;
public static final int SELECTED_INDEX_INVALID = -2;
/** A callback listener for selected group index is updated. */
public interface OnSelectListener {
/** The callback function for selected group index is updated. */
void onSelect(int trapezoidIndex);
}
private BatteryChartViewModel mViewModel;
private int mDividerWidth;
private int mDividerHeight;
private int mTrapezoidCount;
private float mTrapezoidVOffset;
private float mTrapezoidHOffset;
private boolean mIsSlotsClickabled;
private String[] mPercentages = getPercentages();
@VisibleForTesting
int mHoveredIndex = SELECTED_INDEX_INVALID;
@VisibleForTesting
int mSelectedIndex = SELECTED_INDEX_INVALID;
@VisibleForTesting
String[] mTimestamps;
int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
// Colors for drawing the trapezoid shape and dividers.
private int mTrapezoidColor;
@@ -98,25 +88,26 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private final Rect mIndent = new Rect();
private final Rect[] mPercentageBounds =
new Rect[]{new Rect(), new Rect(), new Rect()};
// For drawing the timestamp information.
private final Rect[] mTimestampsBounds =
new Rect[]{new Rect(), new Rect(), new Rect(), new Rect()};
// For drawing the axis label information.
private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
@VisibleForTesting
Handler mHandler = new Handler();
@VisibleForTesting
final Runnable mUpdateClickableStateRun = () -> updateClickableState();
private int[] mLevels;
private Paint mTextPaint;
private Paint mDividerPaint;
private Paint mTrapezoidPaint;
@VisibleForTesting
Paint mTrapezoidCurvePaint = null;
private TrapezoidSlot[] mTrapezoidSlots;
@VisibleForTesting
TrapezoidSlot[] mTrapezoidSlots;
// Records the location to calculate selected index.
private float mTouchUpEventX = Float.MIN_VALUE;
@VisibleForTesting
float mTouchUpEventX = Float.MIN_VALUE;
private BatteryChartView.OnSelectListener mOnSelectListener;
public BatteryChartView(Context context) {
@@ -128,57 +119,25 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
initializeColors(context);
// Registers the click event listener.
setOnClickListener(this);
setSelectedIndex(SELECTED_INDEX_ALL);
setTrapezoidCount(DEFAULT_TRAPEZOID_COUNT);
setClickable(false);
setLatestTimestamp(0);
requestLayout();
}
/** Sets the total trapezoid count for drawing. */
public void setTrapezoidCount(int trapezoidCount) {
Log.i(TAG, "trapezoidCount:" + trapezoidCount);
mTrapezoidCount = trapezoidCount;
mTrapezoidSlots = new TrapezoidSlot[trapezoidCount];
// Allocates the trapezoid slot array.
for (int index = 0; index < trapezoidCount; index++) {
mTrapezoidSlots[index] = new TrapezoidSlot();
}
invalidate();
}
/** Sets all levels value to draw the trapezoid shape */
public void setLevels(int[] levels) {
Log.d(TAG, "setLevels() " + (levels == null ? "null" : levels.length));
if (levels == null) {
mLevels = null;
return;
}
// We should provide trapezoid count + 1 data to draw all trapezoids.
mLevels = levels.length == mTrapezoidCount + 1 ? levels : null;
setClickable(false);
invalidate();
if (mLevels == null) {
return;
}
// Sets the chart is clickable if there is at least one valid item in it.
for (int index = 0; index < mLevels.length - 1; index++) {
if (mLevels[index] != 0 && mLevels[index + 1] != 0) {
setClickable(true);
break;
}
}
}
/** Sets the selected group index to draw highlight effect. */
public void setSelectedIndex(int index) {
if (mSelectedIndex != index) {
mSelectedIndex = index;
/** Sets the data model of this view. */
public void setViewModel(BatteryChartViewModel viewModel) {
if (viewModel == null) {
mViewModel = null;
invalidate();
// Callbacks to the listener if we have.
if (mOnSelectListener != null) {
mOnSelectListener.onSelect(mSelectedIndex);
}
return;
}
Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.",
viewModel.size(), viewModel.selectedIndex()));
mViewModel = viewModel;
initializeAxisLabelsBounds();
initializeTrapezoidSlots(viewModel.size() - 1);
setClickable(hasAnyValidTrapezoid(viewModel));
requestLayout();
}
/** Sets the callback to monitor the selected group index. */
@@ -195,29 +154,6 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
} else {
mTextPaint = null;
}
setVisibility(View.VISIBLE);
requestLayout();
}
/** Sets the latest timestamp for drawing into x-axis information. */
public void setLatestTimestamp(long latestTimestamp) {
if (latestTimestamp == 0) {
latestTimestamp = Clock.systemUTC().millis();
}
if (mTimestamps == null) {
mTimestamps = new String[DEFAULT_TIMESTAMP_COUNT];
}
final long timeSlotOffset =
DateUtils.HOUR_IN_MILLIS * (/*total 24 hours*/ 24 / TIMESTAMP_GAPS_COUNT);
final boolean is24HourFormat = DateFormat.is24HourFormat(getContext());
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
mTimestamps[index] =
ConvertUtils.utcToLocalTimeHour(
getContext(),
latestTimestamp - (TIMESTAMP_GAPS_COUNT - index)
* timeSlotOffset,
is24HourFormat);
}
requestLayout();
}
@@ -226,6 +162,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Measures text bounds and updates indent configuration.
if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.LEFT);
for (int index = 0; index < mPercentages.length; index++) {
mTextPaint.getTextBounds(
mPercentages[index], 0, mPercentages[index].length(),
@@ -235,15 +172,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mIndent.top = mPercentageBounds[0].height();
mIndent.right = mPercentageBounds[0].width() + mTextPadding;
if (mTimestamps != null) {
int maxHeight = 0;
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
mTextPaint.getTextBounds(
mTimestamps[index], 0, mTimestamps[index].length(),
mTimestampsBounds[index]);
maxHeight = Math.max(maxHeight, mTimestampsBounds[index].height());
if (mViewModel != null) {
int maxTop = 0;
for (int index = 0; index < mViewModel.size(); index++) {
final String text = mViewModel.texts().get(index);
mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index));
maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top);
}
mIndent.bottom = maxHeight + round(mTextPadding * 1.5f);
mIndent.bottom = maxTop + round(mTextPadding * 2f);
}
Log.d(TAG, "setIndent:" + mPercentageBounds[0]);
} else {
@@ -254,7 +190,12 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// Before mLevels initialized, the count of trapezoids is unknown. Only draws the
// horizontal percentages and dividers.
drawHorizontalDividers(canvas);
if (mViewModel == null) {
return;
}
drawVerticalDividers(canvas);
drawTrapezoids(canvas);
}
@@ -294,7 +235,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
public void onHoverChanged(boolean hovered) {
super.onHoverChanged(hovered);
if (!hovered) {
mHoveredIndex = SELECTED_INDEX_INVALID; // reset
mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
invalidate();
}
}
@@ -307,15 +248,15 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
}
final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX);
// Ignores the click event if the level is zero.
if (trapezoidIndex == SELECTED_INDEX_INVALID
|| !isValidToDraw(trapezoidIndex)) {
if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
|| !isValidToDraw(mViewModel, trapezoidIndex)) {
return;
}
// Selects all if users click the same trapezoid item two times.
if (trapezoidIndex == mSelectedIndex) {
setSelectedIndex(SELECTED_INDEX_ALL);
} else {
setSelectedIndex(trapezoidIndex);
if (mOnSelectListener != null) {
// Selects all if users click the same trapezoid item two times.
mOnSelectListener.onSelect(
trapezoidIndex == mViewModel.selectedIndex()
? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex);
}
view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
}
@@ -364,8 +305,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2);
} else if (mIsSlotsClickabled) {
mTrapezoidCurvePaint = null;
// Sets levels again to force update the click state.
setLevels(mLevels);
// Sets view model again to force update the click state.
setViewModel(mViewModel);
}
invalidate();
}
@@ -380,6 +321,13 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
super.setClickable(clickable);
}
private void initializeTrapezoidSlots(int count) {
mTrapezoidSlots = new TrapezoidSlot[count];
for (int index = 0; index < mTrapezoidSlots.length; index++) {
mTrapezoidSlots[index] = new TrapezoidSlot();
}
}
private void initializeColors(Context context) {
setBackgroundColor(Color.TRANSPARENT);
mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
@@ -434,10 +382,10 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private void drawPercentage(Canvas canvas, int index, float offsetY) {
if (mTextPaint != null) {
mTextPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(
mPercentages[index],
getWidth() - mPercentageBounds[index].width()
- mPercentageBounds[index].left,
getWidth(),
offsetY + mPercentageBounds[index].height() * .5f,
mTextPaint);
}
@@ -445,9 +393,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private void drawVerticalDividers(Canvas canvas) {
final int width = getWidth() - mIndent.right;
final int dividerCount = mTrapezoidCount + 1;
final int dividerCount = mTrapezoidSlots.length + 1;
final float dividerSpace = dividerCount * mDividerWidth;
final float unitWidth = (width - dividerSpace) / (float) mTrapezoidCount;
final float unitWidth = (width - dividerSpace) / (float) mTrapezoidSlots.length;
final float bottomY = getHeight() - mIndent.bottom;
final float startY = bottomY - mDividerHeight;
final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * .5f;
@@ -463,66 +411,140 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
}
startX = nextX;
}
// Draws the timestamp slot information.
if (mTimestamps != null) {
final float[] xOffsets = new float[DEFAULT_TIMESTAMP_COUNT];
final float baselineX = mDividerWidth * .5f;
final float offsetX = mDividerWidth + unitWidth;
final int slotBarOffset = (/*total 12 bars*/ 12) / TIMESTAMP_GAPS_COUNT;
for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
xOffsets[index] = baselineX + index * offsetX * slotBarOffset;
// Draws the axis label slot information.
if (mViewModel != null) {
final float baselineY = getHeight() - mTextPadding * 1.5f;
Rect[] axisLabelDisplayAreas;
switch (mViewModel.axisLabelPosition()) {
case CENTER_OF_TRAPEZOIDS:
axisLabelDisplayAreas = getAxisLabelDisplayAreas(
/* size= */ mViewModel.size() - 1,
/* baselineX= */ mDividerWidth + unitWidth * .5f,
/* offsetX= */ mDividerWidth + unitWidth,
baselineY,
/* shiftFirstAndLast= */ false);
break;
case BETWEEN_TRAPEZOIDS:
default:
axisLabelDisplayAreas = getAxisLabelDisplayAreas(
/* size= */ mViewModel.size(),
/* baselineX= */ mDividerWidth * .5f,
/* offsetX= */ mDividerWidth + unitWidth,
baselineY,
/* shiftFirstAndLast= */ true);
break;
}
drawTimestamp(canvas, xOffsets);
drawAxisLabels(canvas, axisLabelDisplayAreas, baselineY);
}
}
private void drawTimestamp(Canvas canvas, float[] xOffsets) {
// Draws the 1st timestamp info.
canvas.drawText(
mTimestamps[0],
xOffsets[0] - mTimestampsBounds[0].left,
getTimestampY(0), mTextPaint);
final int latestIndex = DEFAULT_TIMESTAMP_COUNT - 1;
// Draws the last timestamp info.
canvas.drawText(
mTimestamps[latestIndex],
xOffsets[latestIndex] - mTimestampsBounds[latestIndex].width()
- mTimestampsBounds[latestIndex].left,
getTimestampY(latestIndex), mTextPaint);
// Draws the rest of timestamp info since it is located in the center.
for (int index = 1; index <= DEFAULT_TIMESTAMP_COUNT - 2; index++) {
canvas.drawText(
mTimestamps[index],
xOffsets[index]
- (mTimestampsBounds[index].width() - mTimestampsBounds[index].left)
* .5f,
getTimestampY(index), mTextPaint);
/** Gets all the axis label texts displaying area positions if they are shown. */
private Rect[] getAxisLabelDisplayAreas(final int size, final float baselineX,
final float offsetX, final float baselineY, final boolean shiftFirstAndLast) {
final Rect[] result = new Rect[size];
for (int index = 0; index < result.length; index++) {
final float width = mAxisLabelsBounds.get(index).width();
float middle = baselineX + index * offsetX;
if (shiftFirstAndLast) {
if (index == 0) {
middle += width * .5f;
}
if (index == size - 1) {
middle -= width * .5f;
}
}
final float left = middle - width * .5f;
final float right = left + width;
final float top = baselineY + mAxisLabelsBounds.get(index).top;
final float bottom = top + mAxisLabelsBounds.get(index).height();
result[index] = new Rect(round(left), round(top), round(right), round(bottom));
}
return result;
}
private void drawAxisLabels(Canvas canvas, final Rect[] displayAreas, final float baselineY) {
final int lastIndex = displayAreas.length - 1;
// Suppose first and last labels are always able to draw.
drawAxisLabelText(canvas, 0, displayAreas[0], baselineY);
drawAxisLabelText(canvas, lastIndex, displayAreas[lastIndex], baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(canvas, displayAreas, 0, lastIndex, baselineY);
}
/**
* Recursively draws axis labels between the start index and the end index. If the inner number
* can be exactly divided into 2 parts, check and draw the middle index label and then
* recursively draw the 2 parts. Otherwise, divide into 3 parts. Check and draw the middle two
* labels and then recursively draw the 3 parts. If there are any overlaps, skip drawing and go
* back to the uplevel of the recursion.
*/
private void drawAxisLabelsBetweenStartIndexAndEndIndex(Canvas canvas,
final Rect[] displayAreas, final int startIndex, final int endIndex,
final float baselineY) {
if (endIndex - startIndex <= 1) {
return;
}
if ((endIndex - startIndex) % 2 == 0) {
int middleIndex = (startIndex + endIndex) / 2;
if (hasOverlap(displayAreas, startIndex, middleIndex)
|| hasOverlap(displayAreas, middleIndex, endIndex)) {
return;
}
drawAxisLabelText(canvas, middleIndex, displayAreas[middleIndex], baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, startIndex, middleIndex, baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, middleIndex, endIndex, baselineY);
} else {
int middleIndex1 = startIndex + round((endIndex - startIndex) / 3f);
int middleIndex2 = startIndex + round((endIndex - startIndex) * 2 / 3f);
if (hasOverlap(displayAreas, startIndex, middleIndex1)
|| hasOverlap(displayAreas, middleIndex1, middleIndex2)
|| hasOverlap(displayAreas, middleIndex2, endIndex)) {
return;
}
drawAxisLabelText(canvas, middleIndex1, displayAreas[middleIndex1], baselineY);
drawAxisLabelText(canvas, middleIndex2, displayAreas[middleIndex2], baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, startIndex, middleIndex1, baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, middleIndex1, middleIndex2, baselineY);
drawAxisLabelsBetweenStartIndexAndEndIndex(
canvas, displayAreas, middleIndex2, endIndex, baselineY);
}
}
private int getTimestampY(int index) {
return getHeight() - mTimestampsBounds[index].height()
+ (mTimestampsBounds[index].height() + mTimestampsBounds[index].top)
+ round(mTextPadding * 1.5f);
private boolean hasOverlap(
final Rect[] displayAreas, final int leftIndex, final int rightIndex) {
return displayAreas[leftIndex].right + mTextPadding * 2f > displayAreas[rightIndex].left;
}
private void drawAxisLabelText(
Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
mTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(
mViewModel.texts().get(index),
displayArea.centerX(),
baselineY,
mTextPaint);
}
private void drawTrapezoids(Canvas canvas) {
// Ignores invalid trapezoid data.
if (mLevels == null) {
if (mViewModel == null) {
return;
}
final float trapezoidBottom =
getHeight() - mIndent.bottom - mDividerHeight - mDividerWidth
- mTrapezoidVOffset;
final float availableSpace = trapezoidBottom - mDividerWidth * .5f - mIndent.top;
final float availableSpace =
trapezoidBottom - mDividerWidth * .5f - mIndent.top - mTrapezoidVOffset;
final float unitHeight = availableSpace / 100f;
// Draws all trapezoid shapes into the canvas.
final Path trapezoidPath = new Path();
Path trapezoidCurvePath = null;
for (int index = 0; index < mTrapezoidCount; index++) {
for (int index = 0; index < mTrapezoidSlots.length; index++) {
// Not draws the trapezoid for corner or not initialization cases.
if (!isValidToDraw(index)) {
if (!isValidToDraw(mViewModel, index)) {
if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
trapezoidCurvePath = null;
@@ -530,17 +552,18 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
continue;
}
// Configures the trapezoid paint color.
final int trapezoidColor =
!mIsSlotsClickabled
? mTrapezoidColor
: mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL
? mTrapezoidSolidColor : mTrapezoidColor;
final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index
|| mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL)
? mTrapezoidSolidColor : mTrapezoidColor;
final boolean isHoverState =
mIsSlotsClickabled && mHoveredIndex == index && isValidToDraw(mHoveredIndex);
mIsSlotsClickabled && mHoveredIndex == index
&& isValidToDraw(mViewModel, mHoveredIndex);
mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor);
final float leftTop = round(trapezoidBottom - mLevels[index] * unitHeight);
final float rightTop = round(trapezoidBottom - mLevels[index + 1] * unitHeight);
final float leftTop = round(
trapezoidBottom - requireNonNull(mViewModel.levels().get(index)) * unitHeight);
final float rightTop = round(trapezoidBottom
- requireNonNull(mViewModel.levels().get(index + 1)) * unitHeight);
trapezoidPath.reset();
trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
@@ -579,15 +602,37 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
return index;
}
}
return SELECTED_INDEX_INVALID;
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
}
private boolean isValidToDraw(int trapezoidIndex) {
return mLevels != null
private void initializeAxisLabelsBounds() {
mAxisLabelsBounds.clear();
for (int i = 0; i < mViewModel.size(); i++) {
mAxisLabelsBounds.add(new Rect());
}
}
private static boolean isTrapezoidValid(
@NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel.levels().get(trapezoidIndex) != null
&& viewModel.levels().get(trapezoidIndex + 1) != null;
}
private static boolean isValidToDraw(BatteryChartViewModel viewModel, int trapezoidIndex) {
return viewModel != null
&& trapezoidIndex >= 0
&& trapezoidIndex < mLevels.length - 1
&& mLevels[trapezoidIndex] != 0
&& mLevels[trapezoidIndex + 1] != 0;
&& trapezoidIndex < viewModel.size() - 1
&& isTrapezoidValid(viewModel, trapezoidIndex);
}
private static boolean hasAnyValidTrapezoid(@NonNull BatteryChartViewModel viewModel) {
// Sets the chart is clickable if there is at least one valid item in it.
for (int trapezoidIndex = 0; trapezoidIndex < viewModel.size() - 1; trapezoidIndex++) {
if (isTrapezoidValid(viewModel, trapezoidIndex)) {
return true;
}
}
return false;
}
private static String[] getPercentages() {
@@ -621,7 +666,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
}
// A container class for each trapezoid left and right location.
private static final class TrapezoidSlot {
@VisibleForTesting
static final class TrapezoidSlot {
public float mLeft;
public float mRight;

View File

@@ -0,0 +1,109 @@
/*
* 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 androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/** The view model of {@code BatteryChartView} */
class BatteryChartViewModel {
private static final String TAG = "BatteryChartViewModel";
public static final int SELECTED_INDEX_ALL = -1;
public static final int SELECTED_INDEX_INVALID = -2;
// We need at least 2 levels to draw a trapezoid.
private static final int MIN_LEVELS_DATA_SIZE = 2;
enum AxisLabelPosition {
BETWEEN_TRAPEZOIDS,
CENTER_OF_TRAPEZOIDS,
}
private final List<Integer> mLevels;
private final List<String> mTexts;
private final AxisLabelPosition mAxisLabelPosition;
private int mSelectedIndex = SELECTED_INDEX_ALL;
BatteryChartViewModel(
@NonNull List<Integer> levels, @NonNull List<String> texts,
@NonNull AxisLabelPosition axisLabelPosition) {
Preconditions.checkArgument(
levels.size() == texts.size() && levels.size() >= MIN_LEVELS_DATA_SIZE,
String.format(Locale.ENGLISH,
"Invalid BatteryChartViewModel levels.size: %d, texts.size: %d.",
levels.size(), texts.size()));
mLevels = levels;
mTexts = texts;
mAxisLabelPosition = axisLabelPosition;
}
public int size() {
return mLevels.size();
}
public List<Integer> levels() {
return mLevels;
}
public List<String> texts() {
return mTexts;
}
public AxisLabelPosition axisLabelPosition() {
return mAxisLabelPosition;
}
public int selectedIndex() {
return mSelectedIndex;
}
public void setSelectedIndex(int index) {
mSelectedIndex = index;
}
@Override
public int hashCode() {
return Objects.hash(mLevels, mTexts, mSelectedIndex, mAxisLabelPosition);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (!(other instanceof BatteryChartViewModel)) {
return false;
}
final BatteryChartViewModel batteryChartViewModel = (BatteryChartViewModel) other;
return Objects.equals(mLevels, batteryChartViewModel.mLevels)
&& Objects.equals(mTexts, batteryChartViewModel.mTexts)
&& mAxisLabelPosition == batteryChartViewModel.mAxisLabelPosition
&& mSelectedIndex == batteryChartViewModel.mSelectedIndex;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH,
"levels: %s,\ntexts: %s,\naxisLabelPosition: %s, selectedIndex: %d",
Objects.toString(mLevels), Objects.toString(mTexts), mAxisLabelPosition,
mSelectedIndex);
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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 androidx.annotation.NonNull;
import java.util.Collections;
import java.util.List;
/** Wraps the battery usage diff data for each entry used for battery usage app list. */
public class BatteryDiffData {
private final List<BatteryDiffEntry> mAppEntries;
private final List<BatteryDiffEntry> mSystemEntries;
/** Constructor for the diff entries which already have totalConsumePower value. */
public BatteryDiffData(
@NonNull List<BatteryDiffEntry> appDiffEntries,
@NonNull List<BatteryDiffEntry> systemDiffEntries) {
mAppEntries = appDiffEntries;
mSystemEntries = systemDiffEntries;
sortEntries();
}
/** Constructor for the diff entries which have not set totalConsumePower value. */
public BatteryDiffData(
@NonNull List<BatteryDiffEntry> appDiffEntries,
@NonNull List<BatteryDiffEntry> systemDiffEntries,
final double totalConsumePower) {
mAppEntries = appDiffEntries;
mSystemEntries = systemDiffEntries;
setTotalConsumePowerForAllEntries(totalConsumePower);
sortEntries();
}
public List<BatteryDiffEntry> getAppDiffEntryList() {
return mAppEntries;
}
public List<BatteryDiffEntry> getSystemDiffEntryList() {
return mSystemEntries;
}
// Sets total consume power for each entry.
private void setTotalConsumePowerForAllEntries(final double totalConsumePower) {
mAppEntries.forEach(diffEntry -> diffEntry.setTotalConsumePower(totalConsumePower));
mSystemEntries.forEach(diffEntry -> diffEntry.setTotalConsumePower(totalConsumePower));
}
// Sorts entries based on consumed percentage.
private void sortEntries() {
Collections.sort(mAppEntries, BatteryDiffEntry.COMPARATOR);
Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
}
}

View File

@@ -43,6 +43,6 @@ public class BatteryHistoryLoader
public Map<Long, Map<String, BatteryHistEntry>> loadInBackground() {
final PowerUsageFeatureProvider powerUsageFeatureProvider =
FeatureFactory.getFactory(mContext).getPowerUsageFeatureProvider(mContext);
return powerUsageFeatureProvider.getBatteryHistory(mContext);
return powerUsageFeatureProvider.getBatteryHistorySinceLastFullCharge(mContext);
}
}

View File

@@ -49,7 +49,8 @@ public class BatteryHistoryPreference extends Preference {
private TextView mSummaryView;
private CharSequence mSummaryContent;
private BatteryChartView mBatteryChartView;
private BatteryChartView mDailyChartView;
private BatteryChartView mHourlyChartView;
private BatteryChartPreferenceController mChartPreferenceController;
public BatteryHistoryPreference(Context context, AttributeSet attrs) {
@@ -92,8 +93,8 @@ public class BatteryHistoryPreference extends Preference {
void setChartPreferenceController(BatteryChartPreferenceController controller) {
mChartPreferenceController = controller;
if (mBatteryChartView != null) {
mChartPreferenceController.setBatteryChartView(mBatteryChartView);
if (mDailyChartView != null && mHourlyChartView != null) {
mChartPreferenceController.setBatteryChartView(mDailyChartView, mHourlyChartView);
}
}
@@ -105,11 +106,14 @@ public class BatteryHistoryPreference extends Preference {
return;
}
if (mIsChartGraphEnabled) {
mBatteryChartView = (BatteryChartView) view.findViewById(R.id.battery_chart);
mBatteryChartView.setCompanionTextView(
mDailyChartView = (BatteryChartView) view.findViewById(R.id.daily_battery_chart);
mDailyChartView.setCompanionTextView(
(TextView) view.findViewById(R.id.companion_text));
mHourlyChartView = (BatteryChartView) view.findViewById(R.id.hourly_battery_chart);
mHourlyChartView.setCompanionTextView(
(TextView) view.findViewById(R.id.companion_text));
if (mChartPreferenceController != null) {
mChartPreferenceController.setBatteryChartView(mBatteryChartView);
mChartPreferenceController.setBatteryChartView(mDailyChartView, mHourlyChartView);
}
} else {
final TextView chargeView = (TextView) view.findViewById(R.id.charge);

View File

@@ -0,0 +1,98 @@
/*
* 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 androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/** Wraps the battery timestamp and level data used for battery usage chart. */
public final class BatteryLevelData {
/** A container for the battery timestamp and level data. */
public static final class PeriodBatteryLevelData {
// The length of mTimestamps and mLevels must be the same. mLevels[index] might be null when
// there is no level data for the corresponding timestamp.
private final List<Long> mTimestamps;
private final List<Integer> mLevels;
public PeriodBatteryLevelData(
@NonNull List<Long> timestamps, @NonNull List<Integer> levels) {
Preconditions.checkArgument(timestamps.size() == levels.size(),
/* errorMessage= */ "Timestamp: " + timestamps.size() + ", Level: "
+ levels.size());
mTimestamps = timestamps;
mLevels = levels;
}
public List<Long> getTimestamps() {
return mTimestamps;
}
public List<Integer> getLevels() {
return mLevels;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH, "timestamps: %s; levels: %s",
Objects.toString(mTimestamps), Objects.toString(mLevels));
}
}
/**
* There could be 2 cases for the daily battery levels:
* 1) length is 2: The usage data is within 1 day. Only contains start and end data, such as
* data of 2022-01-01 06:00 and 2022-01-01 16:00.
* 2) length > 2: The usage data is more than 1 days. The data should be the start, end and 0am
* data of every day between the start and end, such as data of 2022-01-01 06:00,
* 2022-01-02 00:00, 2022-01-03 00:00 and 2022-01-03 08:00.
*/
private final PeriodBatteryLevelData mDailyBatteryLevels;
// The size of hourly data must be the size of daily data - 1.
private final List<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
public BatteryLevelData(
@NonNull PeriodBatteryLevelData dailyBatteryLevels,
@NonNull List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) {
final long dailySize = dailyBatteryLevels.getTimestamps().size();
final long hourlySize = hourlyBatteryLevelsPerDay.size();
Preconditions.checkArgument(hourlySize == dailySize - 1,
/* errorMessage= */ "DailySize: " + dailySize + ", HourlySize: " + hourlySize);
mDailyBatteryLevels = dailyBatteryLevels;
mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
}
public PeriodBatteryLevelData getDailyBatteryLevels() {
return mDailyBatteryLevels;
}
public List<PeriodBatteryLevelData> getHourlyBatteryLevelsPerDay() {
return mHourlyBatteryLevelsPerDay;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH,
"dailyBatteryLevels: %s; hourlyBatteryLevelsPerDay: %s",
Objects.toString(mDailyBatteryLevels),
Objects.toString(mHourlyBatteryLevelsPerDay));
}
}

View File

@@ -140,7 +140,7 @@ public final class ConvertUtils {
/** Converts UTC timestamp to local time hour data. */
public static String utcToLocalTimeHour(
Context context, long timestamp, boolean is24HourFormat) {
final Context context, final long timestamp, final boolean is24HourFormat) {
final Locale locale = getLocale(context);
// e.g. for 12-hour format: 9 pm
// e.g. for 24-hour format: 09:00
@@ -149,6 +149,15 @@ public final class ConvertUtils {
return DateFormat.format(pattern, timestamp).toString().toLowerCase(locale);
}
/** Converts UTC timestamp to local time day of week data. */
public static String utcToLocalTimeDayOfWeek(
final Context context, final long timestamp, final boolean isAbbreviation) {
final Locale locale = getLocale(context);
final String pattern = DateFormat.getBestDateTimePattern(locale,
isAbbreviation ? "E" : "EEEE");
return DateFormat.format(pattern, timestamp).toString();
}
/** Gets indexed battery usage data for each corresponding time slot. */
public static Map<Integer, List<BatteryDiffEntry>> getIndexedUsageMap(
final Context context,
@@ -267,7 +276,7 @@ public final class ConvertUtils {
diffEntry.setTotalConsumePower(totalConsumePower);
}
}
insert24HoursData(BatteryChartView.SELECTED_INDEX_ALL, resultMap);
insert24HoursData(BatteryChartViewModel.SELECTED_INDEX_ALL, resultMap);
resolveMultiUsersData(context, resultMap);
if (purgeLowPercentageAndFakeData) {
purgeLowPercentageAndFakeData(context, resultMap);

File diff suppressed because it is too large Load Diff

View File

@@ -259,10 +259,7 @@ public class PowerUsageSummary extends PowerUsageBase implements
@VisibleForTesting
void initPreference() {
mBatteryUsagePreference = findPreference(KEY_BATTERY_USAGE);
mBatteryUsagePreference.setSummary(
mPowerFeatureProvider.isChartGraphEnabled(getContext())
? getString(R.string.advanced_battery_preference_summary_with_hours)
: getString(R.string.advanced_battery_preference_summary));
mBatteryUsagePreference.setSummary(getString(R.string.advanced_battery_preference_summary));
mHelpPreference = findPreference(KEY_BATTERY_ERROR);
mHelpPreference.setVisible(false);

View File

@@ -18,18 +18,36 @@ package com.android.settings.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.google.android.material.card.MaterialCardView;
import java.util.Optional;
/**
* Preference that wrapped by {@link MaterialCardView}, only support to set icon, title and summary
*/
public class CardPreference extends Preference {
private View.OnClickListener mPrimaryBtnClickListener = null;
private View.OnClickListener mSecondaryBtnClickListener = null;
private String mPrimaryButtonText = null;
private String mSecondaryButtonText = null;
private Optional<Button> mPrimaryButton = Optional.empty();
private Optional<Button> mSecondaryButton = Optional.empty();
private Optional<View> mButtonsGroup = Optional.empty();
private boolean mPrimaryButtonVisible = false;
private boolean mSecondaryButtonVisible = false;
public CardPreference(Context context) {
this(context, null /* attrs */);
}
@@ -37,4 +55,94 @@ public class CardPreference extends Preference {
public CardPreference(Context context, AttributeSet attrs) {
super(context, attrs, R.attr.cardPreferenceStyle);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
initButtonsAndLayout(holder);
}
private void initButtonsAndLayout(PreferenceViewHolder holder) {
mPrimaryButton = Optional.ofNullable((Button) holder.findViewById(android.R.id.button1));
mSecondaryButton = Optional.ofNullable((Button) holder.findViewById(android.R.id.button2));
mButtonsGroup = Optional.ofNullable(holder.findViewById(R.id.card_preference_buttons));
setPrimaryButtonText(mPrimaryButtonText);
setPrimaryButtonClickListener(mPrimaryBtnClickListener);
setPrimaryButtonVisible(mPrimaryButtonVisible);
setSecondaryButtonText(mSecondaryButtonText);
setSecondaryButtonClickListener(mSecondaryBtnClickListener);
setSecondaryButtonVisible(mSecondaryButtonVisible);
}
/**
* Register a callback to be invoked when the primary button is clicked.
*
* @param l the callback that will run
*/
public void setPrimaryButtonClickListener(View.OnClickListener l) {
mPrimaryButton.ifPresent(button -> button.setOnClickListener(l));
mPrimaryBtnClickListener = l;
}
/**
* Register a callback to be invoked when the secondary button is clicked.
*
* @param l the callback that will run
*/
public void setSecondaryButtonClickListener(View.OnClickListener l) {
mSecondaryButton.ifPresent(button -> button.setOnClickListener(l));
mSecondaryBtnClickListener = l;
}
/**
* Sets the text to be displayed on primary button.
*
* @param text text to be displayed
*/
public void setPrimaryButtonText(String text) {
mPrimaryButton.ifPresent(button -> button.setText(text));
mPrimaryButtonText = text;
}
/**
* Sets the text to be displayed on secondary button.
*
* @param text text to be displayed
*/
public void setSecondaryButtonText(String text) {
mSecondaryButton.ifPresent(button -> button.setText(text));
mSecondaryButtonText = text;
}
/**
* Set the visible on the primary button.
*
* @param visible {@code true} for visible
*/
public void setPrimaryButtonVisible(boolean visible) {
mPrimaryButton.ifPresent(
button -> button.setVisibility(visible ? View.VISIBLE : View.GONE));
mPrimaryButtonVisible = visible;
updateButtonGroupsVisibility();
}
/**
* Set the visible on the secondary button.
*
* @param visible {@code true} for visible
*/
public void setSecondaryButtonVisible(boolean visible) {
mSecondaryButton.ifPresent(
button -> button.setVisibility(visible ? View.VISIBLE : View.GONE));
mSecondaryButtonVisible = visible;
updateButtonGroupsVisibility();
}
private void updateButtonGroupsVisibility() {
int visibility =
(mPrimaryButtonVisible || mSecondaryButtonVisible) ? View.VISIBLE : View.GONE;
mButtonsGroup.ifPresent(group -> group.setVisibility(visibility));
}
}

View File

@@ -42,6 +42,7 @@ import android.text.InputType;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@@ -1495,13 +1496,20 @@ public class WifiConfigController2 implements TextWatcher,
}
// Shows display name of each active subscription.
final ArrayList<CharSequence> displayNames = new ArrayList<>();
ArrayMap<Integer, CharSequence> displayNames = new ArrayMap<>();
int defaultDataSubscriptionId = SubscriptionManager.getDefaultDataSubscriptionId();
for (SubscriptionInfo activeSubInfo : mActiveSubscriptionInfos) {
displayNames.add(
// If multiple SIMs have the same carrier id, only the first or default data SIM is
// displayed.
if (displayNames.containsKey(activeSubInfo.getCarrierId())
&& defaultDataSubscriptionId != activeSubInfo.getSubscriptionId()) {
continue;
}
displayNames.put(activeSubInfo.getCarrierId(),
SubscriptionUtil.getUniqueSubscriptionDisplayName(activeSubInfo, mContext));
}
mEapSimSpinner.setAdapter(
getSpinnerAdapter(displayNames.toArray(new String[displayNames.size()])));
getSpinnerAdapter(displayNames.values().toArray(new String[displayNames.size()])));
mEapSimSpinner.setSelection(0 /* position */);
if (displayNames.size() == 1) {
mEapSimSpinner.setEnabled(false);

View File

@@ -19,6 +19,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
import android.app.Activity;
import android.app.AlertDialog;
@@ -49,7 +50,6 @@ import android.os.Handler;
import android.provider.Telephony.CarrierId;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
@@ -711,27 +711,17 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
// Checks if the SIM subscription is active.
final List<SubscriptionInfo> activeSubscriptionInfos = mContext
.getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList();
final int defaultDataSubscriptionId = SubscriptionManager.getDefaultDataSubscriptionId();
if (activeSubscriptionInfos != null) {
for (SubscriptionInfo subscriptionInfo : activeSubscriptionInfos) {
final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
subscriptionInfo, mContext);
if (config.carrierId == subscriptionInfo.getCarrierId()) {
mEapSimSubscriptionPref.setSummary(displayName);
return;
}
// When it's UNKNOWN_CARRIER_ID, devices connects it with the SIM subscription of
// defaultDataSubscriptionId.
if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID
&& defaultDataSubscriptionId == subscriptionInfo.getSubscriptionId()) {
mEapSimSubscriptionPref.setSummary(displayName);
return;
}
SubscriptionInfo info = fineSubscriptionInfo(config.carrierId, activeSubscriptionInfos,
SubscriptionManager.getDefaultDataSubscriptionId());
if (info != null) {
mEapSimSubscriptionPref.setSummary(
SubscriptionUtil.getUniqueSubscriptionDisplayName(info, mContext));
return;
}
}
if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
if (config.carrierId == UNKNOWN_CARRIER_ID) {
mEapSimSubscriptionPref.setSummary(R.string.wifi_no_related_sim_card);
return;
}
@@ -750,6 +740,25 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
null /* orderBy */);
}
@VisibleForTesting
SubscriptionInfo fineSubscriptionInfo(int carrierId,
List<SubscriptionInfo> activeSubscriptionInfos, int defaultDataSubscriptionId) {
SubscriptionInfo firstMatchedInfo = null;
for (SubscriptionInfo info : activeSubscriptionInfos) {
// When it's UNKNOWN_CARRIER_ID or matched with configured CarrierId,
// devices connects it with the SIM subscription of defaultDataSubscriptionId.
if (defaultDataSubscriptionId == info.getSubscriptionId()
&& (carrierId == info.getCarrierId() || carrierId == UNKNOWN_CARRIER_ID)) {
return info;
}
if (firstMatchedInfo == null && carrierId == info.getCarrierId()) {
firstMatchedInfo = info;
}
}
return firstMatchedInfo;
}
private void refreshMacAddress() {
final String macAddress = mWifiEntry.getMacAddress();
if (TextUtils.isEmpty(macAddress)) {

View File

@@ -162,7 +162,8 @@ public class AppBatteryPreferenceControllerTest {
mController.updateBatteryWithDiffEntry();
assertThat(mBatteryPreference.getSummary()).isEqualTo("No battery use for past 24 hours");
assertThat(mBatteryPreference.getSummary().toString()).isEqualTo(
"No battery use since last full charge");
}
@Test
@@ -175,7 +176,8 @@ public class AppBatteryPreferenceControllerTest {
mController.updateBatteryWithDiffEntry();
assertThat(mBatteryPreference.getSummary()).isEqualTo("60% use for past 24 hours");
assertThat(mBatteryPreference.getSummary().toString()).isEqualTo(
"60% use since last full charge");
}
@Test

View File

@@ -232,7 +232,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testGetPreferenceScreenResId_returnNewLayout() {
public void setPreferenceScreenResId_returnNewLayout() {
assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.power_usage_detail);
}
@@ -252,7 +252,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_HasAppEntry_BuildByAppEntry() {
public void initHeader_HasAppEntry_BuildByAppEntry() {
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
new InstantAppDataProvider() {
@Override
@@ -269,7 +269,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_HasAppEntry_InstantApp() {
public void initHeader_HasAppEntry_InstantApp() {
ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
new InstantAppDataProvider() {
@Override
@@ -286,7 +286,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_noUsageTimeAndGraphDisabled_hasCorrectSummary() {
public void initHeader_noUsageTimeAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -304,7 +304,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_bgTwoMinFgZeroAndGraphDisabled_hasCorrectSummary() {
public void initHeader_bgTwoMinFgZeroAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -324,7 +324,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_bgLessThanAMinFgZeroAndGraphDisabled_hasCorrectSummary() {
public void initHeader_bgLessThanAMinFgZeroAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -345,7 +345,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_totalUsageLessThanAMinAndGraphDisabled_hasCorrectSummary() {
public void initHeader_totalUsageLessThanAMinAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -367,7 +367,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_TotalAMinutesBgLessThanAMinAndGraphDisabled_hasCorrectSummary() {
public void initHeader_TotalAMinutesBgLessThanAMinAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
@@ -387,7 +387,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_TotalAMinBackgroundZeroAndGraphDisabled_hasCorrectSummary() {
public void initHeader_TotalAMinBackgroundZeroAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
final long backgroundTimeZero = 0;
@@ -406,7 +406,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_fgTwoMinBgFourMinAndGraphDisabled_hasCorrectSummary() {
public void initHeader_fgTwoMinBgFourMinAndGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
.thenReturn(false);
final long backgroundTimeFourMinute = 240000;
@@ -424,7 +424,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_noUsageTime_hasCorrectSummary() {
public void initHeader_noUsageTime_hasCorrectSummary() {
Bundle bundle = new Bundle(2);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, /* value */ 0);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, /* value */ 0);
@@ -435,11 +435,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("No usage for past 24 hr");
.isEqualTo("No usage from last full charge");
}
@Test
public void testInitHeader_noUsageTimeButConsumedPower_hasEmptySummary() {
public void initHeader_noUsageTimeButConsumedPower_hasEmptySummary() {
Bundle bundle = new Bundle(3);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, /* value */ 0);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, /* value */ 0);
@@ -454,7 +454,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_backgroundTwoMinForegroundZero_hasCorrectSummary() {
public void initHeader_backgroundTwoMinForegroundZero_hasCorrectSummary() {
final long backgroundTimeTwoMinutes = 120000;
final long foregroundTimeZero = 0;
Bundle bundle = new Bundle(2);
@@ -467,11 +467,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("2 min background for past 24 hr");
.isEqualTo("2 min background from last full charge");
}
@Test
public void testInitHeader_backgroundLessThanAMinForegroundZero_hasCorrectSummary() {
public void initHeader_backgroundLessThanAMinForegroundZero_hasCorrectSummary() {
final long backgroundTimeLessThanAMinute = 59999;
final long foregroundTimeZero = 0;
Bundle bundle = new Bundle(2);
@@ -485,11 +485,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("Background less than a minute for past 24 hr");
.isEqualTo("Background less than a minute from last full charge");
}
@Test
public void testInitHeader_totalUsageLessThanAMin_hasCorrectSummary() {
public void initHeader_totalUsageLessThanAMin_hasCorrectSummary() {
final long backgroundTimeLessThanHalfMinute = 20000;
final long foregroundTimeLessThanHalfMinute = 20000;
Bundle bundle = new Bundle(2);
@@ -504,11 +504,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("Total less than a minute for past 24 hr");
.isEqualTo("Total less than a minute from last full charge");
}
@Test
public void testInitHeader_TotalAMinutesBackgroundLessThanAMin_hasCorrectSummary() {
public void initHeader_TotalAMinutesBackgroundLessThanAMin_hasCorrectSummary() {
final long backgroundTimeZero = 59999;
final long foregroundTimeTwoMinutes = 1;
Bundle bundle = new Bundle(2);
@@ -521,11 +521,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("1 min total • background less than a minute\nfor past 24 hr");
.isEqualTo("1 min total • background less than a minute\nfrom last full charge");
}
@Test
public void testInitHeader_TotalAMinBackgroundZero_hasCorrectSummary() {
public void initHeader_TotalAMinBackgroundZero_hasCorrectSummary() {
final long backgroundTimeZero = 0;
final long foregroundTimeAMinutes = 60000;
Bundle bundle = new Bundle(2);
@@ -538,11 +538,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("1 min total for past 24 hr");
.isEqualTo("1 min total from last full charge");
}
@Test
public void testInitHeader_foregroundTwoMinBackgroundFourMin_hasCorrectSummary() {
public void initHeader_foregroundTwoMinBackgroundFourMin_hasCorrectSummary() {
final long backgroundTimeFourMinute = 240000;
final long foregroundTimeTwoMinutes = 120000;
Bundle bundle = new Bundle(2);
@@ -555,11 +555,11 @@ public class AdvancedPowerUsageDetailTest {
ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
verify(mEntityHeaderController).setSummary(captor.capture());
assertThat(captor.getValue().toString())
.isEqualTo("6 min total • 4 min background\nfor past 24 hr");
.isEqualTo("6 min total • 4 min background\nfrom last full charge");
}
@Test
public void testInitHeader_totalUsageLessThanAMinWithSlotTime_hasCorrectSummary() {
public void initHeader_totalUsageLessThanAMinWithSlotTime_hasCorrectSummary() {
final long backgroundTimeLessThanHalfMinute = 20000;
final long foregroundTimeLessThanHalfMinute = 20000;
Bundle bundle = new Bundle(3);
@@ -579,7 +579,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_TotalAMinBackgroundLessThanAMinWithSlotTime_hasCorrectSummary() {
public void initHeader_TotalAMinBackgroundLessThanAMinWithSlotTime_hasCorrectSummary() {
final long backgroundTimeZero = 59999;
final long foregroundTimeTwoMinutes = 1;
Bundle bundle = new Bundle(3);
@@ -597,7 +597,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_TotalAMinBackgroundZeroWithSlotTime_hasCorrectSummary() {
public void initHeader_TotalAMinBackgroundZeroWithSlotTime_hasCorrectSummary() {
final long backgroundTimeZero = 0;
final long foregroundTimeAMinutes = 60000;
Bundle bundle = new Bundle(3);
@@ -615,7 +615,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_foregroundTwoMinBackgroundFourMinWithSlotTime_hasCorrectSummary() {
public void initHeader_foregroundTwoMinBackgroundFourMinWithSlotTime_hasCorrectSummary() {
final long backgroundTimeFourMinute = 240000;
final long foregroundTimeTwoMinutes = 120000;
Bundle bundle = new Bundle(3);
@@ -633,7 +633,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_systemUidWithChartIsDisabled_nullSummary() {
public void initHeader_systemUidWithChartIsDisabled_nullSummary() {
Bundle bundle = new Bundle(3);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, 240000);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, 120000);
@@ -650,7 +650,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitHeader_systemUidWithChartIsEnabled_notNullSummary() {
public void initHeader_systemUidWithChartIsEnabled_notNullSummary() {
Bundle bundle = new Bundle(3);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, 240000);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, 120000);
@@ -665,21 +665,21 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_hasBasicData() {
public void startBatteryDetailPage_hasBasicData() {
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
mBatteryEntry, USAGE_PERCENT, /*isValidToShowSummary=*/ true);
assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_UID)).isEqualTo(UID);
assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME))
.isEqualTo(BACKGROUND_TIME_MS);
.isEqualTo(BACKGROUND_TIME_MS);
assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME))
.isEqualTo(FOREGROUND_TIME_MS);
.isEqualTo(FOREGROUND_TIME_MS);
assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT))
.isEqualTo(USAGE_PERCENT);
.isEqualTo(USAGE_PERCENT);
}
@Test
public void testStartBatteryDetailPage_invalidToShowSummary_noFGBDData() {
public void startBatteryDetailPage_invalidToShowSummary_noFGBDData() {
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
mBatteryEntry, USAGE_PERCENT, /*isValidToShowSummary=*/ false);
@@ -693,7 +693,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_NormalApp() {
public void startBatteryDetailPage_NormalApp() {
when(mBatteryEntry.getDefaultPackageName()).thenReturn(PACKAGE_NAME[0]);
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
@@ -704,7 +704,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_SystemApp() {
public void startBatteryDetailPage_SystemApp() {
when(mBatteryEntry.getDefaultPackageName()).thenReturn(null);
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
@@ -716,7 +716,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_WorkApp() {
public void startBatteryDetailPage_WorkApp() {
final int appUid = 1010019;
doReturn(appUid).when(mBatteryEntry).getUid();
@@ -727,7 +727,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_typeUser_startByCurrentUser() {
public void startBatteryDetailPage_typeUser_startByCurrentUser() {
when(mBatteryEntry.isUserEntry()).thenReturn(true);
final int currentUser = 20;
@@ -739,7 +739,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testStartBatteryDetailPage_noBatteryUsage_hasBasicData() {
public void startBatteryDetailPage_noBatteryUsage_hasBasicData() {
final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment, PACKAGE_NAME[0]);
@@ -747,16 +747,16 @@ public class AdvancedPowerUsageDetailTest {
verify(mActivity).startActivity(captor.capture());
assertThat(captor.getValue().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS)
.getString(AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME))
.isEqualTo(PACKAGE_NAME[0]);
.getString(AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME))
.isEqualTo(PACKAGE_NAME[0]);
assertThat(captor.getValue().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS)
.getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT))
.isEqualTo("0%");
.getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT))
.isEqualTo("0%");
}
@Test
public void testStartBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName() throws
public void startBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName() throws
PackageManager.NameNotFoundException {
doReturn(UID).when(mPackageManager).getPackageUid(PACKAGE_NAME[0], 0 /* no flag */);
@@ -796,7 +796,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitPreferenceForTriState_isSystemOrDefaultApp_hasCorrectString() {
public void initPreferenceForTriState_isSystemOrDefaultApp_hasCorrectString() {
when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true);
when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true);
@@ -807,7 +807,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitPreferenceForTriState_hasCorrectString() {
public void initPreferenceForTriState_hasCorrectString() {
when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true);
when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(false);
@@ -818,7 +818,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testOnRadioButtonClicked_clickOptimizePref_optimizePreferenceChecked() {
public void onRadioButtonClicked_clickOptimizePref_optimizePreferenceChecked() {
mOptimizePreference.setKey(KEY_PREF_OPTIMIZED);
mRestrictedPreference.setKey(KEY_PREF_RESTRICTED);
mUnrestrictedPreference.setKey(KEY_PREF_UNRESTRICTED);
@@ -830,7 +830,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testOnPause_optimizationModeChanged_logPreference() {
public void onPause_optimizationModeChanged_logPreference() {
final int mode = BatteryOptimizeUtils.MODE_RESTRICTED;
mFragment.mOptimizationMode = mode;
when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode);
@@ -849,7 +849,7 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testOnPause_optimizationModeIsNotChanged_notInvokeLogging() {
public void onPause_optimizationModeIsNotChanged_notInvokeLogging() {
final int mode = BatteryOptimizeUtils.MODE_OPTIMIZED;
mFragment.mOptimizationMode = mode;
when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode);

View File

@@ -41,7 +41,8 @@ public class BatteryDefenderDetectorTest {
mBatteryInfo.discharging = false;
mBatteryDefenderDetector = new BatteryDefenderDetector(mBatteryInfo);
mBatteryDefenderDetector = new BatteryDefenderDetector(
mBatteryInfo, /* extraDefend= */ false);
}
@Test

View File

@@ -17,13 +17,25 @@ package com.android.settings.fuelgauge.batterytip.tips;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.util.Log;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.widget.CardPreference;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
@@ -33,6 +45,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowLog;
@RunWith(RobolectricTestRunner.class)
public class BatteryDefenderTipTest {
@@ -43,6 +56,8 @@ public class BatteryDefenderTipTest {
private MetricsFeatureProvider mMetricsFeatureProvider;
@Mock private BatteryTip mBatteryTip;
@Mock private Preference mPreference;
@Mock private CardPreference mCardPreference;
@Before
public void setUp() {
@@ -52,6 +67,9 @@ public class BatteryDefenderTipTest {
mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider;
mContext = RuntimeEnvironment.application;
mBatteryDefenderTip = new BatteryDefenderTip(BatteryTip.StateType.NEW);
when(mPreference.getContext()).thenReturn(mContext);
when(mCardPreference.getContext()).thenReturn(mContext);
}
@Test
@@ -61,11 +79,19 @@ public class BatteryDefenderTipTest {
}
@Test
public void getSummary_showSummary() {
public void getSummary_notExtraDefended_showNonExtraDefendedSummary() {
assertThat(mBatteryDefenderTip.getSummary(mContext))
.isEqualTo(mContext.getString(R.string.battery_tip_limited_temporarily_summary));
}
@Test
public void getSummary_extraDefended_showExtraDefendedSummary() {
BatteryDefenderTip defenderTip = new BatteryDefenderTip(
BatteryTip.StateType.NEW, /* extraDefended= */ true);
assertThat(defenderTip.getSummary(mContext).toString()).isEqualTo("12%");
}
@Test
public void getIcon_showIcon() {
assertThat(mBatteryDefenderTip.getIconId())
@@ -80,4 +106,94 @@ public class BatteryDefenderTipTest {
verify(mMetricsFeatureProvider).action(mContext,
SettingsEnums.ACTION_BATTERY_DEFENDER_TIP, mBatteryTip.mState);
}
@Test
public void updatePreference_castFail_logErrorMessage() {
mBatteryDefenderTip.updatePreference(mPreference);
assertThat(getLastErrorLog()).isEqualTo("cast Preference to CardPreference failed");
}
@Test
public void updatePreference_shouldSetPrimaryButtonText() {
String expectedText = mContext.getString(R.string.battery_tip_charge_to_full_button);
mBatteryDefenderTip.updatePreference(mCardPreference);
verify(mCardPreference).setPrimaryButtonText(expectedText);
}
@Test
public void updatePreference_shouldSetSecondaryButtonText() {
String expected = mContext.getString(R.string.see_more);
mBatteryDefenderTip.updatePreference(mCardPreference);
verify(mCardPreference).setSecondaryButtonText(expected);
}
@Test
public void updatePreference_shouldSetSecondaryButtonVisible() {
mBatteryDefenderTip.updatePreference(mCardPreference);
verify(mCardPreference).setSecondaryButtonVisible(true);
}
@Test
public void updatePreference_whenCharging_setPrimaryButtonVisibleToBeTrue() {
fakeDeviceIsCharging(true);
mBatteryDefenderTip.updatePreference(mCardPreference);
verify(mCardPreference).setPrimaryButtonVisible(true);
}
@Test
public void updatePreference_whenNotCharging_setPrimaryButtonVisibleToBeFalse() {
fakeDeviceIsCharging(false);
mBatteryDefenderTip.updatePreference(mCardPreference);
verify(mCardPreference).setPrimaryButtonVisible(false);
}
@Test
public void updatePreference_whenGetChargingStatusFailed_setPrimaryButtonVisibleToBeFalse() {
fakeGetChargingStatusFailed();
mBatteryDefenderTip.updatePreference(mCardPreference);
verify(mCardPreference).setPrimaryButtonVisible(false);
}
private void fakeDeviceIsCharging(boolean charging) {
int charged = charging ? 1 : 0; // 1 means charging, 0:not charging
Intent batteryChangedIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
batteryChangedIntent.putExtra(BatteryManager.EXTRA_PLUGGED, charged);
Context mockContext = mock(Context.class);
when(mockContext.getString(anyInt())).thenReturn("fake_string");
when(mCardPreference.getContext()).thenReturn(mockContext);
when(mockContext.registerReceiver(eq(null), any(IntentFilter.class)))
.thenReturn(batteryChangedIntent);
}
private void fakeGetChargingStatusFailed() {
Context mockContext = mock(Context.class);
when(mockContext.getString(anyInt())).thenReturn("fake_string");
when(mCardPreference.getContext()).thenReturn(mockContext);
when(mockContext.registerReceiver(eq(null), any(IntentFilter.class))).thenReturn(null);
}
private String getLastErrorLog() {
return ShadowLog.getLogsForTag(BatteryDefenderTip.class.getSimpleName()).stream()
.filter(log -> log.type == Log.ERROR)
.reduce((first, second) -> second)
.orElse(createErrorLog("No Error Log"))
.msg;
}
private ShadowLog.LogItem createErrorLog(String msg) {
return new ShadowLog.LogItem(Log.ERROR, "tag", msg, null);
}
}

View File

@@ -18,23 +18,24 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.any;
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.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.LocaleList;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.LinearLayout;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -58,15 +59,15 @@ import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
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";
private static final int DESIRED_HISTORY_SIZE =
BatteryChartPreferenceController.DESIRED_HISTORY_SIZE;
@Mock
private InstrumentedPreferenceFragment mFragment;
@@ -79,11 +80,15 @@ public final class BatteryChartPreferenceControllerTest {
@Mock
private BatteryHistEntry mBatteryHistEntry;
@Mock
private BatteryChartView mBatteryChartView;
private BatteryChartView mDailyChartView;
@Mock
private BatteryChartView mHourlyChartView;
@Mock
private PowerGaugePreference mPowerGaugePreference;
@Mock
private BatteryUtils mBatteryUtils;
@Mock
private LinearLayout.LayoutParams mLayoutParams;
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
@@ -96,6 +101,7 @@ public final class BatteryChartPreferenceControllerTest {
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);
@@ -108,10 +114,12 @@ public final class BatteryChartPreferenceControllerTest {
doReturn(new String[]{"com.android.gms.persistent"})
.when(mFeatureFactory.powerUsageFeatureProvider)
.getHideApplicationEntries(mContext);
doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mPrefContext = mContext;
mBatteryChartPreferenceController.mAppListPrefGroup = mAppListGroup;
mBatteryChartPreferenceController.mBatteryChartView = mBatteryChartView;
mBatteryChartPreferenceController.mDailyChartView = mDailyChartView;
mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView;
mBatteryDiffEntry = new BatteryDiffEntry(
mContext,
/*foregroundUsageTimeInMs=*/ 1,
@@ -123,12 +131,10 @@ public final class BatteryChartPreferenceControllerTest {
BatteryDiffEntry.sResourceCache.put(
"fakeBatteryDiffEntryKey",
new BatteryEntry.NameAndIcon("fakeName", /*icon=*/ null, /*iconId=*/ 1));
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
}
@Test
public void testOnDestroy_activityIsChanging_clearBatteryEntryCache() {
public void onDestroy_activityIsChanging_clearBatteryEntryCache() {
doReturn(true).when(mSettingsActivity).isChangingConfigurations();
// Ensures the testing environment is correct.
assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
@@ -138,7 +144,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnDestroy_activityIsNotChanging_notClearBatteryEntryCache() {
public void onDestroy_activityIsNotChanging_notClearBatteryEntryCache() {
doReturn(false).when(mSettingsActivity).isChangingConfigurations();
// Ensures the testing environment is correct.
assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
@@ -148,7 +154,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnDestroy_clearPreferenceCache() {
public void onDestroy_clearPreferenceCache() {
// Ensures the testing environment is correct.
mBatteryChartPreferenceController.mPreferenceCache.put(
PREF_KEY, mPowerGaugePreference);
@@ -160,113 +166,135 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnDestroy_removeAllPreferenceFromPreferenceGroup() {
public void onDestroy_removeAllPreferenceFromPreferenceGroup() {
mBatteryChartPreferenceController.onDestroy();
verify(mAppListGroup).removeAll();
}
@Test
public void testSetBatteryHistoryMap_createExpectedKeysAndLevels() {
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
public void setBatteryChartViewModel_6Hours() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
// Verifies the created battery keys array.
for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
assertThat(mBatteryChartPreferenceController.mBatteryHistoryKeys[index])
// These values is are calculated by hand from createBatteryHistoryMap().
.isEqualTo(index + 1);
}
// Verifies the created battery levels array.
for (int index = 0; index < 13; index++) {
assertThat(mBatteryChartPreferenceController.mBatteryHistoryLevels[index])
// These values is are calculated by hand from createBatteryHistoryMap().
.isEqualTo(100 - index * 2);
}
assertThat(mBatteryChartPreferenceController.mBatteryIndexedMap).hasSize(13);
verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE);
verify(mHourlyChartView, atLeastOnce()).setVisibility(View.VISIBLE);
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 97, 95),
List.of("8 am", "10 am", "12 pm"),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
@Test
public void testSetBatteryHistoryMap_largeSize_createExpectedKeysAndLevels() {
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
public void setBatteryChartViewModel_60Hours() {
BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel(
List.of(100, 83, 59, 41),
List.of("Sat", "Sun", "Mon", "Mon"),
BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
// Verifies the created battery keys array.
for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
assertThat(mBatteryChartPreferenceController.mBatteryHistoryKeys[index])
// These values is are calculated by hand from createBatteryHistoryMap().
.isEqualTo(index + 1);
}
// Verifies the created battery levels array.
for (int index = 0; index < 13; index++) {
assertThat(mBatteryChartPreferenceController.mBatteryHistoryLevels[index])
// These values is are calculated by hand from createBatteryHistoryMap().
.isEqualTo(100 - index * 2);
}
assertThat(mBatteryChartPreferenceController.mBatteryIndexedMap).hasSize(13);
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE);
verify(mHourlyChartView, atLeastOnce()).setVisibility(View.GONE);
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
reset(mDailyChartView);
reset(mHourlyChartView);
doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
mBatteryChartPreferenceController.mDailyChartIndex = 0;
mBatteryChartPreferenceController.refreshUi();
verify(mDailyChartView).setVisibility(View.VISIBLE);
verify(mHourlyChartView).setVisibility(View.VISIBLE);
expectedDailyViewModel.setSelectedIndex(0);
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(100, 97, 95, 93, 91, 89, 87, 85, 83),
List.of("8 am", "10 am", "12 pm", "2 pm", "4 pm", "6 pm", "8 pm", "10 pm",
"12 am"),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
reset(mDailyChartView);
reset(mHourlyChartView);
doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
mBatteryChartPreferenceController.mDailyChartIndex = 1;
mBatteryChartPreferenceController.mHourlyChartIndex = 6;
mBatteryChartPreferenceController.refreshUi();
verify(mDailyChartView).setVisibility(View.VISIBLE);
verify(mHourlyChartView).setVisibility(View.VISIBLE);
expectedDailyViewModel.setSelectedIndex(1);
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
BatteryChartViewModel expectedHourlyViewModel = new BatteryChartViewModel(
List.of(83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59),
List.of("12 am", "2 am", "4 am", "6 am", "8 am", "10 am", "12 pm", "2 pm",
"4 pm", "6 pm", "8 pm", "10 pm", "12 am"),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS);
expectedHourlyViewModel.setSelectedIndex(6);
verify(mHourlyChartView).setViewModel(expectedHourlyViewModel);
reset(mDailyChartView);
reset(mHourlyChartView);
doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
mBatteryChartPreferenceController.mDailyChartIndex = 2;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.refreshUi();
verify(mDailyChartView).setVisibility(View.VISIBLE);
verify(mHourlyChartView).setVisibility(View.VISIBLE);
expectedDailyViewModel.setSelectedIndex(2);
verify(mDailyChartView).setViewModel(expectedDailyViewModel);
verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
List.of(59, 57, 55, 53, 51, 49, 47, 45, 43, 41),
List.of("12 am", "2 am", "4 am", "6 am", "8 am", "10 am", "12 pm", "2 pm",
"4 pm", "6 pm"),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
}
@Test
public void testRefreshUi_batteryIndexedMapIsNull_ignoreRefresh() {
public void refreshUi_normalCase_returnTrue() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue();
}
@Test
public void refreshUi_batteryIndexedMapIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.setBatteryHistoryMap(null);
assertThat(mBatteryChartPreferenceController.refreshUi(
/*trapezoidIndex=*/ 1, /*isForce=*/ false)).isFalse();
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void testRefreshUi_batteryChartViewIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.mBatteryChartView = null;
assertThat(mBatteryChartPreferenceController.refreshUi(
/*trapezoidIndex=*/ 1, /*isForce=*/ false)).isFalse();
public void refreshUi_dailyChartViewIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.mDailyChartView = null;
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void testRefreshUi_trapezoidIndexIsNotChanged_ignoreRefresh() {
final int trapezoidIndex = 1;
mBatteryChartPreferenceController.mTrapezoidIndex = trapezoidIndex;
assertThat(mBatteryChartPreferenceController.refreshUi(
trapezoidIndex, /*isForce=*/ false)).isFalse();
public void refreshUi_hourlyChartViewIsNull_ignoreRefresh() {
mBatteryChartPreferenceController.mHourlyChartView = null;
assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
}
@Test
public void testRefreshUi_forceUpdate_refreshUi() {
final int trapezoidIndex = 1;
mBatteryChartPreferenceController.mTrapezoidIndex = trapezoidIndex;
assertThat(mBatteryChartPreferenceController.refreshUi(
trapezoidIndex, /*isForce=*/ true)).isTrue();
}
@Test
public void testForceRefreshUi_updateTrapezoidIndexIntoSelectAll() {
mBatteryChartPreferenceController.mTrapezoidIndex =
BatteryChartView.SELECTED_INDEX_INVALID;
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
assertThat(mBatteryChartPreferenceController.mTrapezoidIndex)
.isEqualTo(BatteryChartView.SELECTED_INDEX_ALL);
}
@Test
public void testRemoveAndCacheAllPrefs_emptyContent_ignoreRemoveAll() {
final int trapezoidIndex = 1;
public void removeAndCacheAllPrefs_emptyContent_ignoreRemoveAll() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
doReturn(0).when(mAppListGroup).getPreferenceCount();
mBatteryChartPreferenceController.refreshUi(
trapezoidIndex, /*isForce=*/ true);
mBatteryChartPreferenceController.refreshUi();
verify(mAppListGroup, never()).removeAll();
}
@Test
public void testRemoveAndCacheAllPrefs_buildCacheAndRemoveAllPreference() {
final int trapezoidIndex = 1;
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(
trapezoidIndex, /*isForce=*/ true);
mBatteryChartPreferenceController.refreshUi();
assertThat(mBatteryChartPreferenceController.mPreferenceCache.get(PREF_KEY))
.isEqualTo(mPowerGaugePreference);
@@ -274,14 +302,14 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testAddPreferenceToScreen_emptyContent_ignoreAddPreference() {
public void addPreferenceToScreen_emptyContent_ignoreAddPreference() {
mBatteryChartPreferenceController.addPreferenceToScreen(
new ArrayList<BatteryDiffEntry>());
verify(mAppListGroup, never()).addPreference(any());
}
@Test
public void testAddPreferenceToScreen_addPreferenceIntoScreen() {
public void addPreferenceToScreen_addPreferenceIntoScreen() {
final String appLabel = "fake app label";
doReturn(1).when(mAppListGroup).getPreferenceCount();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
@@ -310,7 +338,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testAddPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() {
public void addPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() {
final String appLabel = "fake app label";
doReturn(1).when(mAppListGroup).getPreferenceCount();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
@@ -325,7 +353,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testHandlePreferenceTreeiClick_notPowerGaugePreference_returnFalse() {
public void handlePreferenceTreeClick_notPowerGaugePreference_returnFalse() {
assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick(mAppListGroup))
.isFalse();
@@ -336,7 +364,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testHandlePreferenceTreeClick_forAppEntry_returnTrue() {
public void handlePreferenceTreeClick_forAppEntry_returnTrue() {
doReturn(false).when(mBatteryHistEntry).isAppEntry();
doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
@@ -352,7 +380,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testHandlePreferenceTreeClick_forSystemEntry_returnTrue() {
public void handlePreferenceTreeClick_forSystemEntry_returnTrue() {
mBatteryChartPreferenceController.mBatteryUtils = mBatteryUtils;
doReturn(true).when(mBatteryHistEntry).isAppEntry();
doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
@@ -369,7 +397,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() {
public void setPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -381,7 +409,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setBackgroundUsageTimeOnly() {
public void setPreferenceSummary_setBackgroundUsageTimeOnly() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -393,7 +421,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setTotalUsageTimeLessThanAMinute() {
public void setPreferenceSummary_setTotalUsageTimeLessThanAMinute() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -405,7 +433,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() {
public void setPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -418,7 +446,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_setTotalAndBackgroundUsageTime() {
public void setPreferenceSummary_setTotalAndBackgroundUsageTime() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -430,7 +458,7 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testSetPreferenceSummary_notAllowShownPackage_setSummayAsNull() {
public void setPreferenceSummary_notAllowShownPackage_setSummayAsNull() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
final BatteryDiffEntry batteryDiffEntry =
@@ -445,36 +473,9 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testValidateUsageTime_returnTrueIfBatteryDiffEntryIsValid() {
assertThat(BatteryChartPreferenceController.validateUsageTime(
createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS,
/*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS)))
.isTrue();
}
@Test
public void testValidateUsageTime_foregroundTimeExceedThreshold_returnFalse() {
assertThat(BatteryChartPreferenceController.validateUsageTime(
createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ DateUtils.HOUR_IN_MILLIS * 3,
/*backgroundUsageTimeInMs=*/ 0)))
.isFalse();
}
@Test
public void testValidateUsageTime_backgroundTimeExceedThreshold_returnFalse() {
assertThat(BatteryChartPreferenceController.validateUsageTime(
createBatteryDiffEntry(
/*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ DateUtils.HOUR_IN_MILLIS * 3)))
.isFalse();
}
@Test
public void testOnExpand_expandedIsTrue_addSystemEntriesToPreferenceGroup() {
public void onExpand_expandedIsTrue_addSystemEntriesToPreferenceGroup() {
doReturn(1).when(mAppListGroup).getPreferenceCount();
mBatteryChartPreferenceController.mSystemEntries.add(mBatteryDiffEntry);
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
doReturn("label").when(mBatteryDiffEntry).getAppLabel();
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
@@ -493,10 +494,10 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnExpand_expandedIsFalse_removeSystemEntriesFromPreferenceGroup() {
public void onExpand_expandedIsFalse_removeSystemEntriesFromPreferenceGroup() {
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY);
mBatteryChartPreferenceController.mSystemEntries.add(mBatteryDiffEntry);
mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
// Verifies the cache is empty first.
assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty();
@@ -513,57 +514,17 @@ public final class BatteryChartPreferenceControllerTest {
}
@Test
public void testOnSelect_selectSpecificTimeSlot_logMetric() {
mBatteryChartPreferenceController.onSelect(1 /*slot index*/);
verify(mMetricsFeatureProvider)
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
}
@Test
public void testOnSelect_selectAll_logMetric() {
mBatteryChartPreferenceController.onSelect(
BatteryChartView.SELECTED_INDEX_ALL /*slot index*/);
verify(mMetricsFeatureProvider)
.action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL);
}
@Test
public void testRefreshCategoryTitle_setHourIntoBothTitleTextView() {
mBatteryChartPreferenceController = createController();
setUpBatteryHistoryKeys();
mBatteryChartPreferenceController.mAppListPrefGroup =
spy(new PreferenceCategory(mContext));
mBatteryChartPreferenceController.mExpandDividerPreference =
spy(new ExpandDividerPreference(mContext));
// Simulates select the first slot.
mBatteryChartPreferenceController.mTrapezoidIndex = 0;
mBatteryChartPreferenceController.refreshCategoryTitle();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
// Verifies the title in the preference group.
verify(mBatteryChartPreferenceController.mAppListPrefGroup)
.setTitle(captor.capture());
assertThat(captor.getValue()).isNotEqualTo("App usage for past 24 hr");
// Verifies the title in the expandable divider.
captor = ArgumentCaptor.forClass(String.class);
verify(mBatteryChartPreferenceController.mExpandDividerPreference)
.setTitle(captor.capture());
assertThat(captor.getValue()).isNotEqualTo("System usage for past 24 hr");
}
@Test
public void testRefreshCategoryTitle_setLast24HrIntoBothTitleTextView() {
public void refreshCategoryTitle_setLastFullChargeIntoBothTitleTextView() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mAppListPrefGroup =
spy(new PreferenceCategory(mContext));
mBatteryChartPreferenceController.mExpandDividerPreference =
spy(new ExpandDividerPreference(mContext));
// Simulates select all condition.
mBatteryChartPreferenceController.mTrapezoidIndex =
BatteryChartView.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.mDailyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.refreshCategoryTitle();
@@ -572,76 +533,93 @@ public final class BatteryChartPreferenceControllerTest {
verify(mBatteryChartPreferenceController.mAppListPrefGroup)
.setTitle(captor.capture());
assertThat(captor.getValue())
.isEqualTo("App usage for past 24 hr");
.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 for past 24 hr");
.isEqualTo("System usage since last full charge");
}
@Test
public void testSetTimestampLabel_nullBatteryHistoryKeys_ignore() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mBatteryHistoryKeys = null;
mBatteryChartPreferenceController.mBatteryChartView =
spy(new BatteryChartView(mContext));
mBatteryChartPreferenceController.setTimestampLabel();
public void selectedSlotText_selectAllDaysAllHours_returnNull() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
mBatteryChartPreferenceController.mDailyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
verify(mBatteryChartPreferenceController.mBatteryChartView, never())
.setLatestTimestamp(anyLong());
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
}
@Test
public void testSetTimestampLabel_setExpectedTimestampData() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mBatteryChartView =
spy(new BatteryChartView(mContext));
setUpBatteryHistoryKeys();
public void selectedSlotText_onlyOneDayDataSelectAllHours_returnNull() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mDailyChartIndex = 0;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.setTimestampLabel();
verify(mBatteryChartPreferenceController.mBatteryChartView)
.setLatestTimestamp(1619247636826L);
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
}
@Test
public void testSetTimestampLabel_withoutValidTimestamp_setExpectedTimestampData() {
mBatteryChartPreferenceController = createController();
mBatteryChartPreferenceController.mBatteryChartView =
spy(new BatteryChartView(mContext));
mBatteryChartPreferenceController.mBatteryHistoryKeys = new long[]{0L};
public void selectedSlotText_selectADayAllHours_onlyDayText() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
mBatteryChartPreferenceController.mDailyChartIndex = 1;
mBatteryChartPreferenceController.mHourlyChartIndex =
BatteryChartViewModel.SELECTED_INDEX_ALL;
mBatteryChartPreferenceController.setTimestampLabel();
verify(mBatteryChartPreferenceController.mBatteryChartView)
.setLatestTimestamp(anyLong());
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo("Sunday");
}
@Test
public void testOnSaveInstanceState_restoreSelectedIndexAndExpandState() {
final int expectedIndex = 1;
public void selectedSlotText_onlyOneDayDataSelectAnHour_onlyHourText() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
mBatteryChartPreferenceController.mDailyChartIndex = 0;
mBatteryChartPreferenceController.mHourlyChartIndex = 1;
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
"10 am - 12 pm");
}
@Test
public void selectedSlotText_SelectADayAnHour_dayAndHourText() {
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
mBatteryChartPreferenceController.mDailyChartIndex = 1;
mBatteryChartPreferenceController.mHourlyChartIndex = 8;
assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
"Sunday 4 pm - 6 pm");
}
@Test
public void onSaveInstanceState_restoreSelectedIndexAndExpandState() {
final int expectedDailyIndex = 1;
final int expectedHourlyIndex = 2;
final boolean isExpanded = true;
final Bundle bundle = new Bundle();
mBatteryChartPreferenceController.mTrapezoidIndex = expectedIndex;
mBatteryChartPreferenceController.mDailyChartIndex = expectedDailyIndex;
mBatteryChartPreferenceController.mHourlyChartIndex = expectedHourlyIndex;
mBatteryChartPreferenceController.mIsExpanded = isExpanded;
mBatteryChartPreferenceController.onSaveInstanceState(bundle);
// Replaces the original controller with other values.
mBatteryChartPreferenceController.mTrapezoidIndex = -1;
mBatteryChartPreferenceController.mDailyChartIndex = -1;
mBatteryChartPreferenceController.mHourlyChartIndex = -1;
mBatteryChartPreferenceController.mIsExpanded = false;
mBatteryChartPreferenceController.onCreate(bundle);
mBatteryChartPreferenceController.setBatteryHistoryMap(
createBatteryHistoryMap());
mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(25));
assertThat(mBatteryChartPreferenceController.mTrapezoidIndex)
.isEqualTo(expectedIndex);
assertThat(mBatteryChartPreferenceController.mDailyChartIndex)
.isEqualTo(expectedDailyIndex);
assertThat(mBatteryChartPreferenceController.mHourlyChartIndex)
.isEqualTo(expectedHourlyIndex);
assertThat(mBatteryChartPreferenceController.mIsExpanded).isTrue();
}
@Test
public void testIsValidToShowSummary_returnExpectedResult() {
public void isValidToShowSummary_returnExpectedResult() {
assertThat(mBatteryChartPreferenceController
.isValidToShowSummary("com.google.android.apps.scone"))
.isTrue();
@@ -652,31 +630,42 @@ public final class BatteryChartPreferenceControllerTest {
.isFalse();
}
@Test
public void testIsValidToShowEntry_returnExpectedResult() {
assertThat(mBatteryChartPreferenceController
.isValidToShowEntry("com.google.android.apps.scone"))
.isTrue();
// Verifies the items which are defined in the array list.
assertThat(mBatteryChartPreferenceController
.isValidToShowEntry("com.android.gms.persistent"))
.isFalse();
private static Long generateTimestamp(int index) {
// "2021-04-23 07:00:00 UTC" + index hours
return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS;
}
private static Map<Long, Map<String, BatteryHistEntry>> createBatteryHistoryMap() {
private static Map<Long, Map<String, BatteryHistEntry>> createBatteryHistoryMap(
int numOfHours) {
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
for (int index = 0; index < numOfHours; index++) {
final ContentValues values = new ContentValues();
values.put("batteryLevel", Integer.valueOf(100 - index));
values.put("consumePower", Integer.valueOf(100 - index));
final BatteryHistEntry entry = new BatteryHistEntry(values);
final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
entryMap.put("fake_entry_key" + index, entry);
batteryHistoryMap.put(Long.valueOf(index + 1), entryMap);
batteryHistoryMap.put(generateTimestamp(index), entryMap);
}
return batteryHistoryMap;
}
private Map<Integer, Map<Integer, BatteryDiffData>> 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(
@@ -684,13 +673,6 @@ public final class BatteryChartPreferenceControllerTest {
/*consumePower=*/ 0, mBatteryHistEntry);
}
private void setUpBatteryHistoryKeys() {
mBatteryChartPreferenceController.mBatteryHistoryKeys =
new long[]{1619196786769L, 0L, 1619247636826L};
ConvertUtils.utcToLocalTimeHour(
mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
}
private BatteryChartPreferenceController createController() {
final BatteryChartPreferenceController controller =
new BatteryChartPreferenceController(

View File

@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.os.LocaleList;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
@@ -41,6 +42,7 @@ import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@RunWith(RobolectricTestRunner.class)
@@ -55,6 +57,8 @@ public final class BatteryChartViewTest {
private AccessibilityServiceInfo mMockAccessibilityServiceInfo;
@Mock
private AccessibilityManager mMockAccessibilityManager;
@Mock
private View mMockView;
@Before
public void setUp() {
@@ -74,13 +78,13 @@ public final class BatteryChartViewTest {
}
@Test
public void testIsAccessibilityEnabled_disable_returnFalse() {
public void isAccessibilityEnabled_disable_returnFalse() {
doReturn(false).when(mMockAccessibilityManager).isEnabled();
assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse();
}
@Test
public void testIsAccessibilityEnabled_emptyInfo_returnFalse() {
public void isAccessibilityEnabled_emptyInfo_returnFalse() {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
doReturn(new ArrayList<AccessibilityServiceInfo>())
.when(mMockAccessibilityManager)
@@ -90,68 +94,70 @@ public final class BatteryChartViewTest {
}
@Test
public void testIsAccessibilityEnabled_validServiceId_returnTrue() {
public void isAccessibilityEnabled_validServiceId_returnTrue() {
doReturn(true).when(mMockAccessibilityManager).isEnabled();
assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isTrue();
}
@Test
public void testSetSelectedIndex_invokesCallback() {
public void onClick_invokesCallback() {
final int originalSelectedIndex = 2;
BatteryChartViewModel batteryChartViewModel = new BatteryChartViewModel(
List.of(90, 80, 70, 60), List.of("", "", "", ""),
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS);
batteryChartViewModel.setSelectedIndex(originalSelectedIndex);
mBatteryChartView.setViewModel(batteryChartViewModel);
for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) {
mBatteryChartView.mTrapezoidSlots[i] = new BatteryChartView.TrapezoidSlot();
mBatteryChartView.mTrapezoidSlots[i].mLeft = i;
mBatteryChartView.mTrapezoidSlots[i].mRight = i + 0.5f;
}
final int[] selectedIndex = new int[1];
final int expectedIndex = 2;
mBatteryChartView.mSelectedIndex = 1;
mBatteryChartView.setOnSelectListener(
trapezoidIndex -> {
selectedIndex[0] = trapezoidIndex;
});
mBatteryChartView.setSelectedIndex(expectedIndex);
// Verify onClick() a different index 1.
mBatteryChartView.mTouchUpEventX = 1;
selectedIndex[0] = Integer.MIN_VALUE;
mBatteryChartView.onClick(mMockView);
assertThat(selectedIndex[0]).isEqualTo(1);
assertThat(mBatteryChartView.mSelectedIndex)
.isEqualTo(expectedIndex);
assertThat(selectedIndex[0]).isEqualTo(expectedIndex);
// Verify onClick() the same index 2.
mBatteryChartView.mTouchUpEventX = 2;
selectedIndex[0] = Integer.MIN_VALUE;
mBatteryChartView.onClick(mMockView);
assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewModel.SELECTED_INDEX_ALL);
}
@Test
public void testSetSelectedIndex_sameIndex_notInvokesCallback() {
final int[] selectedIndex = new int[1];
final int expectedIndex = 1;
mBatteryChartView.mSelectedIndex = expectedIndex;
mBatteryChartView.setOnSelectListener(
trapezoidIndex -> {
selectedIndex[0] = trapezoidIndex;
});
mBatteryChartView.setSelectedIndex(expectedIndex);
assertThat(selectedIndex[0]).isNotEqualTo(expectedIndex);
}
@Test
public void testClickable_isChartGraphSlotsEnabledIsFalse_notClickable() {
public void clickable_isChartGraphSlotsEnabledIsFalse_notClickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(false);
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isFalse();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
}
@Test
public void testClickable_accessibilityIsDisabled_clickable() {
public void clickable_accessibilityIsDisabled_clickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(false).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@Test
public void testClickable_accessibilityIsEnabledWithoutValidId_clickable() {
public void clickable_accessibilityIsEnabledWithoutValidId_clickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
@@ -161,30 +167,34 @@ public final class BatteryChartViewTest {
.getEnabledAccessibilityServiceList(anyInt());
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isTrue();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
}
@Test
public void testClickable_accessibilityIsEnabledWithValidId_notClickable() {
public void clickable_accessibilityIsEnabledWithValidId_notClickable() {
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
doReturn(true).when(mMockAccessibilityManager).isEnabled();
mBatteryChartView.onAttachedToWindow();
assertThat(mBatteryChartView.isClickable()).isFalse();
assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
}
@Test
public void testClickable_restoreFromNonClickableState() {
final int[] levels = new int[13];
for (int index = 0; index < levels.length; index++) {
levels[index] = index + 1;
public void clickable_restoreFromNonClickableState() {
final List<Integer> levels = new ArrayList<Integer>();
final List<String> texts = new ArrayList<String>();
for (int index = 0; index < 13; index++) {
levels.add(index + 1);
texts.add("");
}
mBatteryChartView.setTrapezoidCount(12);
mBatteryChartView.setLevels(levels);
mBatteryChartView.setViewModel(new BatteryChartViewModel(levels, texts,
BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
mBatteryChartView.setClickableForce(true);
when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
.thenReturn(true);
@@ -201,14 +211,14 @@ public final class BatteryChartViewTest {
}
@Test
public void testOnAttachedToWindow_addAccessibilityStateChangeListener() {
public void onAttachedToWindow_addAccessibilityStateChangeListener() {
mBatteryChartView.onAttachedToWindow();
verify(mMockAccessibilityManager)
.addAccessibilityStateChangeListener(mBatteryChartView);
}
@Test
public void testOnDetachedFromWindow_removeAccessibilityStateChangeListener() {
public void onDetachedFromWindow_removeAccessibilityStateChangeListener() {
mBatteryChartView.onAttachedToWindow();
mBatteryChartView.mHandler.postDelayed(
mBatteryChartView.mUpdateClickableStateRun, 1000);
@@ -223,7 +233,7 @@ public final class BatteryChartViewTest {
}
@Test
public void testOnAccessibilityStateChanged_postUpdateStateRunnable() {
public void onAccessibilityStateChanged_postUpdateStateRunnable() {
mBatteryChartView.mHandler = spy(mBatteryChartView.mHandler);
mBatteryChartView.onAccessibilityStateChanged(/*enabled=*/ true);

View File

@@ -53,7 +53,7 @@ public final class BatteryHistoryLoaderTest {
public void testLoadIBackground_returnsMapFromPowerFeatureProvider() {
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
doReturn(batteryHistoryMap).when(mFeatureFactory.powerUsageFeatureProvider)
.getBatteryHistory(mContext);
.getBatteryHistorySinceLastFullCharge(mContext);
assertThat(mBatteryHistoryLoader.loadInBackground())
.isSameInstanceAs(batteryHistoryMap);

View File

@@ -26,6 +26,7 @@ import android.os.BatteryManager;
import android.os.BatteryUsageStats;
import android.os.LocaleList;
import android.os.UserHandle;
import android.text.format.DateUtils;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
@@ -39,8 +40,8 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -173,7 +174,8 @@ public final class ConvertUtilsTest {
public void getIndexedUsageMap_returnsExpectedResult() {
// Creates the fake testing data.
final int timeSlotSize = 2;
final long[] batteryHistoryKeys = new long[]{101L, 102L, 103L, 104L, 105L};
final long[] batteryHistoryKeys = new long[]{generateTimestamp(0), generateTimestamp(1),
generateTimestamp(2), generateTimestamp(3), generateTimestamp(4)};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
new HashMap<>();
final BatteryHistEntry fakeEntry = createBatteryHistEntry(
@@ -270,11 +272,11 @@ public final class ConvertUtilsTest {
for (int index = 0; index < remainingSize; index++) {
batteryHistoryMap.put(105L + index + 1, new HashMap<>());
}
when(mPowerUsageFeatureProvider.getBatteryHistory(mContext))
when(mPowerUsageFeatureProvider.getBatteryHistorySinceLastFullCharge(mContext))
.thenReturn(batteryHistoryMap);
final List<BatteryDiffEntry> batteryDiffEntryList =
BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext);
BatteryChartPreferenceController.getAppBatteryUsageData(mContext);
assertThat(batteryDiffEntryList).isNotEmpty();
final BatteryDiffEntry resultEntry = batteryDiffEntryList.get(0);
@@ -472,4 +474,9 @@ public final class ConvertUtilsTest {
assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
}
private static Long generateTimestamp(int index) {
// "2021-04-23 07:00:00 UTC" + index hours
return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS;
}
}

View File

@@ -0,0 +1,950 @@
/*
* 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 static org.mockito.Mockito.when;
import android.content.ContentValues;
import android.content.Context;
import android.text.format.DateUtils;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
@RunWith(RobolectricTestRunner.class)
public class DataProcessorTest {
private static final String FAKE_ENTRY_KEY = "fake_entry_key";
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
mContext = spy(RuntimeEnvironment.application);
mFeatureFactory = FakeFeatureFactory.setupForTest();
mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
}
@Test
public void getBatteryLevelData_emptyHistoryMap_returnNull() {
assertThat(DataProcessor.getBatteryLevelData(
mContext,
/*handler=*/ null,
/*batteryHistoryMap=*/ null,
/*asyncResponseDelegate=*/ null))
.isNull();
assertThat(DataProcessor.getBatteryLevelData(
mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null))
.isNull();
}
@Test
public void getBatteryLevelData_notEnoughData_returnNull() {
// The timestamps are within 1 hour.
final long[] timestamps = {1000000L, 2000000L, 3000000L};
final int[] levels = {100, 99, 98};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
assertThat(DataProcessor.getBatteryLevelData(
mContext, /*handler=*/ null, batteryHistoryMap, /*asyncResponseDelegate=*/ null))
.isNull();
}
@Test
public void getBatteryLevelData_returnExpectedResult() {
// Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00, 2022-01-01 02:00:00
final long[] timestamps = {1640966400000L, 1640970000000L, 1640973600000L};
final int[] levels = {100, 99, 98};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
final BatteryLevelData resultData =
DataProcessor.getBatteryLevelData(
mContext,
/*handler=*/ null,
batteryHistoryMap,
/*asyncResponseDelegate=*/ null);
final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[2]);
final List<Integer> expectedDailyLevels = List.of(levels[0], levels[2]);
final List<List<Long>> expectedHourlyTimestamps = List.of(expectedDailyTimestamps);
final List<List<Integer>> expectedHourlyLevels = List.of(expectedDailyLevels);
verifyExpectedBatteryLevelData(
resultData,
expectedDailyTimestamps,
expectedDailyLevels,
expectedHourlyTimestamps,
expectedHourlyLevels);
}
@Test
public void getHistoryMapWithExpectedTimestamps_emptyHistoryMap_returnEmptyMap() {
assertThat(DataProcessor
.getHistoryMapWithExpectedTimestamps(mContext, new HashMap<>()))
.isEmpty();
}
@Test
public void getHistoryMapWithExpectedTimestamps_returnExpectedMap() {
// Timezone GMT+8
final long[] timestamps = {
1640966700000L, // 2022-01-01 00:05:00
1640970180000L, // 2022-01-01 01:03:00
1640973840000L, // 2022-01-01 02:04:00
1640978100000L, // 2022-01-01 03:15:00
1640981400000L // 2022-01-01 04:10:00
};
final int[] levels = {100, 94, 90, 82, 50};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
final Map<Long, Map<String, BatteryHistEntry>> resultMap =
DataProcessor.getHistoryMapWithExpectedTimestamps(mContext, batteryHistoryMap);
// Timezone GMT+8
final long[] expectedTimestamps = {
1640966400000L, // 2022-01-01 00:00:00
1640970000000L, // 2022-01-01 01:00:00
1640973600000L, // 2022-01-01 02:00:00
1640977200000L, // 2022-01-01 03:00:00
1640980800000L // 2022-01-01 04:00:00
};
final int[] expectedLevels = {100, 94, 90, 84, 56};
assertThat(resultMap).hasSize(expectedLevels.length);
for (int index = 0; index < expectedLevels.length; index++) {
assertThat(resultMap.get(expectedTimestamps[index]).get(FAKE_ENTRY_KEY).mBatteryLevel)
.isEqualTo(expectedLevels[index]);
}
}
@Test
public void getLevelDataThroughProcessedHistoryMap_notEnoughData_returnNull() {
final long[] timestamps = {100L};
final int[] levels = {100};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
assertThat(
DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap))
.isNull();
}
@Test
public void getLevelDataThroughProcessedHistoryMap_OneDayData_returnExpectedResult() {
// Timezone GMT+8
final long[] timestamps = {
1640966400000L, // 2022-01-01 00:00:00
1640970000000L, // 2022-01-01 01:00:00
1640973600000L, // 2022-01-01 02:00:00
1640977200000L, // 2022-01-01 03:00:00
1640980800000L // 2022-01-01 04:00:00
};
final int[] levels = {100, 94, 90, 82, 50};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
final BatteryLevelData resultData =
DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap);
final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[4]);
final List<Integer> expectedDailyLevels = List.of(levels[0], levels[4]);
final List<List<Long>> expectedHourlyTimestamps = List.of(
List.of(timestamps[0], timestamps[2], timestamps[4])
);
final List<List<Integer>> expectedHourlyLevels = List.of(
List.of(levels[0], levels[2], levels[4])
);
verifyExpectedBatteryLevelData(
resultData,
expectedDailyTimestamps,
expectedDailyLevels,
expectedHourlyTimestamps,
expectedHourlyLevels);
}
@Test
public void getLevelDataThroughProcessedHistoryMap_MultipleDaysData_returnExpectedResult() {
// Timezone GMT+8
final long[] timestamps = {
1641038400000L, // 2022-01-01 20:00:00
1641060000000L, // 2022-01-02 02:00:00
1641067200000L, // 2022-01-02 04:00:00
1641081600000L, // 2022-01-02 08:00:00
};
final int[] levels = {100, 94, 90, 82};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
createHistoryMap(timestamps, levels);
final BatteryLevelData resultData =
DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap);
final List<Long> expectedDailyTimestamps = List.of(
1641038400000L, // 2022-01-01 20:00:00
1641052800000L, // 2022-01-02 00:00:00
1641081600000L // 2022-01-02 08:00:00
);
final List<Integer> expectedDailyLevels = new ArrayList<>();
expectedDailyLevels.add(100);
expectedDailyLevels.add(null);
expectedDailyLevels.add(82);
final List<List<Long>> expectedHourlyTimestamps = List.of(
List.of(
1641038400000L, // 2022-01-01 20:00:00
1641045600000L, // 2022-01-01 22:00:00
1641052800000L // 2022-01-02 00:00:00
),
List.of(
1641052800000L, // 2022-01-02 00:00:00
1641060000000L, // 2022-01-02 02:00:00
1641067200000L, // 2022-01-02 04:00:00
1641074400000L, // 2022-01-02 06:00:00
1641081600000L // 2022-01-02 08:00:00
)
);
final List<Integer> expectedHourlyLevels1 = new ArrayList<>();
expectedHourlyLevels1.add(100);
expectedHourlyLevels1.add(null);
expectedHourlyLevels1.add(null);
final List<Integer> expectedHourlyLevels2 = new ArrayList<>();
expectedHourlyLevels2.add(null);
expectedHourlyLevels2.add(94);
expectedHourlyLevels2.add(90);
expectedHourlyLevels2.add(null);
expectedHourlyLevels2.add(82);
final List<List<Integer>> expectedHourlyLevels = List.of(
expectedHourlyLevels1,
expectedHourlyLevels2
);
verifyExpectedBatteryLevelData(
resultData,
expectedDailyTimestamps,
expectedDailyLevels,
expectedHourlyTimestamps,
expectedHourlyLevels);
}
@Test
public void getTimestampSlots_emptyRawList_returnEmptyList() {
final List<Long> resultList =
DataProcessor.getTimestampSlots(new ArrayList<>());
assertThat(resultList).isEmpty();
}
@Test
public void getTimestampSlots_startWithEvenHour_returnExpectedResult() {
final Calendar startCalendar = Calendar.getInstance();
startCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
final Calendar endCalendar = Calendar.getInstance();
endCalendar.set(2022, 6, 5, 22, 30, 50); // 2022-07-05 22:30:50
final Calendar expectedStartCalendar = Calendar.getInstance();
expectedStartCalendar.set(2022, 6, 5, 6, 0, 0); // 2022-07-05 06:00:00
final Calendar expectedEndCalendar = Calendar.getInstance();
expectedEndCalendar.set(2022, 6, 5, 22, 0, 0); // 2022-07-05 22:00:00
verifyExpectedTimestampSlots(
startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar);
}
@Test
public void getTimestampSlots_startWithOddHour_returnExpectedResult() {
final Calendar startCalendar = Calendar.getInstance();
startCalendar.set(2022, 6, 5, 5, 0, 50); // 2022-07-05 05:00:50
final Calendar endCalendar = Calendar.getInstance();
endCalendar.set(2022, 6, 6, 21, 00, 50); // 2022-07-06 21:00:50
final Calendar expectedStartCalendar = Calendar.getInstance();
expectedStartCalendar.set(2022, 6, 5, 6, 00, 00); // 2022-07-05 06:00:00
final Calendar expectedEndCalendar = Calendar.getInstance();
expectedEndCalendar.set(2022, 6, 6, 20, 00, 00); // 2022-07-06 20:00:00
verifyExpectedTimestampSlots(
startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar);
}
@Test
public void getDailyTimestamps_notEnoughData_returnEmptyList() {
assertThat(DataProcessor.getDailyTimestamps(new ArrayList<>())).isEmpty();
assertThat(DataProcessor.getDailyTimestamps(List.of(100L))).isEmpty();
}
@Test
public void getDailyTimestamps_OneDayData_returnExpectedList() {
// Timezone GMT+8
final List<Long> timestamps = List.of(
1640966400000L, // 2022-01-01 00:00:00
1640970000000L, // 2022-01-01 01:00:00
1640973600000L, // 2022-01-01 02:00:00
1640977200000L, // 2022-01-01 03:00:00
1640980800000L // 2022-01-01 04:00:00
);
final List<Long> expectedTimestamps = List.of(
1640966400000L, // 2022-01-01 00:00:00
1640980800000L // 2022-01-01 04:00:00
);
assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
}
@Test
public void getDailyTimestamps_MultipleDaysData_returnExpectedList() {
// Timezone GMT+8
final List<Long> timestamps = List.of(
1640988000000L, // 2022-01-01 06:00:00
1641060000000L, // 2022-01-02 02:00:00
1641160800000L, // 2022-01-03 06:00:00
1641254400000L // 2022-01-04 08:00:00
);
final List<Long> expectedTimestamps = List.of(
1640988000000L, // 2022-01-01 06:00:00
1641052800000L, // 2022-01-02 00:00:00
1641139200000L, // 2022-01-03 00:00:00
1641225600000L, // 2022-01-04 00:00:00
1641254400000L // 2022-01-04 08:00:00
);
assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
}
@Test
public void isFromFullCharge_emptyData_returnFalse() {
assertThat(DataProcessor.isFromFullCharge(null)).isFalse();
assertThat(DataProcessor.isFromFullCharge(new HashMap<>())).isFalse();
}
@Test
public void isFromFullCharge_notChargedData_returnFalse() {
final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
final ContentValues values = new ContentValues();
values.put("batteryLevel", 98);
final BatteryHistEntry entry = new BatteryHistEntry(values);
entryMap.put(FAKE_ENTRY_KEY, entry);
assertThat(DataProcessor.isFromFullCharge(entryMap)).isFalse();
}
@Test
public void isFromFullCharge_chargedData_returnTrue() {
final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
final ContentValues values = new ContentValues();
values.put("batteryLevel", 100);
final BatteryHistEntry entry = new BatteryHistEntry(values);
entryMap.put(FAKE_ENTRY_KEY, entry);
assertThat(DataProcessor.isFromFullCharge(entryMap)).isTrue();
}
@Test
public void findNearestTimestamp_returnExpectedResult() {
long[] results = DataProcessor.findNearestTimestamp(
Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 15L);
assertThat(results).isEqualTo(new long[] {10L, 20L});
results = DataProcessor.findNearestTimestamp(
Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 10L);
assertThat(results).isEqualTo(new long[] {10L, 10L});
results = DataProcessor.findNearestTimestamp(
Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 5L);
assertThat(results).isEqualTo(new long[] {0L, 10L});
results = DataProcessor.findNearestTimestamp(
Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 50L);
assertThat(results).isEqualTo(new long[] {40L, 0L});
}
@Test
public void getTimestampOfNextDay_returnExpectedResult() {
// 2021-02-28 06:00:00 => 2021-03-01 00:00:00
assertThat(DataProcessor.getTimestampOfNextDay(1614463200000L))
.isEqualTo(1614528000000L);
// 2021-12-31 16:00:00 => 2022-01-01 00:00:00
assertThat(DataProcessor.getTimestampOfNextDay(1640937600000L))
.isEqualTo(1640966400000L);
}
@Test
public void isForDailyChart_returnExpectedResult() {
assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ true, 0L)).isTrue();
// 2022-01-01 00:00:00
assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ false, 1640966400000L))
.isTrue();
// 2022-01-01 01:00:05
assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ false, 1640970005000L))
.isFalse();
}
@Test
public void getBatteryUsageMap_emptyHistoryMap_returnNull() {
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
assertThat(DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, new HashMap<>())).isNull();
}
@Test
public void getBatteryUsageMap_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641045600000L, // 2022-01-01 22:00:00
1641049200000L, // 2022-01-01 23:00:00
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L, // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
final BatteryHistEntry fakeEntry = createBatteryHistEntry(
ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0, /*uid=*/ 0L,
currentUserId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
/*foregroundUsageTimeInMs=*/ 0L, /*backgroundUsageTimeInMs=*/ 0L);
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 5.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 20.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 15L,
25L);
entryMap.put(entry.getKey(), entry);
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
// Adds the index = 3 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 40.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 25L,
/*backgroundUsageTimeInMs=*/ 35L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 3L, currentUserId,
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*foregroundUsageTimeInMs=*/ 40L,
/*backgroundUsageTimeInMs=*/ 50L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package3", "label3", /*consumePower=*/ 15.0, /*uid=*/ 4L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 5L,
/*backgroundUsageTimeInMs=*/ 5L);
entryMap.put(entry.getKey(), entry);
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[3], entryMap);
// Adds the index = 4 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 40.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
/*backgroundUsageTimeInMs=*/ 40L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 20.0, /*uid=*/ 3L, currentUserId,
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*foregroundUsageTimeInMs=*/ 50L,
/*backgroundUsageTimeInMs=*/ 60L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package3", "label3", /*consumePower=*/ 40.0, /*uid=*/ 4L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 5L,
/*backgroundUsageTimeInMs=*/ 5L);
entryMap.put(entry.getKey(), entry);
entryMap.put(fakeEntry.getKey(), fakeEntry);
batteryHistoryMap.put(batteryHistoryKeys[4], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
// Adds the day 1 data.
List<Long> timestamps =
List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
// Adds the day 2 data.
timestamps = List.of(batteryHistoryKeys[2], batteryHistoryKeys[4]);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 40.0,
/*foregroundUsageTimeInMs=*/ 30, /*backgroundUsageTimeInMs=*/ 40);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(1), currentUserId, /*uid=*/ 4L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 40.0,
/*foregroundUsageTimeInMs=*/ 5, /*backgroundUsageTimeInMs=*/ 5);
assertBatteryDiffEntry(
resultDiffData.getSystemDiffEntryList().get(0), currentUserId, /*uid=*/ 3L,
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 20.0,
/*foregroundUsageTimeInMs=*/ 50, /*backgroundUsageTimeInMs=*/ 60);
resultDiffData = resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 100.0,
/*foregroundUsageTimeInMs=*/ 15, /*backgroundUsageTimeInMs=*/ 25);
resultDiffData = resultMap.get(1).get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 4L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0,
/*foregroundUsageTimeInMs=*/ 5, /*backgroundUsageTimeInMs=*/ 5);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(1), currentUserId, /*uid=*/ 2L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 25.0,
/*foregroundUsageTimeInMs=*/ 15, /*backgroundUsageTimeInMs=*/ 15);
assertBatteryDiffEntry(
resultDiffData.getSystemDiffEntryList().get(0), currentUserId, /*uid=*/ 3L,
ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 25.0,
/*foregroundUsageTimeInMs=*/ 50, /*backgroundUsageTimeInMs=*/ 60);
}
@Test
public void getBatteryUsageMap_multipleUsers_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 5.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId + 1,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 5.0, /*uid=*/ 3L, currentUserId + 2,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 15.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 30.0, /*uid=*/ 2L, currentUserId + 1,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 15.0, /*uid=*/ 3L, currentUserId + 2,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 25.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 50.0, /*uid=*/ 2L, currentUserId + 1,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 25.0, /*uid=*/ 3L, currentUserId + 2,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
/*backgroundUsageTimeInMs=*/ 30L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
final BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 1L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 25.0,
/*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 10);
assertBatteryDiffEntry(
resultDiffData.getSystemDiffEntryList().get(0), BatteryUtils.UID_OTHER_USERS,
/*uid=*/ BatteryUtils.UID_OTHER_USERS, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
/*consumePercentage=*/ 75.0, /*foregroundUsageTimeInMs=*/ 0,
/*backgroundUsageTimeInMs=*/ 0);
assertThat(resultMap.get(0).get(0)).isNotNull();
assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull();
}
@Test
public void getBatteryUsageMap_usageTimeExceed_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 500.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 3600000L,
/*backgroundUsageTimeInMs=*/ 7200000L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
final BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
// Verifies the clipped usage time.
final float ratio = (float) (7200) / (float) (3600 + 7200);
final BatteryDiffEntry resultEntry = resultDiffData.getAppDiffEntryList().get(0);
assertThat(resultEntry.mForegroundUsageTimeInMs)
.isEqualTo(Math.round(entry.mForegroundUsageTimeInMs * ratio));
assertThat(resultEntry.mBackgroundUsageTimeInMs)
.isEqualTo(Math.round(entry.mBackgroundUsageTimeInMs * ratio));
assertThat(resultEntry.mConsumePower)
.isEqualTo(entry.mConsumePower * ratio);
assertThat(resultMap.get(0).get(0)).isNotNull();
assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull();
}
@Test
public void getBatteryUsageMap_hideApplicationEntries_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
when(mPowerUsageFeatureProvider.getHideApplicationEntries(mContext))
.thenReturn(new CharSequence[]{"package1"});
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
final BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
assertBatteryDiffEntry(
resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0,
/*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 20);
}
@Test
public void getBatteryUsageMap_hideBackgroundUsageTime_returnsExpectedResult() {
final long[] batteryHistoryKeys = new long[]{
1641052800000L, // 2022-01-02 00:00:00
1641056400000L, // 2022-01-02 01:00:00
1641060000000L // 2022-01-02 02:00:00
};
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
final int currentUserId = mContext.getUserId();
// Adds the index = 0 data.
Map<String, BatteryHistEntry> entryMap = new HashMap<>();
BatteryHistEntry entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
// Adds the index = 1 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
/*backgroundUsageTimeInMs=*/ 0L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
// Adds the index = 2 data.
entryMap = new HashMap<>();
entry = createBatteryHistEntry(
"package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 1L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
entry = createBatteryHistEntry(
"package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId,
ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
/*backgroundUsageTimeInMs=*/ 20L);
entryMap.put(entry.getKey(), entry);
batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
final List<Integer> levels = List.of(100, 100);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
when(mPowerUsageFeatureProvider.getHideBackgroundUsageTimeSet(mContext))
.thenReturn(new HashSet(Arrays.asList((CharSequence) "package2")));
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
DataProcessor.getBatteryUsageMap(
mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
final BatteryDiffData resultDiffData =
resultMap
.get(DataProcessor.SELECTED_INDEX_ALL)
.get(DataProcessor.SELECTED_INDEX_ALL);
BatteryDiffEntry resultEntry = resultDiffData.getAppDiffEntryList().get(0);
assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(20);
resultEntry = resultDiffData.getAppDiffEntryList().get(1);
assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(0);
}
private static Map<Long, Map<String, BatteryHistEntry>> createHistoryMap(
final long[] timestamps, final int[] levels) {
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
for (int index = 0; index < timestamps.length; index++) {
final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
final ContentValues values = new ContentValues();
values.put(BatteryHistEntry.KEY_BATTERY_LEVEL, levels[index]);
final BatteryHistEntry entry = new BatteryHistEntry(values);
entryMap.put(FAKE_ENTRY_KEY, entry);
batteryHistoryMap.put(timestamps[index], entryMap);
}
return batteryHistoryMap;
}
private static BatteryHistEntry createBatteryHistEntry(
final String packageName, final String appLabel, final double consumePower,
final long uid, final long userId, final int consumerType,
final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs) {
// Only insert required fields.
final ContentValues values = new ContentValues();
values.put(BatteryHistEntry.KEY_PACKAGE_NAME, packageName);
values.put(BatteryHistEntry.KEY_APP_LABEL, appLabel);
values.put(BatteryHistEntry.KEY_UID, uid);
values.put(BatteryHistEntry.KEY_USER_ID, userId);
values.put(BatteryHistEntry.KEY_CONSUMER_TYPE, consumerType);
values.put(BatteryHistEntry.KEY_CONSUME_POWER, consumePower);
values.put(BatteryHistEntry.KEY_FOREGROUND_USAGE_TIME, foregroundUsageTimeInMs);
values.put(BatteryHistEntry.KEY_BACKGROUND_USAGE_TIME, backgroundUsageTimeInMs);
return new BatteryHistEntry(values);
}
private static void verifyExpectedBatteryLevelData(
final BatteryLevelData resultData,
final List<Long> expectedDailyTimestamps,
final List<Integer> expectedDailyLevels,
final List<List<Long>> expectedHourlyTimestamps,
final List<List<Integer>> expectedHourlyLevels) {
final BatteryLevelData.PeriodBatteryLevelData dailyResultData =
resultData.getDailyBatteryLevels();
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData =
resultData.getHourlyBatteryLevelsPerDay();
verifyExpectedDailyBatteryLevelData(
dailyResultData, expectedDailyTimestamps, expectedDailyLevels);
verifyExpectedHourlyBatteryLevelData(
hourlyResultData, expectedHourlyTimestamps, expectedHourlyLevels);
}
private static void verifyExpectedDailyBatteryLevelData(
final BatteryLevelData.PeriodBatteryLevelData dailyResultData,
final List<Long> expectedDailyTimestamps,
final List<Integer> expectedDailyLevels) {
assertThat(dailyResultData.getTimestamps()).isEqualTo(expectedDailyTimestamps);
assertThat(dailyResultData.getLevels()).isEqualTo(expectedDailyLevels);
}
private static void verifyExpectedHourlyBatteryLevelData(
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData,
final List<List<Long>> expectedHourlyTimestamps,
final List<List<Integer>> expectedHourlyLevels) {
final int expectedHourlySize = expectedHourlyTimestamps.size();
assertThat(hourlyResultData).hasSize(expectedHourlySize);
for (int dailyIndex = 0; dailyIndex < expectedHourlySize; dailyIndex++) {
assertThat(hourlyResultData.get(dailyIndex).getTimestamps())
.isEqualTo(expectedHourlyTimestamps.get(dailyIndex));
assertThat(hourlyResultData.get(dailyIndex).getLevels())
.isEqualTo(expectedHourlyLevels.get(dailyIndex));
}
}
private static void verifyExpectedTimestampSlots(
final Calendar start,
final Calendar end,
final Calendar expectedStart,
final Calendar expectedEnd) {
expectedStart.set(Calendar.MILLISECOND, 0);
expectedEnd.set(Calendar.MILLISECOND, 0);
final ArrayList<Long> timestampSlots = new ArrayList<>();
timestampSlots.add(start.getTimeInMillis());
timestampSlots.add(end.getTimeInMillis());
final List<Long> resultList =
DataProcessor.getTimestampSlots(timestampSlots);
for (int index = 0; index < resultList.size(); index++) {
final long expectedTimestamp =
expectedStart.getTimeInMillis() + index * DateUtils.HOUR_IN_MILLIS;
assertThat(resultList.get(index)).isEqualTo(expectedTimestamp);
}
assertThat(resultList.get(resultList.size() - 1))
.isEqualTo(expectedEnd.getTimeInMillis());
}
private static void assertBatteryDiffEntry(
final BatteryDiffEntry entry, final long userId, final long uid,
final int consumerType, final double consumePercentage,
final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs) {
assertThat(entry.mBatteryHistEntry.mUserId).isEqualTo(userId);
assertThat(entry.mBatteryHistEntry.mUid).isEqualTo(uid);
assertThat(entry.mBatteryHistEntry.mConsumerType).isEqualTo(consumerType);
assertThat(entry.getPercentOfTotal()).isEqualTo(consumePercentage);
assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
}
}

View File

@@ -139,17 +139,7 @@ public class PowerUsageSummaryTest {
}
@Test
public void initPreference_chartGraphEnabled_hasCorrectSummary() {
mFragment.initPreference();
verify(mBatteryUsagePreference).setSummary("View usage for past 24 hours");
}
@Test
public void initPreference_chartGraphDisabled_hasCorrectSummary() {
when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mRealContext))
.thenReturn(false);
public void initPreference_hasCorrectSummary() {
mFragment.initPreference();
verify(mBatteryUsagePreference).setSummary("View usage from last full charge");

View File

@@ -16,9 +16,18 @@
package com.android.settings.widget;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.content.Context;
import android.view.View;
import android.widget.Button;
import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
@@ -26,23 +35,301 @@ 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 class CardPreferenceTest {
private Context mContext;
private CardPreference mCardPreference;
private PreferenceViewHolder mHolder;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
mContext.setTheme(R.style.SettingsPreferenceTheme);
mCardPreference = new CardPreference(mContext);
Context context = ApplicationProvider.getApplicationContext();
context.setTheme(R.style.Theme_Settings);
mCardPreference = new CardPreference(context);
View rootView = View.inflate(context, R.layout.card_preference_layout, /* parent= */ null);
mHolder = PreferenceViewHolder.createInstanceForTests(rootView);
}
@Test
public void getLayoutResource() {
assertThat(mCardPreference.getLayoutResource()).isEqualTo(R.layout.card_preference_layout);
public void newACardPreference_layoutResourceShouldBeCardPreferenceLayout() {
Context context = ApplicationProvider.getApplicationContext();
context.setTheme(R.style.SettingsPreferenceTheme);
CardPreference cardPreference = new CardPreference(context);
assertThat(cardPreference.getLayoutResource()).isEqualTo(R.layout.card_preference_layout);
}
@Test
public void onBindViewHolder_noButtonVisible_buttonsLayoutShouldBeGone() {
mCardPreference.onBindViewHolder(mHolder);
assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
}
@Test
public void onBindViewHolder_setPrimaryButtonVisibility_buttonsLayoutShouldBeVisible() {
mCardPreference.setPrimaryButtonVisible(true);
mCardPreference.onBindViewHolder(mHolder);
assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(VISIBLE);
}
@Test
public void onBindViewHolder_setPrimaryButtonVisibility_shouldApplyToPrimaryButton() {
mCardPreference.setPrimaryButtonVisible(true);
mCardPreference.onBindViewHolder(mHolder);
assertThat(getPrimaryButton().getVisibility()).isEqualTo(VISIBLE);
}
@Test
public void onBindViewHolder_setSecondaryButtonVisibility_buttonsLayoutShouldBeVisible() {
mCardPreference.setSecondaryButtonVisible(true);
mCardPreference.onBindViewHolder(mHolder);
assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(VISIBLE);
}
@Test
public void onBindViewHolder_setSecondaryButtonVisibility_shouldApplyToSecondaryButton() {
mCardPreference.setSecondaryButtonVisible(true);
mCardPreference.onBindViewHolder(mHolder);
assertThat(getSecondaryButton().getVisibility()).isEqualTo(VISIBLE);
}
@Test
public void onBindViewHolder_setPrimaryButtonText_shouldApplyToPrimaryButton() {
String expectedText = "primary-button";
mCardPreference.setPrimaryButtonText(expectedText);
mCardPreference.onBindViewHolder(mHolder);
assertThat(getPrimaryButton().getText().toString()).isEqualTo(expectedText);
}
@Test
public void onBindViewHolder_setSecondaryButtonText_shouldApplyToSecondaryButton() {
String expectedText = "secondary-button";
mCardPreference.setSecondaryButtonText(expectedText);
mCardPreference.onBindViewHolder(mHolder);
assertThat(getSecondaryButton().getText().toString()).isEqualTo(expectedText);
}
@Test
public void onBindViewHolder_initialTextForPrimaryButtonShouldBeEmpty() {
mCardPreference.onBindViewHolder(mHolder);
assertThat(getPrimaryButton().getText().toString()).isEqualTo("");
}
@Test
public void onBindViewHolder_initialTextForSecondaryButtonShouldBeEmpty() {
mCardPreference.onBindViewHolder(mHolder);
assertThat(getSecondaryButton().getText().toString()).isEqualTo("");
}
@Test
public void performClickOnPrimaryButton_shouldCalledClickListener() {
final boolean[] hasCalled = {false};
View.OnClickListener clickListener = v -> hasCalled[0] = true;
mCardPreference.setPrimaryButtonClickListener(clickListener);
mCardPreference.onBindViewHolder(mHolder);
getPrimaryButton().performClick();
assertThat(hasCalled[0]).isTrue();
}
@Test
public void performClickOnSecondaryButton_shouldCalledClickListener() {
final boolean[] hasCalled = {false};
View.OnClickListener clickListener = v -> hasCalled[0] = true;
mCardPreference.setSecondaryButtonClickListener(clickListener);
mCardPreference.onBindViewHolder(mHolder);
getSecondaryButton().performClick();
assertThat(hasCalled[0]).isTrue();
}
@Test
public void onBindViewHolder_primaryButtonDefaultIsGone() {
mCardPreference.onBindViewHolder(mHolder);
assertThat(getPrimaryButton().getVisibility()).isEqualTo(GONE);
}
@Test
public void onBindViewHolder_secondaryButtonDefaultIsGone() {
mCardPreference.onBindViewHolder(mHolder);
assertThat(getSecondaryButton().getVisibility()).isEqualTo(GONE);
}
@Test
public void setPrimaryButtonVisibility_setTrueAfterBindViewHolder_shouldBeVisible() {
mCardPreference.setPrimaryButtonVisible(false);
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setPrimaryButtonVisible(true);
assertThat(getPrimaryButton().getVisibility()).isEqualTo(VISIBLE);
}
@Test
public void setPrimaryButtonText_setAfterBindViewHolder_setOnUi() {
String expectedText = "123456";
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setPrimaryButtonText(expectedText);
assertThat(getPrimaryButton().getText().toString()).isEqualTo(expectedText);
}
@Test
public void setPrimaryButtonText_setNull_shouldBeEmptyText() {
final String emptyString = "";
mCardPreference.setPrimaryButtonText("1234");
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setPrimaryButtonText(null);
assertThat(getPrimaryButton().getText().toString()).isEqualTo(emptyString);
}
@Test
public void setPrimaryButtonClickListener_setAfterOnBindViewHolder() {
final String[] hasCalled = {""};
String expectedClickedResult = "was called";
View.OnClickListener clickListener = v -> hasCalled[0] = expectedClickedResult;
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setPrimaryButtonClickListener(clickListener);
getPrimaryButton().performClick();
assertThat(hasCalled[0]).isEqualTo(expectedClickedResult);
}
@Test
public void setPrimaryButtonClickListener_setNull_shouldClearTheOnClickListener() {
final String[] hasCalled = {"not called"};
View.OnClickListener clickListener = v -> hasCalled[0] = "called once";
mCardPreference.setPrimaryButtonClickListener(clickListener);
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setPrimaryButtonClickListener(null);
getPrimaryButton().performClick();
assertThat(hasCalled[0]).isEqualTo("not called");
}
@Test
public void setSecondaryButtonVisibility_setTrueAfterBindViewHolder_shouldBeVisible() {
mCardPreference.setSecondaryButtonVisible(false);
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setSecondaryButtonVisible(true);
assertThat(getSecondaryButton().getVisibility()).isEqualTo(VISIBLE);
}
@Test
public void setSecondaryButtonText_setAfterBindViewHolder_setOnUi() {
String expectedText = "10101010";
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setSecondaryButtonText(expectedText);
assertThat(getSecondaryButton().getText().toString()).isEqualTo(expectedText);
}
@Test
public void setSecondaryButtonText_setNull_shouldBeEmptyText() {
String emptyString = "";
mCardPreference.setSecondaryButtonText("1234");
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setSecondaryButtonText(null);
assertThat(getSecondaryButton().getText().toString()).isEqualTo(emptyString);
}
@Test
public void setSecondaryButtonClickListener_setAfterOnBindViewHolder() {
final String[] hasCalled = {""};
String expectedClickedResult = "2nd was called";
View.OnClickListener clickListener = v -> hasCalled[0] = expectedClickedResult;
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setSecondaryButtonClickListener(clickListener);
getSecondaryButton().performClick();
assertThat(hasCalled[0]).isEqualTo(expectedClickedResult);
}
@Test
public void setSecondaryButtonClickListener_setNull_shouldClearTheOnClickListener() {
final String[] hasCalled = {"not called"};
View.OnClickListener clickListener = v -> hasCalled[0] = "called once";
mCardPreference.setSecondaryButtonClickListener(clickListener);
mCardPreference.onBindViewHolder(mHolder);
mCardPreference.setSecondaryButtonClickListener(null);
getSecondaryButton().performClick();
assertThat(hasCalled[0]).isEqualTo("not called");
}
@Test
public void
setPrimaryButtonVisibility_onlyPrimaryButtonVisible_setGone_buttonGroupShouldBeGone() {
mCardPreference.setPrimaryButtonVisible(true);
mCardPreference.setSecondaryButtonVisible(false);
mCardPreference.onBindViewHolder(mHolder);
assertWithMessage("PreCondition: buttonsView should be Visible")
.that(getCardPreferenceButtonsView().getVisibility())
.isEqualTo(VISIBLE);
mCardPreference.setPrimaryButtonVisible(false);
assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
}
@Test
public void
setSecondaryButtonVisibility_only2ndButtonVisible_setGone_buttonGroupShouldBeGone() {
mCardPreference.setPrimaryButtonVisible(false);
mCardPreference.setSecondaryButtonVisible(true);
mCardPreference.onBindViewHolder(mHolder);
assertWithMessage("PreCondition: buttonsView should be Visible")
.that(getCardPreferenceButtonsView().getVisibility())
.isEqualTo(VISIBLE);
mCardPreference.setSecondaryButtonVisible(false);
assertThat(getCardPreferenceButtonsView().getVisibility()).isEqualTo(GONE);
}
private View getCardPreferenceButtonsView() {
return mHolder.findViewById(R.id.card_preference_buttons);
}
private Button getPrimaryButton() {
return (Button) mHolder.findViewById(android.R.id.button1);
}
private Button getSecondaryButton() {
return (Button) mHolder.findViewById(android.R.id.button2);
}
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.wifi;
import static com.android.settings.wifi.WifiConfigController2.WIFI_EAP_METHOD_SIM;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -86,6 +88,8 @@ public class WifiConfigController2Test {
private AndroidKeystoreAliasLoader mAndroidKeystoreAliasLoader;
@Mock
private WifiManager mWifiManager;
@Mock
Spinner mEapMethodSimSpinner;
private View mView;
private Spinner mHiddenSettingsSpinner;
private Spinner mEapCaCertSpinner;
@@ -141,6 +145,7 @@ public class WifiConfigController2Test {
mContext.getString(R.string.wifi_do_not_provide_eap_user_cert);
ipSettingsSpinner.setSelection(DHCP);
mShadowSubscriptionManager = shadowOf(mContext.getSystemService(SubscriptionManager.class));
when(mEapMethodSimSpinner.getSelectedItemPosition()).thenReturn(WIFI_EAP_METHOD_SIM);
mController = new TestWifiConfigController2(mConfigUiBase, mView, mWifiEntry,
WifiConfigUiBase2.MODE_CONNECT);
@@ -813,10 +818,7 @@ public class WifiConfigController2Test {
when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_EAP);
mController = new TestWifiConfigController2(mConfigUiBase, mView, mWifiEntry,
WifiConfigUiBase2.MODE_CONNECT);
final Spinner eapMethodSpinner = mock(Spinner.class);
when(eapMethodSpinner.getSelectedItemPosition()).thenReturn(
WifiConfigController2.WIFI_EAP_METHOD_SIM);
mController.mEapMethodSpinner = eapMethodSpinner;
mController.mEapMethodSpinner = mEapMethodSimSpinner;
mController.loadSims();
@@ -837,10 +839,7 @@ public class WifiConfigController2Test {
mShadowSubscriptionManager.setActiveSubscriptionInfoList(Arrays.asList(subscriptionInfo));
mController = new TestWifiConfigController2(mConfigUiBase, mView, mWifiEntry,
WifiConfigUiBase2.MODE_CONNECT);
final Spinner eapMethodSpinner = mock(Spinner.class);
when(eapMethodSpinner.getSelectedItemPosition()).thenReturn(
WifiConfigController2.WIFI_EAP_METHOD_SIM);
mController.mEapMethodSpinner = eapMethodSpinner;
mController.mEapMethodSpinner = mEapMethodSimSpinner;
mController.loadSims();
@@ -848,6 +847,48 @@ public class WifiConfigController2Test {
assertThat(wifiConfiguration.carrierId).isEqualTo(carrierId);
}
@Test
public void loadSims_twoSimsWithDifferentCarrierId_showTwoSims() {
SubscriptionInfo sub1 = createMockSubscription(1, "sub1", 8888);
SubscriptionInfo sub2 = createMockSubscription(2, "sub2", 9999);
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2));
mShadowSubscriptionManager.setActiveSubscriptionInfoList(Arrays.asList(sub1, sub2));
when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_EAP);
mController = new TestWifiConfigController2(mConfigUiBase, mView, mWifiEntry,
WifiConfigUiBase2.MODE_CONNECT);
mController.mEapMethodSpinner = mEapMethodSimSpinner;
ShadowSubscriptionManager.setDefaultDataSubscriptionId(1);
mController.loadSims();
assertThat(mController.mEapSimSpinner.getAdapter().getCount()).isEqualTo(2);
}
@Test
public void loadSims_twoSimsWithSameCarrierId_showOneDefaultDataSim() {
SubscriptionInfo sub1 = createMockSubscription(1, "sub1", 9999);
SubscriptionInfo sub2 = createMockSubscription(2, "sub2", 9999);
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2));
mShadowSubscriptionManager.setActiveSubscriptionInfoList(Arrays.asList(sub1, sub2));
when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_EAP);
mController = new TestWifiConfigController2(mConfigUiBase, mView, mWifiEntry,
WifiConfigUiBase2.MODE_CONNECT);
mController.mEapMethodSpinner = mEapMethodSimSpinner;
ShadowSubscriptionManager.setDefaultDataSubscriptionId(1);
mController.loadSims();
assertThat(mController.mEapSimSpinner.getAdapter().getCount()).isEqualTo(1);
assertThat(mController.mEapSimSpinner.getSelectedItem().toString()).isEqualTo("sub1");
ShadowSubscriptionManager.setDefaultDataSubscriptionId(2);
mController.loadSims();
assertThat(mController.mEapSimSpinner.getAdapter().getCount()).isEqualTo(1);
assertThat(mController.mEapSimSpinner.getSelectedItem().toString()).isEqualTo("sub2");
}
@Test
public void loadCaCertificateValue_shouldPersistentAsDefault() {
setUpModifyingSavedCertificateConfigController(null, null);
@@ -940,4 +981,12 @@ public class WifiConfigController2Test {
// certificates are covered by mController.onItemSelected after showSecurityFields end.
mController.mEapMethodSpinner.setSelection(Eap.TLS);
}
private SubscriptionInfo createMockSubscription(int subId, String displayName, int carrierId) {
SubscriptionInfo sub = mock(SubscriptionInfo.class);
when(sub.getSubscriptionId()).thenReturn(subId);
when(sub.getDisplayName()).thenReturn(displayName);
when(sub.getCarrierId()).thenReturn(carrierId);
return sub;
}
}

View File

@@ -58,6 +58,7 @@ import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import android.view.View;
import android.view.View.OnClickListener;
@@ -108,6 +109,7 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
// TODO(b/143326832): Should add test cases for connect button.
@@ -1791,4 +1793,74 @@ public class WifiDetailPreferenceController2Test {
return pref;
}
@Test
public void fineSubscriptionInfo_noMatchedCarrierId_returnNull() {
setUpSpyController();
SubscriptionInfo sub1 = mockSubscriptionInfo(1, "sim1", 1111);
SubscriptionInfo sub2 = mockSubscriptionInfo(2, "sim2", 2222);
List<SubscriptionInfo> activeSubInfos = Arrays.asList(sub1, sub2);
SubscriptionInfo info = mController.fineSubscriptionInfo(3333, activeSubInfos, 1);
assertThat(info).isNull();
info = mController.fineSubscriptionInfo(3333, activeSubInfos, 2);
assertThat(info).isNull();
}
@Test
public void fineSubscriptionInfo_diffCarrierId_returnMatchedOne() {
setUpSpyController();
SubscriptionInfo sub1 = mockSubscriptionInfo(1, "sim1", 1111);
SubscriptionInfo sub2 = mockSubscriptionInfo(2, "sim2", 2222);
List<SubscriptionInfo> activeSubInfos = Arrays.asList(sub1, sub2);
SubscriptionInfo info = mController.fineSubscriptionInfo(1111, activeSubInfos, 1);
assertThat(info).isNotNull();
assertThat(info.getDisplayName().toString()).isEqualTo("sim1");
info = mController.fineSubscriptionInfo(1111, activeSubInfos, 2);
assertThat(info).isNotNull();
assertThat(info.getDisplayName().toString()).isEqualTo("sim1");
info = mController.fineSubscriptionInfo(2222, activeSubInfos, 1);
assertThat(info).isNotNull();
assertThat(info.getDisplayName().toString()).isEqualTo("sim2");
info = mController.fineSubscriptionInfo(2222, activeSubInfos, 2);
assertThat(info).isNotNull();
assertThat(info.getDisplayName().toString()).isEqualTo("sim2");
}
@Test
public void fineSubscriptionInfo_sameCarrierId_returnDefaultDataOne() {
setUpSpyController();
SubscriptionInfo sub1 = mockSubscriptionInfo(1, "sim1", 1111);
SubscriptionInfo sub2 = mockSubscriptionInfo(2, "sim2", 1111);
List<SubscriptionInfo> activeSubInfos = Arrays.asList(sub1, sub2);
SubscriptionInfo info = mController.fineSubscriptionInfo(1111, activeSubInfos, 1);
assertThat(info).isNotNull();
assertThat(info.getDisplayName().toString()).isEqualTo("sim1");
info = mController.fineSubscriptionInfo(1111, activeSubInfos, 2);
assertThat(info).isNotNull();
assertThat(info.getDisplayName().toString()).isEqualTo("sim2");
}
private SubscriptionInfo mockSubscriptionInfo(int subId, String displayName, int carrierId) {
SubscriptionInfo info = mock(SubscriptionInfo.class);
when(info.getSubscriptionId()).thenReturn(subId);
when(info.getDisplayName()).thenReturn(displayName);
when(info.getCarrierId()).thenReturn(carrierId);
return info;
}
}

View File

@@ -16,26 +16,42 @@
package com.android.settings.security;
import static android.content.Context.FACE_SERVICE;
import static android.content.Context.FINGERPRINT_SERVICE;
import static android.content.pm.PackageManager.FEATURE_FACE;
import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_INCLUDE_PREF_SCREEN;
import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_KEY;
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.when;
import android.annotation.XmlRes;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.os.Looper;
import android.provider.SearchIndexableResource;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settings.biometrics.combination.CombinedBiometricStatusPreferenceController;
import com.android.settings.biometrics.face.FaceStatusPreferenceController;
import com.android.settings.biometrics.fingerprint.FingerprintStatusPreferenceController;
import com.android.settings.core.PreferenceXmlParserUtils;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.security.trustagent.TrustAgentManager;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
import org.junit.Before;
import org.junit.Test;
@@ -52,26 +68,44 @@ public class SecuritySettingsTest {
private Context mContext;
private SecuritySettingsFeatureProvider mSecuritySettingsFeatureProvider;
private SecuritySettings mSecuritySettings;
@Mock
private TrustAgentManager mTrustAgentManager;
@Mock
private FaceManager mFaceManager;
@Mock
private FingerprintManager mFingerprintManager;
@Mock
private PackageManager mPackageManager;
@Before
@UiThreadTest
public void setup() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.hasSystemFeature(FEATURE_FACE)).thenReturn(true);
when(mPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
doReturn(mFaceManager).when(mContext).getSystemService(FACE_SERVICE);
doReturn(mFingerprintManager).when(mContext).getSystemService(FINGERPRINT_SERVICE);
FakeFeatureFactory mFeatureFactory = FakeFeatureFactory.setupForTest();
mSecuritySettingsFeatureProvider = mFeatureFactory.getSecuritySettingsFeatureProvider();
SecurityFeatureProvider mSecurityFeatureProvider =
mFeatureFactory.getSecurityFeatureProvider();
when(mSecurityFeatureProvider.getTrustAgentManager()).thenReturn(mTrustAgentManager);
mSecuritySettingsFeatureProvider = mFeatureFactory.getSecuritySettingsFeatureProvider();
mSecuritySettings = new SecuritySettings();
}
@Test
public void noAlternativeFragmentAvailable_pageIndexIncluded() throws Exception {
when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment())
.thenReturn(false);
when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment()).thenReturn(
false);
BaseSearchIndexProvider indexProvider = SecuritySettings.SEARCH_INDEX_DATA_PROVIDER;
List<String> allXmlKeys = getAllXmlKeys(indexProvider);
@@ -83,8 +117,8 @@ public class SecuritySettingsTest {
@Test
public void alternativeFragmentAvailable_pageIndexExcluded() throws Exception {
when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment())
.thenReturn(true);
when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment()).thenReturn(
true);
BaseSearchIndexProvider indexProvider = SecuritySettings.SEARCH_INDEX_DATA_PROVIDER;
List<String> allXmlKeys = getAllXmlKeys(indexProvider);
@@ -94,6 +128,45 @@ public class SecuritySettingsTest {
assertThat(allXmlKeys).isEmpty();
}
@Test
@UiThreadTest
public void preferenceController_containsFaceWhenAvailable() {
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
final List<AbstractPreferenceController> controllers =
mSecuritySettings.createPreferenceControllers(mContext);
assertThat(isFacePrefAvailable(controllers)).isTrue();
assertThat(isFingerprintPrefAvailable(controllers)).isFalse();
assertThat(isCombinedPrefAvailable(controllers)).isFalse();
}
@Test
@UiThreadTest
public void preferenceController_containsFingerprintWhenAvailable() {
when(mFaceManager.isHardwareDetected()).thenReturn(false);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
final List<AbstractPreferenceController> controllers =
mSecuritySettings.createPreferenceControllers(mContext);
assertThat(isFacePrefAvailable(controllers)).isFalse();
assertThat(isFingerprintPrefAvailable(controllers)).isTrue();
assertThat(isCombinedPrefAvailable(controllers)).isFalse();
}
@Test
@UiThreadTest
public void preferenceController_containsCombinedBiometricWhenAvailable() {
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
final List<AbstractPreferenceController> controllers =
mSecuritySettings.createPreferenceControllers(mContext);
assertThat(isFacePrefAvailable(controllers)).isFalse();
assertThat(isFingerprintPrefAvailable(controllers)).isFalse();
assertThat(isCombinedPrefAvailable(controllers)).isTrue();
}
private List<String> getAllXmlKeys(BaseSearchIndexProvider indexProvider) throws Exception {
final List<SearchIndexableResource> resources = indexProvider.getXmlResourcesToIndex(
mContext, true /* not used*/);
@@ -109,11 +182,29 @@ public class SecuritySettingsTest {
private List<String> getKeysFromXml(@XmlRes int xmlResId) throws Exception {
final List<String> keys = new ArrayList<>();
final List<Bundle> metadata = PreferenceXmlParserUtils
.extractMetadata(mContext, xmlResId, FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN);
final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext, xmlResId,
FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN);
for (Bundle bundle : metadata) {
keys.add(bundle.getString(METADATA_KEY));
}
return keys;
}
boolean isFacePrefAvailable(List<AbstractPreferenceController> controllers) {
return controllers.stream().filter(
controller -> controller instanceof FaceStatusPreferenceController
&& controller.isAvailable()).count() == 1;
}
boolean isFingerprintPrefAvailable(List<AbstractPreferenceController> controllers) {
return controllers.stream().filter(
controller -> controller instanceof FingerprintStatusPreferenceController
&& controller.isAvailable()).count() == 1;
}
boolean isCombinedPrefAvailable(List<AbstractPreferenceController> controllers) {
return controllers.stream().filter(
controller -> controller instanceof CombinedBiometricStatusPreferenceController
&& controller.isAvailable()).count() == 1;
}
}