OSDN Git Service

Create a new list when building suggestion data.
[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 POSITION_NOT_FOUND = -1;
44     public static final int MAX_SUGGESTION_COUNT = 2;
45
46     // stable id for different type of items.
47     @VisibleForTesting
48     static final int STABLE_ID_SUGGESTION_CONTAINER = 0;
49     static final int STABLE_ID_SUGGESTION_CONDITION_DIVIDER = 1;
50     @VisibleForTesting
51     static final int STABLE_ID_CONDITION_HEADER = 2;
52     @VisibleForTesting
53     static final int STABLE_ID_CONDITION_FOOTER = 3;
54     @VisibleForTesting
55     static final int STABLE_ID_CONDITION_CONTAINER = 4;
56
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;
62
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<>();
69
70         buildItemsData();
71     }
72
73     public int getItemIdByPosition(int position) {
74         return mItems.get(position).id;
75     }
76
77     public int getItemTypeByPosition(int position) {
78         return mItems.get(position).type;
79     }
80
81     public Object getItemEntityByPosition(int position) {
82         return mItems.get(position).entity;
83     }
84
85     public List<Item> getItemList() {
86         return mItems;
87     }
88
89     public int size() {
90         return mItems.size();
91     }
92
93     public Object getItemEntityById(long id) {
94         for (final Item item : mItems) {
95             if (item.id == id) {
96                 return item.entity;
97             }
98         }
99         return null;
100     }
101
102     public DashboardCategory getCategory() {
103         return mCategory;
104     }
105
106     public List<Condition> getConditions() {
107         return mConditions;
108     }
109
110     public List<Suggestion> getSuggestions() {
111         return mSuggestions;
112     }
113
114     public boolean hasSuggestion() {
115         return sizeOf(mSuggestions) > 0;
116     }
117
118     public boolean isConditionExpanded() {
119         return mConditionExpanded;
120     }
121
122     /**
123      * Find the position of the object in mItems list, using the equals method to compare
124      *
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
127      */
128     public int getPositionByEntity(Object entity) {
129         if (entity == null) return POSITION_NOT_FOUND;
130
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)) {
135                 return i;
136             }
137         }
138
139         return POSITION_NOT_FOUND;
140     }
141
142     /**
143      * Find the position of the Tile object.
144      * <p>
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.
147      *
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
150      */
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) {
156                 return i;
157             } else if (entity instanceof Tile && tile.title.equals(((Tile) entity).title)) {
158                 return i;
159             }
160         }
161
162         return POSITION_NOT_FOUND;
163     }
164
165     /**
166      * Add item into list when {@paramref add} is true.
167      *
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
172      */
173     private void addToItemList(Object item, int type, int stableId, boolean add) {
174         if (add) {
175             mItems.add(new Item(item, type, stableId));
176         }
177     }
178
179     /**
180      * Build the mItems list using mConditions, mSuggestions, mCategories data
181      * and mIsShowingAll, mConditionExpanded flag.
182      */
183     private void buildItemsData() {
184         final List<Condition> conditions = getConditionsToShow(mConditions);
185         final boolean hasConditions = sizeOf(conditions) > 0;
186
187         final List<Suggestion> suggestions = getSuggestionsToShow(mSuggestions);
188         final boolean hasSuggestions = sizeOf(suggestions) > 0;
189
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);
194
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);
198
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);
203
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);
208
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);
212
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),
218                         true /* add */);
219             }
220         }
221     }
222
223     private static int sizeOf(List<?> list) {
224         return list == null ? 0 : list.size();
225     }
226
227     private List<Condition> getConditionsToShow(List<Condition> conditions) {
228         if (conditions == null) {
229             return null;
230         }
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);
237             }
238         }
239         return result;
240     }
241
242     private List<Suggestion> getSuggestionsToShow(List<Suggestion> suggestions) {
243         if (suggestions == null) {
244             return null;
245         }
246         if (suggestions.size() <= MAX_SUGGESTION_COUNT) {
247             return suggestions;
248         }
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));
252         }
253         return suggestionsToShow;
254     }
255
256     /**
257      * Builder used to build the ItemsData
258      */
259     public static class Builder {
260         private DashboardCategory mCategory;
261         private List<Condition> mConditions;
262         private List<Suggestion> mSuggestions;
263         private boolean mConditionExpanded;
264
265         public Builder() {
266         }
267
268         public Builder(DashboardData dashboardData) {
269             mCategory = dashboardData.mCategory;
270             mConditions = dashboardData.mConditions;
271             mSuggestions = dashboardData.mSuggestions;
272             mConditionExpanded = dashboardData.mConditionExpanded;
273         }
274
275         public Builder setCategory(DashboardCategory category) {
276             this.mCategory = category;
277             return this;
278         }
279
280         public Builder setConditions(List<Condition> conditions) {
281             this.mConditions = conditions;
282             return this;
283         }
284
285         public Builder setSuggestions(List<Suggestion> suggestions) {
286             this.mSuggestions = suggestions;
287             return this;
288         }
289
290         public Builder setConditionExpanded(boolean expanded) {
291             this.mConditionExpanded = expanded;
292             return this;
293         }
294
295         public DashboardData build() {
296             return new DashboardData(this);
297         }
298     }
299
300     /**
301      * A DiffCallback to calculate the difference between old and new Item
302      * List in DashboardData
303      */
304     public static class ItemsDataDiffCallback extends DiffUtil.Callback {
305         final private List<Item> mOldItems;
306         final private List<Item> mNewItems;
307
308         public ItemsDataDiffCallback(List<Item> oldItems, List<Item> newItems) {
309             mOldItems = oldItems;
310             mNewItems = newItems;
311         }
312
313         @Override
314         public int getOldListSize() {
315             return mOldItems.size();
316         }
317
318         @Override
319         public int getNewListSize() {
320             return mNewItems.size();
321         }
322
323         @Override
324         public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
325             return mOldItems.get(oldItemPosition).id == mNewItems.get(newItemPosition).id;
326         }
327
328         @Override
329         public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
330             return mOldItems.get(oldItemPosition).equals(mNewItems.get(newItemPosition));
331         }
332
333     }
334
335     /**
336      * An item contains the data needed in the DashboardData.
337      */
338     static class Item {
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;
350
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 {
355         }
356
357         /**
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.
362          */
363         public final Object entity;
364
365         /**
366          * The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile)
367          */
368         @ItemTypes
369         public final int type;
370
371         /**
372          * Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item.
373          */
374         public final int id;
375
376         public Item(Object entity, @ItemTypes int type, int id) {
377             this.entity = entity;
378             this.type = type;
379             this.id = id;
380         }
381
382         /**
383          * Override it to make comparision in the {@link ItemsDataDiffCallback}
384          *
385          * @param obj object to compared with
386          * @return true if the same object or has equal value.
387          */
388         @Override
389         public boolean equals(Object obj) {
390             if (this == obj) {
391                 return true;
392             }
393
394             if (!(obj instanceof Item)) {
395                 return false;
396             }
397
398             final Item targetItem = (Item) obj;
399             if (type != targetItem.type || id != targetItem.id) {
400                 return false;
401             }
402
403             switch (type) {
404                 case TYPE_DASHBOARD_TILE:
405                     final Tile localTile = (Tile) entity;
406                     final Tile targetTile = (Tile) targetItem.entity;
407
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) {
419                             return false;
420                         }
421                     }
422                     // Otherwise Fall through to default
423                 default:
424                     return entity == null ? targetItem.entity == null
425                             : entity.equals(targetItem.entity);
426             }
427         }
428     }
429
430     /**
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
433      */
434     public static class ConditionHeaderData {
435         public final List<Icon> conditionIcons;
436         public final CharSequence title;
437         public final int conditionCount;
438
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());
446             }
447         }
448     }
449
450 }