1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.fuelgauge.batteryusage;
18 
19 import androidx.annotation.NonNull;
20 import androidx.core.util.Preconditions;
21 
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Objects;
26 
27 /** The view model of {@code BatteryChartView} */
28 class BatteryChartViewModel {
29     private static final String TAG = "BatteryChartViewModel";
30 
31     public static final int SELECTED_INDEX_ALL = -1;
32     public static final int SELECTED_INDEX_INVALID = -2;
33 
34     // We need at least 2 levels to draw a trapezoid.
35     private static final int MIN_LEVELS_DATA_SIZE = 2;
36 
37     enum AxisLabelPosition {
38         BETWEEN_TRAPEZOIDS,
39         CENTER_OF_TRAPEZOIDS,
40     }
41 
42     interface LabelTextGenerator {
43         /** Generates the label text. The text may be abbreviated to save space. */
generateText(List<Long> timestamps, int index)44         String generateText(List<Long> timestamps, int index);
45 
46         /** Generates the full text for slot information. */
generateFullText(List<Long> timestamps, int index)47         String generateFullText(List<Long> timestamps, int index);
48 
49         /** Generates the full text for accessibility. */
generateContentDescription(List<Long> timestamps, int index)50         String generateContentDescription(List<Long> timestamps, int index);
51 
52         /** Generates the battery level text of a slot for accessibility.*/
generateSlotBatteryLevelText(List<Integer> levels, int index)53         String generateSlotBatteryLevelText(List<Integer> levels, int index);
54     }
55 
56     private final List<Integer> mLevels;
57     private final List<Long> mTimestamps;
58     private final AxisLabelPosition mAxisLabelPosition;
59     private final LabelTextGenerator mLabelTextGenerator;
60     private final String[] mTexts;
61     private final String[] mFullTexts;
62     private final String[] mContentDescription;
63     private final String[] mBatteryLevelTexts;
64 
65     private int mSelectedIndex = SELECTED_INDEX_ALL;
66     private int mHighlightSlotIndex = SELECTED_INDEX_INVALID;
67 
BatteryChartViewModel( @onNull List<Integer> levels, @NonNull List<Long> timestamps, @NonNull AxisLabelPosition axisLabelPosition, @NonNull LabelTextGenerator labelTextGenerator)68     BatteryChartViewModel(
69             @NonNull List<Integer> levels,
70             @NonNull List<Long> timestamps,
71             @NonNull AxisLabelPosition axisLabelPosition,
72             @NonNull LabelTextGenerator labelTextGenerator) {
73         Preconditions.checkArgument(
74                 levels.size() == timestamps.size() && levels.size() >= MIN_LEVELS_DATA_SIZE,
75                 String.format(
76                         Locale.ENGLISH,
77                         "Invalid BatteryChartViewModel levels.size: %d, timestamps.size: %d.",
78                         levels.size(),
79                         timestamps.size()));
80         mLevels = levels;
81         mTimestamps = timestamps;
82         mAxisLabelPosition = axisLabelPosition;
83         mLabelTextGenerator = labelTextGenerator;
84         mTexts = new String[size()];
85         mFullTexts = new String[size()];
86         mContentDescription = new String[size()];
87         // Last one for SELECTED_INDEX_ALL
88         mBatteryLevelTexts = new String[size() + 1];
89     }
90 
size()91     public int size() {
92         return mLevels.size();
93     }
94 
getLevel(int index)95     public Integer getLevel(int index) {
96         return mLevels.get(index);
97     }
98 
getText(int index)99     public String getText(int index) {
100         if (mTexts[index] == null) {
101             mTexts[index] = mLabelTextGenerator.generateText(mTimestamps, index);
102         }
103         return mTexts[index];
104     }
105 
getFullText(int index)106     public String getFullText(int index) {
107         if (mFullTexts[index] == null) {
108             mFullTexts[index] = mLabelTextGenerator.generateFullText(mTimestamps, index);
109         }
110         return mFullTexts[index];
111     }
112 
getContentDescription(int index)113     public String getContentDescription(int index) {
114         if (mContentDescription[index] == null) {
115             mContentDescription[index] =
116                     mLabelTextGenerator.generateContentDescription(mTimestamps, index);
117         }
118         return mContentDescription[index];
119     }
120 
getSlotBatteryLevelText(int index)121     public String getSlotBatteryLevelText(int index) {
122         final int textIndex = index != SELECTED_INDEX_ALL ? index : size();
123         if (mBatteryLevelTexts[textIndex] == null) {
124             mBatteryLevelTexts[textIndex] =
125                     mLabelTextGenerator.generateSlotBatteryLevelText(mLevels, index);
126         }
127         return mBatteryLevelTexts[textIndex];
128     }
129 
axisLabelPosition()130     public AxisLabelPosition axisLabelPosition() {
131         return mAxisLabelPosition;
132     }
133 
selectedIndex()134     public int selectedIndex() {
135         return mSelectedIndex;
136     }
137 
setSelectedIndex(int index)138     public void setSelectedIndex(int index) {
139         mSelectedIndex = index;
140     }
141 
getHighlightSlotIndex()142     public int getHighlightSlotIndex() {
143         return mHighlightSlotIndex;
144     }
145 
setHighlightSlotIndex(int index)146     public void setHighlightSlotIndex(int index) {
147         mHighlightSlotIndex = index;
148     }
149 
150     @Override
hashCode()151     public int hashCode() {
152         return Objects.hash(mLevels, mTimestamps, mSelectedIndex, mAxisLabelPosition);
153     }
154 
155     @Override
equals(Object other)156     public boolean equals(Object other) {
157         if (this == other) {
158             return true;
159         } else if (!(other instanceof BatteryChartViewModel)) {
160             return false;
161         }
162         final BatteryChartViewModel batteryChartViewModel = (BatteryChartViewModel) other;
163         return Objects.equals(mLevels, batteryChartViewModel.mLevels)
164                 && Objects.equals(mTimestamps, batteryChartViewModel.mTimestamps)
165                 && mAxisLabelPosition == batteryChartViewModel.mAxisLabelPosition
166                 && mSelectedIndex == batteryChartViewModel.mSelectedIndex;
167     }
168 
169     @Override
toString()170     public String toString() {
171         // Generate all the texts and full texts.
172         for (int i = 0; i < size(); i++) {
173             getText(i);
174             getFullText(i);
175         }
176 
177         return new StringBuilder()
178                 .append("levels: " + Objects.toString(mLevels))
179                 .append(", timestamps: " + Objects.toString(mTimestamps))
180                 .append(", texts: " + Arrays.toString(mTexts))
181                 .append(", fullTexts: " + Arrays.toString(mFullTexts))
182                 .append(", axisLabelPosition: " + mAxisLabelPosition)
183                 .append(", selectedIndex: " + mSelectedIndex)
184                 .toString();
185     }
186 }
187