2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.settings.dashboard;
18 import android.annotation.IntDef;
19 import android.graphics.drawable.Icon;
20 import android.service.settings.suggestions.Suggestion;
21 import android.support.annotation.VisibleForTesting;
22 import android.support.v7.util.DiffUtil;
23 import android.text.TextUtils;
25 import com.android.settings.R;
26 import com.android.settings.dashboard.conditional.Condition;
27 import com.android.settingslib.drawer.DashboardCategory;
28 import com.android.settingslib.drawer.Tile;
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Objects;
37 * Description about data list used in the DashboardAdapter. In the data list each item can be
38 * Condition, suggestion or category tile.
40 * ItemsData has inner class Item, which represents the Item in data list.
42 public class DashboardData {
43 public static final int POSITION_NOT_FOUND = -1;
44 public static final int MAX_SUGGESTION_COUNT = 2;
46 // stable id for different type of items.
48 static final int STABLE_ID_SUGGESTION_CONTAINER = 0;
49 static final int STABLE_ID_SUGGESTION_CONDITION_DIVIDER = 1;
51 static final int STABLE_ID_CONDITION_HEADER = 2;
53 static final int STABLE_ID_CONDITION_FOOTER = 3;
55 static final int STABLE_ID_CONDITION_CONTAINER = 4;
57 private final List<Item> mItems;
58 private final DashboardCategory mCategory;
59 private final List<Condition> mConditions;
60 private final List<Suggestion> mSuggestions;
61 private final boolean mConditionExpanded;
63 private DashboardData(Builder builder) {
64 mCategory = builder.mCategory;
65 mConditions = builder.mConditions;
66 mSuggestions = builder.mSuggestions;
67 mConditionExpanded = builder.mConditionExpanded;
68 mItems = new ArrayList<>();
73 public int getItemIdByPosition(int position) {
74 return mItems.get(position).id;
77 public int getItemTypeByPosition(int position) {
78 return mItems.get(position).type;
81 public Object getItemEntityByPosition(int position) {
82 return mItems.get(position).entity;
85 public List<Item> getItemList() {
93 public Object getItemEntityById(long id) {
94 for (final Item item : mItems) {
102 public DashboardCategory getCategory() {
106 public List<Condition> getConditions() {
110 public List<Suggestion> getSuggestions() {
114 public boolean hasSuggestion() {
115 return sizeOf(mSuggestions) > 0;
118 public boolean isConditionExpanded() {
119 return mConditionExpanded;
123 * Find the position of the object in mItems list, using the equals method to compare
125 * @param entity the object that need to be found in list
126 * @return position of the object, return POSITION_NOT_FOUND if object isn't in the list
128 public int getPositionByEntity(Object entity) {
129 if (entity == null) return POSITION_NOT_FOUND;
131 final int size = mItems.size();
132 for (int i = 0; i < size; i++) {
133 final Object item = mItems.get(i).entity;
134 if (entity.equals(item)) {
139 return POSITION_NOT_FOUND;
143 * Find the position of the Tile object.
145 * First, try to find the exact identical instance of the tile object, if not found,
146 * then try to find a tile has the same title.
148 * @param tile tile that need to be found
149 * @return position of the object, return INDEX_NOT_FOUND if object isn't in the list
151 public int getPositionByTile(Tile tile) {
152 final int size = mItems.size();
153 for (int i = 0; i < size; i++) {
154 final Object entity = mItems.get(i).entity;
155 if (entity == tile) {
157 } else if (entity instanceof Tile && tile.title.equals(((Tile) entity).title)) {
162 return POSITION_NOT_FOUND;
166 * Add item into list when {@paramref add} is true.
168 * @param item maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
169 * @param type type of the item, and value is the layout id
170 * @param stableId The stable id for this item
171 * @param add flag about whether to add item into list
173 private void addToItemList(Object item, int type, int stableId, boolean add) {
175 mItems.add(new Item(item, type, stableId));
180 * Build the mItems list using mConditions, mSuggestions, mCategories data
181 * and mIsShowingAll, mConditionExpanded flag.
183 private void buildItemsData() {
184 final List<Condition> conditions = getConditionsToShow(mConditions);
185 final boolean hasConditions = sizeOf(conditions) > 0;
187 final List<Suggestion> suggestions = getSuggestionsToShow(mSuggestions);
188 final boolean hasSuggestions = sizeOf(suggestions) > 0;
190 /* Suggestion container. This is the card view that contains the list of suggestions.
191 * This will be added whenever the suggestion list is not empty */
192 addToItemList(suggestions, R.layout.suggestion_container,
193 STABLE_ID_SUGGESTION_CONTAINER, hasSuggestions);
195 /* Divider between suggestion and conditions if both are present. */
196 addToItemList(null /* item */, R.layout.horizontal_divider,
197 STABLE_ID_SUGGESTION_CONDITION_DIVIDER, hasSuggestions && hasConditions);
199 /* Condition header. This will be present when there is condition and it is collapsed */
200 addToItemList(new ConditionHeaderData(conditions),
201 R.layout.condition_header,
202 STABLE_ID_CONDITION_HEADER, hasConditions && !mConditionExpanded);
204 /* Condition container. This is the card view that contains the list of conditions.
205 * This will be added whenever the condition list is not empty and expanded */
206 addToItemList(conditions, R.layout.condition_container,
207 STABLE_ID_CONDITION_CONTAINER, hasConditions && mConditionExpanded);
209 /* Condition footer. This will be present when there is condition and it is expanded */
210 addToItemList(null /* item */, R.layout.condition_footer,
211 STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded);
213 if (mCategory != null) {
214 final List<Tile> tiles = mCategory.getTiles();
215 for (int i = 0; i < tiles.size(); i++) {
216 final Tile tile = tiles.get(i);
217 addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),
223 private static int sizeOf(List<?> list) {
224 return list == null ? 0 : list.size();
227 private List<Condition> getConditionsToShow(List<Condition> conditions) {
228 if (conditions == null) {
231 List<Condition> result = new ArrayList<>();
232 final int size = conditions == null ? 0 : conditions.size();
233 for (int i = 0; i < size; i++) {
234 final Condition condition = conditions.get(i);
235 if (condition.shouldShow()) {
236 result.add(condition);
242 private List<Suggestion> getSuggestionsToShow(List<Suggestion> suggestions) {
243 if (suggestions == null) {
246 if (suggestions.size() <= MAX_SUGGESTION_COUNT) {
249 final List<Suggestion> suggestionsToShow = new ArrayList<>(MAX_SUGGESTION_COUNT);
250 for (int i = 0; i < MAX_SUGGESTION_COUNT; i++) {
251 suggestionsToShow.add(suggestions.get(i));
253 return suggestionsToShow;
257 * Builder used to build the ItemsData
259 public static class Builder {
260 private DashboardCategory mCategory;
261 private List<Condition> mConditions;
262 private List<Suggestion> mSuggestions;
263 private boolean mConditionExpanded;
268 public Builder(DashboardData dashboardData) {
269 mCategory = dashboardData.mCategory;
270 mConditions = dashboardData.mConditions;
271 mSuggestions = dashboardData.mSuggestions;
272 mConditionExpanded = dashboardData.mConditionExpanded;
275 public Builder setCategory(DashboardCategory category) {
276 this.mCategory = category;
280 public Builder setConditions(List<Condition> conditions) {
281 this.mConditions = conditions;
285 public Builder setSuggestions(List<Suggestion> suggestions) {
286 this.mSuggestions = suggestions;
290 public Builder setConditionExpanded(boolean expanded) {
291 this.mConditionExpanded = expanded;
295 public DashboardData build() {
296 return new DashboardData(this);
301 * A DiffCallback to calculate the difference between old and new Item
302 * List in DashboardData
304 public static class ItemsDataDiffCallback extends DiffUtil.Callback {
305 final private List<Item> mOldItems;
306 final private List<Item> mNewItems;
308 public ItemsDataDiffCallback(List<Item> oldItems, List<Item> newItems) {
309 mOldItems = oldItems;
310 mNewItems = newItems;
314 public int getOldListSize() {
315 return mOldItems.size();
319 public int getNewListSize() {
320 return mNewItems.size();
324 public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
325 return mOldItems.get(oldItemPosition).id == mNewItems.get(newItemPosition).id;
329 public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
330 return mOldItems.get(oldItemPosition).equals(mNewItems.get(newItemPosition));
336 * An item contains the data needed in the DashboardData.
339 // valid types in field type
340 private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
341 private static final int TYPE_SUGGESTION_CONTAINER =
342 R.layout.suggestion_container;
343 private static final int TYPE_CONDITION_CONTAINER =
344 R.layout.condition_container;
345 private static final int TYPE_CONDITION_HEADER =
346 R.layout.condition_header;
347 private static final int TYPE_CONDITION_FOOTER =
348 R.layout.condition_footer;
349 private static final int TYPE_SUGGESTION_CONDITION_DIVIDER = R.layout.horizontal_divider;
351 @IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONTAINER, TYPE_CONDITION_CONTAINER,
352 TYPE_CONDITION_HEADER, TYPE_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_DIVIDER})
353 @Retention(RetentionPolicy.SOURCE)
354 public @interface ItemTypes {
358 * The main data object in item, usually is a {@link Tile}, {@link Condition}
359 * object. This object can also be null when the
360 * item is an divider line. Please refer to {@link #buildItemsData()} for
361 * detail usage of the Item.
363 public final Object entity;
366 * The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile)
369 public final int type;
372 * Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item.
376 public Item(Object entity, @ItemTypes int type, int id) {
377 this.entity = entity;
383 * Override it to make comparision in the {@link ItemsDataDiffCallback}
385 * @param obj object to compared with
386 * @return true if the same object or has equal value.
389 public boolean equals(Object obj) {
394 if (!(obj instanceof Item)) {
398 final Item targetItem = (Item) obj;
399 if (type != targetItem.type || id != targetItem.id) {
404 case TYPE_DASHBOARD_TILE:
405 final Tile localTile = (Tile) entity;
406 final Tile targetTile = (Tile) targetItem.entity;
408 // Only check title and summary for dashboard tile
409 return TextUtils.equals(localTile.title, targetTile.title)
410 && TextUtils.equals(localTile.summary, targetTile.summary);
411 case TYPE_SUGGESTION_CONTAINER:
412 case TYPE_CONDITION_CONTAINER:
413 // If entity is suggestion and contains remote view, force refresh
414 final List entities = (List) entity;
415 if (!entities.isEmpty()) {
416 Object firstEntity = entities.get(0);
417 if (firstEntity instanceof Tile
418 && ((Tile) firstEntity).remoteViews != null) {
422 // Otherwise Fall through to default
424 return entity == null ? targetItem.entity == null
425 : entity.equals(targetItem.entity);
431 * This class contains the data needed to build the suggestion/condition header. The data can
432 * also be used to check the diff in DiffUtil.Callback
434 public static class ConditionHeaderData {
435 public final List<Icon> conditionIcons;
436 public final CharSequence title;
437 public final int conditionCount;
439 public ConditionHeaderData(List<Condition> conditions) {
440 conditionCount = sizeOf(conditions);
441 title = conditionCount > 0 ? conditions.get(0).getTitle() : null;
442 conditionIcons = new ArrayList<>();
443 for (int i = 0; conditions != null && i < conditions.size(); i++) {
444 final Condition condition = conditions.get(i);
445 conditionIcons.add(condition.getIcon());