OSDN Git Service

Zen Condition text and primary click changes
[android-x86/packages-apps-Settings.git] / src / com / android / settings / dashboard / DashboardData.java
1 /*
2  * Copyright (C) 2016 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 package com.android.settings.dashboard;
17
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;
24
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;
29
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;
35
36 /**
37  * Description about data list used in the DashboardAdapter. In the data list each item can be
38  * Condition, suggestion or category tile.
39  * <p>
40  * ItemsData has inner class Item, which represents the Item in data list.
41  */
42 public class DashboardData {
43     public static final int HEADER_MODE_DEFAULT = 0;
44     public static final int HEADER_MODE_SUGGESTION_EXPANDED = 1;
45     public static final int HEADER_MODE_FULLY_EXPANDED = 2;
46     public static final int HEADER_MODE_COLLAPSED = 3;
47
48     @Retention(RetentionPolicy.SOURCE)
49     @IntDef({HEADER_MODE_DEFAULT, HEADER_MODE_SUGGESTION_EXPANDED, HEADER_MODE_FULLY_EXPANDED,
50             HEADER_MODE_COLLAPSED})
51     public @interface HeaderMode {
52     }
53
54     public static final int POSITION_NOT_FOUND = -1;
55     public static final int DEFAULT_SUGGESTION_COUNT = 2;
56
57     // stable id for different type of items.
58     @VisibleForTesting
59     static final int STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER = 0;
60     @VisibleForTesting
61     static final int STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER = 1;
62     @VisibleForTesting
63     static final int STABLE_ID_SUGGESTION_CONDITION_FOOTER = 2;
64     @VisibleForTesting
65     static final int STABLE_ID_SUGGESTION_CONTAINER = 3;
66     @VisibleForTesting
67     static final int STABLE_ID_CONDITION_CONTAINER = 4;
68
69     private final List<Item> mItems;
70     private final DashboardCategory mCategory;
71     private final List<Condition> mConditions;
72     private final List<Suggestion> mSuggestions;
73     @HeaderMode
74     private final int mSuggestionConditionMode;
75
76     private DashboardData(Builder builder) {
77         mCategory = builder.mCategory;
78         mConditions = builder.mConditions;
79         mSuggestions = builder.mSuggestionsV2;
80         mSuggestionConditionMode = builder.mSuggestionConditionMode;
81
82         mItems = new ArrayList<>();
83
84         buildItemsData();
85     }
86
87     public int getItemIdByPosition(int position) {
88         return mItems.get(position).id;
89     }
90
91     public int getItemTypeByPosition(int position) {
92         return mItems.get(position).type;
93     }
94
95     public Object getItemEntityByPosition(int position) {
96         return mItems.get(position).entity;
97     }
98
99     public List<Item> getItemList() {
100         return mItems;
101     }
102
103     public int size() {
104         return mItems.size();
105     }
106
107     public Object getItemEntityById(long id) {
108         for (final Item item : mItems) {
109             if (item.id == id) {
110                 return item.entity;
111             }
112         }
113         return null;
114     }
115
116     public DashboardCategory getCategory() {
117         return mCategory;
118     }
119
120     public List<Condition> getConditions() {
121         return mConditions;
122     }
123
124     public List<Suggestion> getSuggestions() {
125         return mSuggestions;
126     }
127
128     public int getSuggestionConditionMode() {
129         return mSuggestionConditionMode;
130     }
131
132     /**
133      * Find the position of the object in mItems list, using the equals method to compare
134      *
135      * @param entity the object that need to be found in list
136      * @return position of the object, return POSITION_NOT_FOUND if object isn't in the list
137      */
138     public int getPositionByEntity(Object entity) {
139         if (entity == null) return POSITION_NOT_FOUND;
140
141         final int size = mItems.size();
142         for (int i = 0; i < size; i++) {
143             final Object item = mItems.get(i).entity;
144             if (entity.equals(item)) {
145                 return i;
146             }
147         }
148
149         return POSITION_NOT_FOUND;
150     }
151
152     /**
153      * Find the position of the Tile object.
154      * <p>
155      * First, try to find the exact identical instance of the tile object, if not found,
156      * then try to find a tile has the same title.
157      *
158      * @param tile tile that need to be found
159      * @return position of the object, return INDEX_NOT_FOUND if object isn't in the list
160      */
161     public int getPositionByTile(Tile tile) {
162         final int size = mItems.size();
163         for (int i = 0; i < size; i++) {
164             final Object entity = mItems.get(i).entity;
165             if (entity == tile) {
166                 return i;
167             } else if (entity instanceof Tile && tile.title.equals(((Tile) entity).title)) {
168                 return i;
169             }
170         }
171
172         return POSITION_NOT_FOUND;
173     }
174
175     /**
176      * Add item into list when {@paramref add} is true.
177      *
178      * @param item     maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
179      * @param type     type of the item, and value is the layout id
180      * @param stableId The stable id for this item
181      * @param add      flag about whether to add item into list
182      */
183     private void addToItemList(Object item, int type, int stableId, boolean add) {
184         if (add) {
185             mItems.add(new Item(item, type, stableId));
186         }
187     }
188
189     /**
190      * Build the mItems list using mConditions, mSuggestions, mCategories data
191      * and mIsShowingAll, mSuggestionConditionMode flag.
192      */
193     private void buildItemsData() {
194         final boolean hasSuggestions = sizeOf(mSuggestions) > 0;
195         final List<Condition> conditions = getConditionsToShow(mConditions);
196         final boolean hasConditions = sizeOf(conditions) > 0;
197
198         final List<Suggestion> suggestions = getSuggestionsToShow(mSuggestions);
199
200         final int hiddenSuggestion = hasSuggestions
201                 ? sizeOf(mSuggestions) - sizeOf(suggestions)
202                 : 0;
203
204         final boolean hasSuggestionAndCollapsed = hasSuggestions
205                 && mSuggestionConditionMode == HEADER_MODE_COLLAPSED;
206         final boolean onlyHasConditionAndCollapsed = !hasSuggestions
207                 && hasConditions
208                 && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED;
209
210         /* Top suggestion/condition header. This will be present when there is any suggestion
211          * and the mode is collapsed */
212         addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
213                 R.layout.suggestion_condition_header,
214                 STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER, hasSuggestionAndCollapsed);
215
216         /* Use mid header if there is only condition & it's in collapsed mode */
217         addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
218                 R.layout.suggestion_condition_header,
219                 STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER, onlyHasConditionAndCollapsed);
220
221         addToItemList(suggestions, R.layout.suggestion_condition_container,
222                 STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);
223
224         /* Second suggestion/condition header. This will be added when there is at least one
225          * suggestion or condition that is not currently displayed, and the user can expand the
226          * section to view more items. */
227         addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
228                 R.layout.suggestion_condition_header,
229                 STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER,
230                 mSuggestionConditionMode != HEADER_MODE_COLLAPSED
231                         && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED
232                         && (hiddenSuggestion > 0 || hasConditions && hasSuggestions));
233
234             /* Condition container. This is the card view that contains the list of conditions.
235              * This will be added whenever the condition list is not empty */
236         addToItemList(conditions, R.layout.suggestion_condition_container,
237                 STABLE_ID_CONDITION_CONTAINER,
238                 hasConditions && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED);
239
240             /* Suggestion/condition footer. This will be present when the section is fully expanded
241              * or when there is no conditions and no hidden suggestions */
242         addToItemList(null /* item */, R.layout.suggestion_condition_footer,
243                 STABLE_ID_SUGGESTION_CONDITION_FOOTER,
244                 (hasConditions || hasSuggestions)
245                         && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED
246                         || hasSuggestions
247                         && !hasConditions
248                         && hiddenSuggestion == 0);
249
250         if (mCategory != null) {
251             final List<Tile> tiles = mCategory.getTiles();
252             for (int j = 0; j < tiles.size(); j++) {
253                 final Tile tile = tiles.get(j);
254                 addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),
255                         true /* add */);
256             }
257         }
258     }
259
260     private static int sizeOf(List<?> list) {
261         return list == null ? 0 : list.size();
262     }
263
264     private List<Condition> getConditionsToShow(List<Condition> conditions) {
265         if (conditions == null) {
266             return null;
267         }
268         List<Condition> result = new ArrayList<>();
269         final int size = conditions == null ? 0 : conditions.size();
270         for (int i = 0; i < size; i++) {
271             final Condition condition = conditions.get(i);
272             if (condition.shouldShow()) {
273                 result.add(condition);
274             }
275         }
276         return result;
277     }
278
279     private List<Suggestion> getSuggestionsToShow(List<Suggestion> suggestions) {
280         if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) {
281             return null;
282         }
283         if (mSuggestionConditionMode != HEADER_MODE_DEFAULT
284                 || suggestions.size() <= DEFAULT_SUGGESTION_COUNT) {
285             return suggestions;
286         }
287         return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT);
288     }
289
290     /**
291      * Builder used to build the ItemsData
292      * <p>
293      * {@link #mSuggestionConditionMode} have default value while others are not.
294      */
295     public static class Builder {
296         @HeaderMode
297         private int mSuggestionConditionMode = HEADER_MODE_DEFAULT;
298
299         private DashboardCategory mCategory;
300         private List<Condition> mConditions;
301         private List<Suggestion> mSuggestionsV2;
302
303         public Builder() {
304         }
305
306         public Builder(DashboardData dashboardData) {
307             mCategory = dashboardData.mCategory;
308             mConditions = dashboardData.mConditions;
309             mSuggestionsV2 = dashboardData.mSuggestions;
310             mSuggestionConditionMode = dashboardData.mSuggestionConditionMode;
311         }
312
313         public Builder setCategory(DashboardCategory category) {
314             this.mCategory = category;
315             return this;
316         }
317
318         public Builder setConditions(List<Condition> conditions) {
319             this.mConditions = conditions;
320             return this;
321         }
322
323         public Builder setSuggestions(List<Suggestion> suggestions) {
324             this.mSuggestionsV2 = suggestions;
325             return this;
326         }
327
328         public Builder setSuggestionConditionMode(@HeaderMode int mode) {
329             this.mSuggestionConditionMode = mode;
330             return this;
331         }
332
333         public DashboardData build() {
334             return new DashboardData(this);
335         }
336     }
337
338     /**
339      * A DiffCallback to calculate the difference between old and new Item
340      * List in DashboardData
341      */
342     public static class ItemsDataDiffCallback extends DiffUtil.Callback {
343         final private List<Item> mOldItems;
344         final private List<Item> mNewItems;
345
346         public ItemsDataDiffCallback(List<Item> oldItems, List<Item> newItems) {
347             mOldItems = oldItems;
348             mNewItems = newItems;
349         }
350
351         @Override
352         public int getOldListSize() {
353             return mOldItems.size();
354         }
355
356         @Override
357         public int getNewListSize() {
358             return mNewItems.size();
359         }
360
361         @Override
362         public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
363             return mOldItems.get(oldItemPosition).id == mNewItems.get(newItemPosition).id;
364         }
365
366         @Override
367         public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
368             return mOldItems.get(oldItemPosition).equals(mNewItems.get(newItemPosition));
369         }
370
371     }
372
373     /**
374      * An item contains the data needed in the DashboardData.
375      */
376     static class Item {
377         // valid types in field type
378         private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
379         private static final int TYPE_SUGGESTION_CONDITION_CONTAINER =
380                 R.layout.suggestion_condition_container;
381         private static final int TYPE_SUGGESTION_CONDITION_HEADER =
382                 R.layout.suggestion_condition_header;
383         private static final int TYPE_SUGGESTION_CONDITION_FOOTER =
384                 R.layout.suggestion_condition_footer;
385         private static final int TYPE_DASHBOARD_SPACER = R.layout.dashboard_spacer;
386
387         @IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONDITION_CONTAINER,
388                 TYPE_SUGGESTION_CONDITION_HEADER, TYPE_SUGGESTION_CONDITION_FOOTER,
389                 TYPE_DASHBOARD_SPACER})
390         @Retention(RetentionPolicy.SOURCE)
391         public @interface ItemTypes {
392         }
393
394         /**
395          * The main data object in item, usually is a {@link Tile}, {@link Condition}
396          * object. This object can also be null when the
397          * item is an divider line. Please refer to {@link #buildItemsData()} for
398          * detail usage of the Item.
399          */
400         public final Object entity;
401
402         /**
403          * The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile)
404          */
405         @ItemTypes
406         public final int type;
407
408         /**
409          * Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item.
410          */
411         public final int id;
412
413         public Item(Object entity, @ItemTypes int type, int id) {
414             this.entity = entity;
415             this.type = type;
416             this.id = id;
417         }
418
419         /**
420          * Override it to make comparision in the {@link ItemsDataDiffCallback}
421          *
422          * @param obj object to compared with
423          * @return true if the same object or has equal value.
424          */
425         @Override
426         public boolean equals(Object obj) {
427             if (this == obj) {
428                 return true;
429             }
430
431             if (!(obj instanceof Item)) {
432                 return false;
433             }
434
435             final Item targetItem = (Item) obj;
436             if (type != targetItem.type || id != targetItem.id) {
437                 return false;
438             }
439
440             switch (type) {
441                 case TYPE_DASHBOARD_TILE:
442                     final Tile localTile = (Tile) entity;
443                     final Tile targetTile = (Tile) targetItem.entity;
444
445                     // Only check title and summary for dashboard tile
446                     return TextUtils.equals(localTile.title, targetTile.title)
447                             && TextUtils.equals(localTile.summary, targetTile.summary);
448                 case TYPE_SUGGESTION_CONDITION_CONTAINER:
449                     // If entity is suggestion and contains remote view, force refresh
450                     final List entities = (List) entity;
451                     if (!entities.isEmpty()) {
452                         Object firstEntity = entities.get(0);
453                         if (firstEntity instanceof Tile
454                                 && ((Tile) firstEntity).remoteViews != null) {
455                             return false;
456                         }
457                     }
458                     // Otherwise Fall through to default
459                 default:
460                     return entity == null ? targetItem.entity == null
461                             : entity.equals(targetItem.entity);
462             }
463         }
464     }
465
466     /**
467      * This class contains the data needed to build the suggestion/condition header. The data can
468      * also be used to check the diff in DiffUtil.Callback
469      */
470     public static class SuggestionConditionHeaderData {
471         public final List<Icon> conditionIcons;
472         public final CharSequence title;
473         public final int conditionCount;
474         public final int hiddenSuggestionCount;
475
476         public SuggestionConditionHeaderData(List<Condition> conditions,
477                 int hiddenSuggestionCount) {
478             conditionCount = sizeOf(conditions);
479             this.hiddenSuggestionCount = hiddenSuggestionCount;
480             title = conditionCount > 0 ? conditions.get(0).getTitle() : null;
481             conditionIcons = new ArrayList<>();
482             for (int i = 0; conditions != null && i < conditions.size(); i++) {
483                 final Condition condition = conditions.get(i);
484                 conditionIcons.add(condition.getIcon());
485             }
486         }
487     }
488
489 }