Loading src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java +37 −52 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; Loading @@ -53,7 +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; Loading Loading @@ -98,13 +98,13 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro @VisibleForTesting boolean mIsExpanded = false; @VisibleForTesting int[] mBatteryHistoryLevels; @VisibleForTesting long[] mBatteryHistoryKeys; @VisibleForTesting int mTrapezoidIndex = BatteryChartViewV2.SELECTED_INDEX_INVALID; BatteryChartViewModel mViewModel; @VisibleForTesting int mTrapezoidIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; private boolean mIs24HourFormat = false; private boolean mIs24HourFormat; private boolean mIsFooterPrefAdded = false; private PreferenceScreen mPreferenceScreen; private FooterPreference mFooterPreference; Loading Loading @@ -252,10 +252,11 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro @Override public void onSelect(int trapezoidIndex) { Log.d(TAG, "onChartSelect:" + trapezoidIndex); refreshUi(trapezoidIndex, /*isForce=*/ false); mTrapezoidIndex = trapezoidIndex; refreshUi(); mMetricsFeatureProvider.action( mPrefContext, trapezoidIndex == BatteryChartViewV2.SELECTED_INDEX_ALL trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT); } Loading @@ -276,18 +277,19 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { mBatteryIndexedMap = null; mBatteryHistoryKeys = null; mBatteryHistoryLevels = null; mViewModel = null; addFooterPreferenceIfNeeded(false); return; } mBatteryHistoryKeys = getBatteryHistoryKeys(batteryHistoryMap); mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE]; List<Integer> levels = new ArrayList<Integer>(); 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)); levels.add(0); continue; } // Averages the battery level in each time slot to avoid corner conditions. Loading @@ -295,16 +297,17 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro for (BatteryHistEntry entry : entryMap.values()) { batteryLevelCounter += entry.mBatteryLevel; } mBatteryHistoryLevels[index] = Math.round(batteryLevelCounter / entryMap.size()); levels.add(Math.round(batteryLevelCounter / entryMap.size())); } forceRefreshUi(); final List<String> texts = generateTimestampTexts(mBatteryHistoryKeys, mContext); mViewModel = new BatteryChartViewModel(levels, texts, mTrapezoidIndex); refreshUi(); Log.d(TAG, String.format( "setBatteryHistoryMap() size=%d key=%s\nlevels=%s", "setBatteryHistoryMap() size=%d key=%s\nview model=%s", batteryHistoryMap.size(), ConvertUtils.utcToLocalTime(mPrefContext, mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1]), Arrays.toString(mBatteryHistoryLevels))); mViewModel)); // Loads item icon and label in the background. new LoadAllItemsInfoTask(batteryHistoryMap).execute(); Loading @@ -319,35 +322,20 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro private void setBatteryChartViewInner(final BatteryChartViewV2 batteryChartView) { mBatteryChartView = batteryChartView; mBatteryChartView.setOnSelectListener(this); forceRefreshUi(); } private void forceRefreshUi() { final int refreshIndex = mTrapezoidIndex == BatteryChartViewV2.SELECTED_INDEX_INVALID ? BatteryChartViewV2.SELECTED_INDEX_ALL : mTrapezoidIndex; if (mBatteryChartView != null) { mBatteryChartView.setLevels(mBatteryHistoryLevels); mBatteryChartView.setSelectedIndex(refreshIndex); setTimestampLabel(); } refreshUi(refreshIndex, /*isForce=*/ true); refreshUi(); } @VisibleForTesting boolean refreshUi(int trapezoidIndex, boolean isForce) { boolean refreshUi() { // Invalid refresh condition. if (mBatteryIndexedMap == null || mBatteryChartView == null || (mTrapezoidIndex == trapezoidIndex && !isForce)) { if (mBatteryIndexedMap == null || mBatteryChartView == null) { return false; } Log.d(TAG, String.format("refreshUi: index=%d size=%d isForce:%b", trapezoidIndex, mBatteryIndexedMap.size(), isForce)); if (mViewModel != null) { mViewModel.setSelectedIndex(mTrapezoidIndex); } mBatteryChartView.setViewModel(mViewModel); mTrapezoidIndex = trapezoidIndex; mBatteryChartView.setSelectedIndex(mTrapezoidIndex); mHandler.post(() -> { final long start = System.currentTimeMillis(); removeAndCacheAllPrefs(); Loading Loading @@ -584,20 +572,6 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro return !contains(packageName, mNotAllowShowEntryPackages); } @VisibleForTesting void setTimestampLabel() { if (mBatteryChartView == null || mBatteryHistoryKeys == null) { return; } final boolean is24HourFormat = DateFormat.is24HourFormat(mContext); final String[] labels = new String[mBatteryHistoryKeys.length]; for (int i = 0; i < mBatteryHistoryKeys.length; i++) { labels[i] = ConvertUtils.utcToLocalTimeHour(mContext, mBatteryHistoryKeys[i], is24HourFormat); } mBatteryChartView.setAxisLabels(labels); } private void addFooterPreferenceIfNeeded(boolean containAppItems) { if (mIsFooterPrefAdded || mFooterPreference == null) { return; Loading @@ -610,6 +584,17 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference)); } private static List<String> generateTimestampTexts( @NonNull long[] timestamps, Context context) { final boolean is24HourFormat = DateFormat.is24HourFormat(context); final List<String> texts = new ArrayList<String>(); for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) { texts.add(ConvertUtils.utcToLocalTimeHour(context, timestamps[index * 2], is24HourFormat)); } return texts; } private static boolean contains(String target, CharSequence[] packageNames) { if (target != null && packageNames != null) { for (CharSequence packageName : packageNames) { Loading Loading @@ -654,7 +639,7 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro getBatteryHistoryKeys(batteryHistoryMap), batteryHistoryMap, /*purgeLowPercentageAndFakeData=*/ true); return batteryIndexedMap.get(BatteryChartViewV2.SELECTED_INDEX_ALL); return batteryIndexedMap.get(BatteryChartViewModel.SELECTED_INDEX_ALL); } /** Used for {@link AppBatteryPreferenceController}. */ Loading Loading @@ -735,7 +720,7 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro // Posts results back to main thread to refresh UI. mHandler.post(() -> { mBatteryIndexedMap = indexedUsageMap; forceRefreshUi(); refreshUi(); }); } } Loading src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * 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 BatteryChartViewV2} */ 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; private final List<Integer> mLevels; private final List<String> mTexts; private int mSelectedIndex; BatteryChartViewModel( @NonNull List<Integer> levels, @NonNull List<String> texts, int selectedIndex) { Preconditions.checkArgument( levels.size() == texts.size() && levels.size() >= MIN_LEVELS_DATA_SIZE && selectedIndex >= SELECTED_INDEX_ALL && selectedIndex < levels.size(), String.format(Locale.getDefault(), "Invalid BatteryChartViewModel" + " levels.size: %d\ntexts.size: %d\nselectedIndex: %d.", levels.size(), texts.size(), selectedIndex)); mLevels = levels; mTexts = texts; mSelectedIndex = selectedIndex; } public int size() { return mLevels.size(); } public List<Integer> levels() { return mLevels; } public List<String> texts() { return mTexts; } public int selectedIndex() { return mSelectedIndex; } public void setSelectedIndex(int index) { mSelectedIndex = index; } @Override public int hashCode() { return Objects.hash(mLevels, mTexts, mSelectedIndex); } @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) && mSelectedIndex == batteryChartViewModel.mSelectedIndex; } @Override public String toString() { return String.format(Locale.getDefault(), "levels: %s\ntexts: %s\nselectedIndex: %d", Objects.toString(mLevels), Objects.toString(mTexts), mSelectedIndex); } } src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java +69 −78 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import static com.android.settings.Utils.formatPercentage; import static java.lang.Math.round; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; Loading @@ -38,6 +37,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; Loading @@ -61,16 +61,14 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli 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 float mTrapezoidVOffset; Loading @@ -79,9 +77,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private String[] mPercentages = getPercentages(); @VisibleForTesting int mHoveredIndex = SELECTED_INDEX_INVALID; @VisibleForTesting int mSelectedIndex = SELECTED_INDEX_INVALID; int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; @VisibleForTesting String[] mAxisLabels; Loading @@ -103,7 +99,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli @VisibleForTesting final Runnable mUpdateClickableStateRun = () -> updateClickableState(); private int[] mLevels; private Paint mTextPaint; private Paint mDividerPaint; private Paint mTrapezoidPaint; Loading @@ -126,44 +121,26 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli initializeColors(context); // Registers the click event listener. setOnClickListener(this); setSelectedIndex(SELECTED_INDEX_ALL); setClickable(false); requestLayout(); } /** Sets all levels value to draw the trapezoid shape */ public void setLevels(int[] levels) { Log.d(TAG, "setLevels() " + (levels == null ? "null" : levels.length)); // At least 2 levels to draw a trapezoid. if (levels == null || levels.length < 2) { mLevels = null; /** Sets the data model of this view. */ public void setViewModel(BatteryChartViewModel viewModel) { if (viewModel == null) { mViewModel = null; invalidate(); return; } mLevels = levels; // Initialize trapezoid slots. mTrapezoidSlots = new TrapezoidSlot[mLevels.length - 1]; for (int index = 0; index < mTrapezoidSlots.length; index++) { mTrapezoidSlots[index] = new TrapezoidSlot(); } Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.", viewModel.size(), viewModel.selectedIndex())); mViewModel = viewModel; setClickable(false); invalidate(); // 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; invalidate(); } initializeTrapezoidSlots(viewModel.size() - 1); initializeAxisLabels(viewModel.texts()); setClickable(hasNonZeroTrapezoid(viewModel.levels())); requestLayout(); } /** Sets the callback to monitor the selected group index. */ Loading @@ -184,26 +161,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli requestLayout(); } /** * Sets the X-axis labels list for each level. This class will choose some labels among the * input list to show. * * @param labels The length of this parameter should be the same as the length of * {@code levels}. */ public void setAxisLabels(@NonNull String[] labels) { if (mAxisLabels == null) { mAxisLabels = new String[DEFAULT_AXIS_LABEL_COUNT]; } // Current logic is always showing {@code AXIS_LABEL_GAPS_COUNT} labels. // TODO: Support different count of labels for different levels sizes. final int step = (labels.length - 1) / AXIS_LABEL_GAPS_COUNT; for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT; index++) { mAxisLabels[index] = labels[index * step]; } requestLayout(); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Loading Loading @@ -240,7 +197,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli // Before mLevels initialized, the count of trapezoids is unknown. Only draws the // horizontal percentages and dividers. drawHorizontalDividers(canvas); if (mLevels == null) { if (mViewModel == null) { return; } drawVerticalDividers(canvas); Loading Loading @@ -282,7 +239,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli public void onHoverChanged(boolean hovered) { super.onHoverChanged(hovered); if (!hovered) { mHoveredIndex = SELECTED_INDEX_INVALID; // reset mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset invalidate(); } } Loading @@ -295,13 +252,15 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli } final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX); // Ignores the click event if the level is zero. if (trapezoidIndex == SELECTED_INDEX_INVALID if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID || !isValidToDraw(trapezoidIndex)) { return; } if (mOnSelectListener != null) { // Selects all if users click the same trapezoid item two times. mOnSelectListener.onSelect( trapezoidIndex == mSelectedIndex ? SELECTED_INDEX_ALL : trapezoidIndex); trapezoidIndex == mViewModel.selectedIndex() ? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex); } view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK); } Loading Loading @@ -350,8 +309,8 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli 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(); } Loading @@ -366,6 +325,28 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli super.setClickable(clickable); } private void initializeTrapezoidSlots(int count) { mTrapezoidSlots = new TrapezoidSlot[count]; for (int index = 0; index < mTrapezoidSlots.length; index++) { mTrapezoidSlots[index] = new TrapezoidSlot(); } } /** * Initializes the displayed X-axis labels list selected from the model all texts list. */ private void initializeAxisLabels(@NonNull List<String> allTexts) { if (mAxisLabels == null) { mAxisLabels = new String[DEFAULT_AXIS_LABEL_COUNT]; } // Current logic is always showing {@code AXIS_LABEL_GAPS_COUNT} labels. // TODO: Support different count of labels for different levels sizes. final int step = (allTexts.size() - 1) / AXIS_LABEL_GAPS_COUNT; for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT; index++) { mAxisLabels[index] = allTexts.get(index * step); } } private void initializeColors(Context context) { setBackgroundColor(Color.TRANSPARENT); mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context); Loading Loading @@ -498,7 +479,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private void drawTrapezoids(Canvas canvas) { // Ignores invalid trapezoid data. if (mLevels == null) { if (mViewModel == null) { return; } final float trapezoidBottom = Loading @@ -519,17 +500,17 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli continue; } // Configures the trapezoid paint color. final int trapezoidColor = !mIsSlotsClickabled ? mTrapezoidColor : mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index || mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL) ? mTrapezoidSolidColor : mTrapezoidColor; final boolean isHoverState = mIsSlotsClickabled && mHoveredIndex == index && isValidToDraw(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 - mViewModel.levels().get(index) * unitHeight); final float rightTop = round( trapezoidBottom - mViewModel.levels().get(index + 1) * unitHeight); trapezoidPath.reset(); trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom); trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop); Loading Loading @@ -568,15 +549,25 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli return index; } } return SELECTED_INDEX_INVALID; return BatteryChartViewModel.SELECTED_INDEX_INVALID; } private boolean isValidToDraw(int trapezoidIndex) { return mLevels != null return mViewModel != null && trapezoidIndex >= 0 && trapezoidIndex < mLevels.length - 1 && mLevels[trapezoidIndex] != 0 && mLevels[trapezoidIndex + 1] != 0; && trapezoidIndex < mViewModel.size() - 1 && mViewModel.levels().get(trapezoidIndex) != 0 && mViewModel.levels().get(trapezoidIndex + 1) != 0; } private static boolean hasNonZeroTrapezoid(List<Integer> levels) { // Sets the chart is clickable if there is at least one valid item in it. for (int index = 0; index < levels.size() - 1; index++) { if (levels.get(index) != 0 && levels.get(index + 1) != 0) { return true; } } return false; } private static String[] getPercentages() { Loading tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java +32 −80 File changed.Preview size limit exceeded, changes collapsed. Show changes tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java +13 −7 Original line number Diff line number Diff line Loading @@ -42,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) Loading Loading @@ -100,13 +101,15 @@ public final class BatteryChartViewV2Test { @Test public void onClick_invokesCallback() { mBatteryChartView.setLevels(new int[] {90, 80, 70, 60}); final int originalSelectedIndex = 2; mBatteryChartView.setViewModel( new BatteryChartViewModel(List.of(90, 80, 70, 60), List.of("", "", "", ""), originalSelectedIndex)); for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) { mBatteryChartView.mTrapezoidSlots[i] = new BatteryChartViewV2.TrapezoidSlot(); mBatteryChartView.mTrapezoidSlots[i].mLeft = i; mBatteryChartView.mTrapezoidSlots[i].mRight = i + 0.5f; } mBatteryChartView.mSelectedIndex = 2; final int[] selectedIndex = new int[1]; mBatteryChartView.setOnSelectListener( trapezoidIndex -> { Loading @@ -123,7 +126,7 @@ public final class BatteryChartViewV2Test { mBatteryChartView.mTouchUpEventX = 2; selectedIndex[0] = Integer.MIN_VALUE; mBatteryChartView.onClick(mMockView); assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewV2.SELECTED_INDEX_ALL); assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewModel.SELECTED_INDEX_ALL); } @Test Loading Loading @@ -178,11 +181,14 @@ public final class BatteryChartViewV2Test { @Test public void clickable_restoreFromNonClickableState() { final int[] levels = new int[13]; for (int index = 0; index < levels.length; index++) { levels[index] = index + 1; } mBatteryChartView.setLevels(levels); 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.setViewModel(new BatteryChartViewModel( levels, texts, BatteryChartViewModel.SELECTED_INDEX_ALL)); mBatteryChartView.setClickableForce(true); when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext)) .thenReturn(true); Loading Loading
src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java +37 −52 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; Loading @@ -53,7 +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; Loading Loading @@ -98,13 +98,13 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro @VisibleForTesting boolean mIsExpanded = false; @VisibleForTesting int[] mBatteryHistoryLevels; @VisibleForTesting long[] mBatteryHistoryKeys; @VisibleForTesting int mTrapezoidIndex = BatteryChartViewV2.SELECTED_INDEX_INVALID; BatteryChartViewModel mViewModel; @VisibleForTesting int mTrapezoidIndex = BatteryChartViewModel.SELECTED_INDEX_ALL; private boolean mIs24HourFormat = false; private boolean mIs24HourFormat; private boolean mIsFooterPrefAdded = false; private PreferenceScreen mPreferenceScreen; private FooterPreference mFooterPreference; Loading Loading @@ -252,10 +252,11 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro @Override public void onSelect(int trapezoidIndex) { Log.d(TAG, "onChartSelect:" + trapezoidIndex); refreshUi(trapezoidIndex, /*isForce=*/ false); mTrapezoidIndex = trapezoidIndex; refreshUi(); mMetricsFeatureProvider.action( mPrefContext, trapezoidIndex == BatteryChartViewV2.SELECTED_INDEX_ALL trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT); } Loading @@ -276,18 +277,19 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { mBatteryIndexedMap = null; mBatteryHistoryKeys = null; mBatteryHistoryLevels = null; mViewModel = null; addFooterPreferenceIfNeeded(false); return; } mBatteryHistoryKeys = getBatteryHistoryKeys(batteryHistoryMap); mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE]; List<Integer> levels = new ArrayList<Integer>(); 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)); levels.add(0); continue; } // Averages the battery level in each time slot to avoid corner conditions. Loading @@ -295,16 +297,17 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro for (BatteryHistEntry entry : entryMap.values()) { batteryLevelCounter += entry.mBatteryLevel; } mBatteryHistoryLevels[index] = Math.round(batteryLevelCounter / entryMap.size()); levels.add(Math.round(batteryLevelCounter / entryMap.size())); } forceRefreshUi(); final List<String> texts = generateTimestampTexts(mBatteryHistoryKeys, mContext); mViewModel = new BatteryChartViewModel(levels, texts, mTrapezoidIndex); refreshUi(); Log.d(TAG, String.format( "setBatteryHistoryMap() size=%d key=%s\nlevels=%s", "setBatteryHistoryMap() size=%d key=%s\nview model=%s", batteryHistoryMap.size(), ConvertUtils.utcToLocalTime(mPrefContext, mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1]), Arrays.toString(mBatteryHistoryLevels))); mViewModel)); // Loads item icon and label in the background. new LoadAllItemsInfoTask(batteryHistoryMap).execute(); Loading @@ -319,35 +322,20 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro private void setBatteryChartViewInner(final BatteryChartViewV2 batteryChartView) { mBatteryChartView = batteryChartView; mBatteryChartView.setOnSelectListener(this); forceRefreshUi(); } private void forceRefreshUi() { final int refreshIndex = mTrapezoidIndex == BatteryChartViewV2.SELECTED_INDEX_INVALID ? BatteryChartViewV2.SELECTED_INDEX_ALL : mTrapezoidIndex; if (mBatteryChartView != null) { mBatteryChartView.setLevels(mBatteryHistoryLevels); mBatteryChartView.setSelectedIndex(refreshIndex); setTimestampLabel(); } refreshUi(refreshIndex, /*isForce=*/ true); refreshUi(); } @VisibleForTesting boolean refreshUi(int trapezoidIndex, boolean isForce) { boolean refreshUi() { // Invalid refresh condition. if (mBatteryIndexedMap == null || mBatteryChartView == null || (mTrapezoidIndex == trapezoidIndex && !isForce)) { if (mBatteryIndexedMap == null || mBatteryChartView == null) { return false; } Log.d(TAG, String.format("refreshUi: index=%d size=%d isForce:%b", trapezoidIndex, mBatteryIndexedMap.size(), isForce)); if (mViewModel != null) { mViewModel.setSelectedIndex(mTrapezoidIndex); } mBatteryChartView.setViewModel(mViewModel); mTrapezoidIndex = trapezoidIndex; mBatteryChartView.setSelectedIndex(mTrapezoidIndex); mHandler.post(() -> { final long start = System.currentTimeMillis(); removeAndCacheAllPrefs(); Loading Loading @@ -584,20 +572,6 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro return !contains(packageName, mNotAllowShowEntryPackages); } @VisibleForTesting void setTimestampLabel() { if (mBatteryChartView == null || mBatteryHistoryKeys == null) { return; } final boolean is24HourFormat = DateFormat.is24HourFormat(mContext); final String[] labels = new String[mBatteryHistoryKeys.length]; for (int i = 0; i < mBatteryHistoryKeys.length; i++) { labels[i] = ConvertUtils.utcToLocalTimeHour(mContext, mBatteryHistoryKeys[i], is24HourFormat); } mBatteryChartView.setAxisLabels(labels); } private void addFooterPreferenceIfNeeded(boolean containAppItems) { if (mIsFooterPrefAdded || mFooterPreference == null) { return; Loading @@ -610,6 +584,17 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference)); } private static List<String> generateTimestampTexts( @NonNull long[] timestamps, Context context) { final boolean is24HourFormat = DateFormat.is24HourFormat(context); final List<String> texts = new ArrayList<String>(); for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) { texts.add(ConvertUtils.utcToLocalTimeHour(context, timestamps[index * 2], is24HourFormat)); } return texts; } private static boolean contains(String target, CharSequence[] packageNames) { if (target != null && packageNames != null) { for (CharSequence packageName : packageNames) { Loading Loading @@ -654,7 +639,7 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro getBatteryHistoryKeys(batteryHistoryMap), batteryHistoryMap, /*purgeLowPercentageAndFakeData=*/ true); return batteryIndexedMap.get(BatteryChartViewV2.SELECTED_INDEX_ALL); return batteryIndexedMap.get(BatteryChartViewModel.SELECTED_INDEX_ALL); } /** Used for {@link AppBatteryPreferenceController}. */ Loading Loading @@ -735,7 +720,7 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro // Posts results back to main thread to refresh UI. mHandler.post(() -> { mBatteryIndexedMap = indexedUsageMap; forceRefreshUi(); refreshUi(); }); } } Loading
src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * 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 BatteryChartViewV2} */ 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; private final List<Integer> mLevels; private final List<String> mTexts; private int mSelectedIndex; BatteryChartViewModel( @NonNull List<Integer> levels, @NonNull List<String> texts, int selectedIndex) { Preconditions.checkArgument( levels.size() == texts.size() && levels.size() >= MIN_LEVELS_DATA_SIZE && selectedIndex >= SELECTED_INDEX_ALL && selectedIndex < levels.size(), String.format(Locale.getDefault(), "Invalid BatteryChartViewModel" + " levels.size: %d\ntexts.size: %d\nselectedIndex: %d.", levels.size(), texts.size(), selectedIndex)); mLevels = levels; mTexts = texts; mSelectedIndex = selectedIndex; } public int size() { return mLevels.size(); } public List<Integer> levels() { return mLevels; } public List<String> texts() { return mTexts; } public int selectedIndex() { return mSelectedIndex; } public void setSelectedIndex(int index) { mSelectedIndex = index; } @Override public int hashCode() { return Objects.hash(mLevels, mTexts, mSelectedIndex); } @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) && mSelectedIndex == batteryChartViewModel.mSelectedIndex; } @Override public String toString() { return String.format(Locale.getDefault(), "levels: %s\ntexts: %s\nselectedIndex: %d", Objects.toString(mLevels), Objects.toString(mTexts), mSelectedIndex); } }
src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java +69 −78 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import static com.android.settings.Utils.formatPercentage; import static java.lang.Math.round; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; Loading @@ -38,6 +37,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; Loading @@ -61,16 +61,14 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli 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 float mTrapezoidVOffset; Loading @@ -79,9 +77,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private String[] mPercentages = getPercentages(); @VisibleForTesting int mHoveredIndex = SELECTED_INDEX_INVALID; @VisibleForTesting int mSelectedIndex = SELECTED_INDEX_INVALID; int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; @VisibleForTesting String[] mAxisLabels; Loading @@ -103,7 +99,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli @VisibleForTesting final Runnable mUpdateClickableStateRun = () -> updateClickableState(); private int[] mLevels; private Paint mTextPaint; private Paint mDividerPaint; private Paint mTrapezoidPaint; Loading @@ -126,44 +121,26 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli initializeColors(context); // Registers the click event listener. setOnClickListener(this); setSelectedIndex(SELECTED_INDEX_ALL); setClickable(false); requestLayout(); } /** Sets all levels value to draw the trapezoid shape */ public void setLevels(int[] levels) { Log.d(TAG, "setLevels() " + (levels == null ? "null" : levels.length)); // At least 2 levels to draw a trapezoid. if (levels == null || levels.length < 2) { mLevels = null; /** Sets the data model of this view. */ public void setViewModel(BatteryChartViewModel viewModel) { if (viewModel == null) { mViewModel = null; invalidate(); return; } mLevels = levels; // Initialize trapezoid slots. mTrapezoidSlots = new TrapezoidSlot[mLevels.length - 1]; for (int index = 0; index < mTrapezoidSlots.length; index++) { mTrapezoidSlots[index] = new TrapezoidSlot(); } Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.", viewModel.size(), viewModel.selectedIndex())); mViewModel = viewModel; setClickable(false); invalidate(); // 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; invalidate(); } initializeTrapezoidSlots(viewModel.size() - 1); initializeAxisLabels(viewModel.texts()); setClickable(hasNonZeroTrapezoid(viewModel.levels())); requestLayout(); } /** Sets the callback to monitor the selected group index. */ Loading @@ -184,26 +161,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli requestLayout(); } /** * Sets the X-axis labels list for each level. This class will choose some labels among the * input list to show. * * @param labels The length of this parameter should be the same as the length of * {@code levels}. */ public void setAxisLabels(@NonNull String[] labels) { if (mAxisLabels == null) { mAxisLabels = new String[DEFAULT_AXIS_LABEL_COUNT]; } // Current logic is always showing {@code AXIS_LABEL_GAPS_COUNT} labels. // TODO: Support different count of labels for different levels sizes. final int step = (labels.length - 1) / AXIS_LABEL_GAPS_COUNT; for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT; index++) { mAxisLabels[index] = labels[index * step]; } requestLayout(); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Loading Loading @@ -240,7 +197,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli // Before mLevels initialized, the count of trapezoids is unknown. Only draws the // horizontal percentages and dividers. drawHorizontalDividers(canvas); if (mLevels == null) { if (mViewModel == null) { return; } drawVerticalDividers(canvas); Loading Loading @@ -282,7 +239,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli public void onHoverChanged(boolean hovered) { super.onHoverChanged(hovered); if (!hovered) { mHoveredIndex = SELECTED_INDEX_INVALID; // reset mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset invalidate(); } } Loading @@ -295,13 +252,15 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli } final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX); // Ignores the click event if the level is zero. if (trapezoidIndex == SELECTED_INDEX_INVALID if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID || !isValidToDraw(trapezoidIndex)) { return; } if (mOnSelectListener != null) { // Selects all if users click the same trapezoid item two times. mOnSelectListener.onSelect( trapezoidIndex == mSelectedIndex ? SELECTED_INDEX_ALL : trapezoidIndex); trapezoidIndex == mViewModel.selectedIndex() ? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex); } view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK); } Loading Loading @@ -350,8 +309,8 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli 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(); } Loading @@ -366,6 +325,28 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli super.setClickable(clickable); } private void initializeTrapezoidSlots(int count) { mTrapezoidSlots = new TrapezoidSlot[count]; for (int index = 0; index < mTrapezoidSlots.length; index++) { mTrapezoidSlots[index] = new TrapezoidSlot(); } } /** * Initializes the displayed X-axis labels list selected from the model all texts list. */ private void initializeAxisLabels(@NonNull List<String> allTexts) { if (mAxisLabels == null) { mAxisLabels = new String[DEFAULT_AXIS_LABEL_COUNT]; } // Current logic is always showing {@code AXIS_LABEL_GAPS_COUNT} labels. // TODO: Support different count of labels for different levels sizes. final int step = (allTexts.size() - 1) / AXIS_LABEL_GAPS_COUNT; for (int index = 0; index < DEFAULT_AXIS_LABEL_COUNT; index++) { mAxisLabels[index] = allTexts.get(index * step); } } private void initializeColors(Context context) { setBackgroundColor(Color.TRANSPARENT); mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context); Loading Loading @@ -498,7 +479,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private void drawTrapezoids(Canvas canvas) { // Ignores invalid trapezoid data. if (mLevels == null) { if (mViewModel == null) { return; } final float trapezoidBottom = Loading @@ -519,17 +500,17 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli continue; } // Configures the trapezoid paint color. final int trapezoidColor = !mIsSlotsClickabled ? mTrapezoidColor : mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index || mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL) ? mTrapezoidSolidColor : mTrapezoidColor; final boolean isHoverState = mIsSlotsClickabled && mHoveredIndex == index && isValidToDraw(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 - mViewModel.levels().get(index) * unitHeight); final float rightTop = round( trapezoidBottom - mViewModel.levels().get(index + 1) * unitHeight); trapezoidPath.reset(); trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom); trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop); Loading Loading @@ -568,15 +549,25 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli return index; } } return SELECTED_INDEX_INVALID; return BatteryChartViewModel.SELECTED_INDEX_INVALID; } private boolean isValidToDraw(int trapezoidIndex) { return mLevels != null return mViewModel != null && trapezoidIndex >= 0 && trapezoidIndex < mLevels.length - 1 && mLevels[trapezoidIndex] != 0 && mLevels[trapezoidIndex + 1] != 0; && trapezoidIndex < mViewModel.size() - 1 && mViewModel.levels().get(trapezoidIndex) != 0 && mViewModel.levels().get(trapezoidIndex + 1) != 0; } private static boolean hasNonZeroTrapezoid(List<Integer> levels) { // Sets the chart is clickable if there is at least one valid item in it. for (int index = 0; index < levels.size() - 1; index++) { if (levels.get(index) != 0 && levels.get(index + 1) != 0) { return true; } } return false; } private static String[] getPercentages() { Loading
tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java +32 −80 File changed.Preview size limit exceeded, changes collapsed. Show changes
tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java +13 −7 Original line number Diff line number Diff line Loading @@ -42,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) Loading Loading @@ -100,13 +101,15 @@ public final class BatteryChartViewV2Test { @Test public void onClick_invokesCallback() { mBatteryChartView.setLevels(new int[] {90, 80, 70, 60}); final int originalSelectedIndex = 2; mBatteryChartView.setViewModel( new BatteryChartViewModel(List.of(90, 80, 70, 60), List.of("", "", "", ""), originalSelectedIndex)); for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) { mBatteryChartView.mTrapezoidSlots[i] = new BatteryChartViewV2.TrapezoidSlot(); mBatteryChartView.mTrapezoidSlots[i].mLeft = i; mBatteryChartView.mTrapezoidSlots[i].mRight = i + 0.5f; } mBatteryChartView.mSelectedIndex = 2; final int[] selectedIndex = new int[1]; mBatteryChartView.setOnSelectListener( trapezoidIndex -> { Loading @@ -123,7 +126,7 @@ public final class BatteryChartViewV2Test { mBatteryChartView.mTouchUpEventX = 2; selectedIndex[0] = Integer.MIN_VALUE; mBatteryChartView.onClick(mMockView); assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewV2.SELECTED_INDEX_ALL); assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewModel.SELECTED_INDEX_ALL); } @Test Loading Loading @@ -178,11 +181,14 @@ public final class BatteryChartViewV2Test { @Test public void clickable_restoreFromNonClickableState() { final int[] levels = new int[13]; for (int index = 0; index < levels.length; index++) { levels[index] = index + 1; } mBatteryChartView.setLevels(levels); 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.setViewModel(new BatteryChartViewModel( levels, texts, BatteryChartViewModel.SELECTED_INDEX_ALL)); mBatteryChartView.setClickableForce(true); when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext)) .thenReturn(true); Loading