• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.content.Context;
19 import android.content.pm.PackageManager;
20 import android.graphics.drawable.Drawable;
21 import android.graphics.drawable.Icon;
22 import android.os.Bundle;
23 import android.support.v7.widget.PopupMenu;
24 import android.support.v7.widget.RecyclerView;
25 import android.text.TextUtils;
26 import android.util.ArrayMap;
27 import android.util.TypedValue;
28 import android.view.ContextThemeWrapper;
29 import android.view.LayoutInflater;
30 import android.view.MenuItem;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.ImageView;
34 import android.widget.TextView;
35 
36 import com.android.internal.logging.MetricsLogger;
37 import com.android.internal.logging.MetricsProto.MetricsEvent;
38 import com.android.internal.util.ArrayUtils;
39 import com.android.settings.R;
40 import com.android.settings.SettingsActivity;
41 import com.android.settings.dashboard.conditional.Condition;
42 import com.android.settings.dashboard.conditional.ConditionAdapterUtils;
43 import com.android.settingslib.SuggestionParser;
44 import com.android.settingslib.drawer.DashboardCategory;
45 import com.android.settingslib.drawer.Tile;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
51         implements View.OnClickListener {
52     public static final String TAG = "DashboardAdapter";
53     private static final String STATE_SUGGESTION_LIST = "suggestion_list";
54     private static final String STATE_CATEGORY_LIST = "category_list";
55     private static final String STATE_IS_SHOWING_ALL = "is_showing_all";
56     private static final String STATE_SUGGESTION_MODE = "suggestion_mode";
57     private static final int NS_SPACER = 0;
58     private static final int NS_SUGGESTION = 1000;
59     private static final int NS_ITEMS = 2000;
60     private static final int NS_CONDITION = 3000;
61 
62     private static int SUGGESTION_MODE_DEFAULT = 0;
63     private static int SUGGESTION_MODE_COLLAPSED = 1;
64     private static int SUGGESTION_MODE_EXPANDED = 2;
65 
66     private static final int DEFAULT_SUGGESTION_COUNT = 2;
67 
68     private final List<Object> mItems = new ArrayList<>();
69     private final List<Integer> mTypes = new ArrayList<>();
70     private final List<Integer> mIds = new ArrayList<>();
71     private final IconCache mCache;
72 
73     private final Context mContext;
74 
75     private List<DashboardCategory> mCategories;
76     private List<Condition> mConditions;
77     private List<Tile> mSuggestions;
78 
79     private boolean mIsShowingAll;
80     // Used for counting items;
81     private int mId;
82 
83     private int mSuggestionMode = SUGGESTION_MODE_DEFAULT;
84 
85     private Condition mExpandedCondition = null;
86     private SuggestionParser mSuggestionParser;
87 
DashboardAdapter(Context context, SuggestionParser parser, Bundle savedInstanceState, List<Condition> conditions)88     public DashboardAdapter(Context context, SuggestionParser parser, Bundle savedInstanceState,
89                 List<Condition> conditions) {
90         mContext = context;
91         mCache = new IconCache(context);
92         mSuggestionParser = parser;
93         mConditions = conditions;
94 
95         setHasStableIds(true);
96 
97         boolean showAll = true;
98         if (savedInstanceState != null) {
99             mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
100             mCategories = savedInstanceState.getParcelableArrayList(STATE_CATEGORY_LIST);
101             showAll = savedInstanceState.getBoolean(STATE_IS_SHOWING_ALL, true);
102             mSuggestionMode = savedInstanceState.getInt(
103                     STATE_SUGGESTION_MODE, SUGGESTION_MODE_DEFAULT);
104         }
105         setShowingAll(showAll);
106     }
107 
getSuggestions()108     public List<Tile> getSuggestions() {
109         return mSuggestions;
110     }
111 
setCategoriesAndSuggestions(List<DashboardCategory> categories, List<Tile> suggestions)112     public void setCategoriesAndSuggestions(List<DashboardCategory> categories,
113             List<Tile> suggestions) {
114         mSuggestions = suggestions;
115         mCategories = categories;
116 
117         // TODO: Better place for tinting?
118         TypedValue tintColor = new TypedValue();
119         mContext.getTheme().resolveAttribute(com.android.internal.R.attr.colorAccent,
120                 tintColor, true);
121         for (int i = 0; i < categories.size(); i++) {
122             for (int j = 0; j < categories.get(i).tiles.size(); j++) {
123                 Tile tile = categories.get(i).tiles.get(j);
124 
125                 if (!mContext.getPackageName().equals(
126                         tile.intent.getComponent().getPackageName())) {
127                     // If this drawable is coming from outside Settings, tint it to match the
128                     // color.
129                     tile.icon.setTint(tintColor.data);
130                 }
131             }
132         }
133         recountItems();
134     }
135 
setConditions(List<Condition> conditions)136     public void setConditions(List<Condition> conditions) {
137         mConditions = conditions;
138         recountItems();
139     }
140 
isShowingAll()141     public boolean isShowingAll() {
142         return mIsShowingAll;
143     }
144 
notifyChanged(Tile tile)145     public void notifyChanged(Tile tile) {
146         notifyDataSetChanged();
147     }
148 
setShowingAll(boolean showingAll)149     public void setShowingAll(boolean showingAll) {
150         mIsShowingAll = showingAll;
151         recountItems();
152     }
153 
recountItems()154     private void recountItems() {
155         reset();
156         boolean hasConditions = false;
157         for (int i = 0; mConditions != null && i < mConditions.size(); i++) {
158             boolean shouldShow = mConditions.get(i).shouldShow();
159             hasConditions |= shouldShow;
160             countItem(mConditions.get(i), R.layout.condition_card, shouldShow, NS_CONDITION);
161         }
162         boolean hasSuggestions = mSuggestions != null && mSuggestions.size() != 0;
163         countItem(null, R.layout.dashboard_spacer, hasConditions && hasSuggestions, NS_SPACER);
164         countItem(null, R.layout.suggestion_header, hasSuggestions, NS_SPACER);
165         resetCount();
166         if (mSuggestions != null) {
167             int maxSuggestions = getDisplayableSuggestionCount();
168             for (int i = 0; i < mSuggestions.size(); i++) {
169                 countItem(mSuggestions.get(i), R.layout.suggestion_tile, i < maxSuggestions,
170                         NS_SUGGESTION);
171             }
172         }
173         resetCount();
174         for (int i = 0; mCategories != null && i < mCategories.size(); i++) {
175             DashboardCategory category = mCategories.get(i);
176             countItem(category, R.layout.dashboard_category, mIsShowingAll, NS_ITEMS);
177             for (int j = 0; j < category.tiles.size(); j++) {
178                 Tile tile = category.tiles.get(j);
179                 countItem(tile, R.layout.dashboard_tile, mIsShowingAll
180                         || ArrayUtils.contains(DashboardSummary.INITIAL_ITEMS,
181                         tile.intent.getComponent().getClassName()), NS_ITEMS);
182             }
183         }
184         notifyDataSetChanged();
185     }
186 
187     private void resetCount() {
188         mId = 0;
189     }
190 
191     private void reset() {
192         mItems.clear();
193         mTypes.clear();
194         mIds.clear();
195         mId = 0;
196     }
197 
198     private void countItem(Object object, int type, boolean add, int nameSpace) {
199         if (add) {
200             mItems.add(object);
201             mTypes.add(type);
202             // TODO: Counting namespaces for handling of suggestions/conds appearing/disappearing.
203             mIds.add(mId + nameSpace);
204         }
205         mId++;
206     }
207 
208     private int getDisplayableSuggestionCount() {
209         final int suggestionSize = mSuggestions.size();
210         return mSuggestionMode == SUGGESTION_MODE_DEFAULT
211                 ? Math.min(DEFAULT_SUGGESTION_COUNT, suggestionSize)
212                 : mSuggestionMode == SUGGESTION_MODE_EXPANDED
213                         ? suggestionSize : 0;
214     }
215 
216     @Override
217     public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
218         return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
219                 viewType, parent, false));
220     }
221 
222     @Override
223     public void onBindViewHolder(DashboardItemHolder holder, int position) {
224         switch (mTypes.get(position)) {
225             case R.layout.dashboard_category:
226                 onBindCategory(holder, (DashboardCategory) mItems.get(position));
227                 break;
228             case R.layout.dashboard_tile:
229                 final Tile tile = (Tile) mItems.get(position);
230                 onBindTile(holder, tile);
231                 holder.itemView.setTag(tile);
232                 holder.itemView.setOnClickListener(this);
233                 break;
234             case R.layout.suggestion_header:
235                 onBindSuggestionHeader(holder);
236                 break;
237             case R.layout.suggestion_tile:
238                 final Tile suggestion = (Tile) mItems.get(position);
239                 onBindTile(holder, suggestion);
240                 holder.itemView.setOnClickListener(new View.OnClickListener() {
241                     @Override
242                     public void onClick(View v) {
243                         MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION,
244                                 DashboardAdapter.getSuggestionIdentifier(mContext, suggestion));
245                         ((SettingsActivity) mContext).startSuggestion(suggestion.intent);
246                     }
247                 });
248                 holder.itemView.findViewById(R.id.overflow).setOnClickListener(
249                         new View.OnClickListener() {
250                             @Override
251                             public void onClick(View v) {
252                                 showRemoveOption(v, suggestion);
253                             }
254                         });
255                 break;
256             case R.layout.see_all:
257                 onBindSeeAll(holder);
258                 break;
259             case R.layout.condition_card:
260                 ConditionAdapterUtils.bindViews((Condition) mItems.get(position), holder,
261                         mItems.get(position) == mExpandedCondition, this,
262                         new View.OnClickListener() {
263                             @Override
264                             public void onClick(View v) {
265                                 onExpandClick(v);
266                             }
267                         });
268                 break;
269         }
270     }
271 
272     private void showRemoveOption(View v, final Tile suggestion) {
273         PopupMenu popup = new PopupMenu(
274                 new ContextThemeWrapper(mContext, R.style.Theme_AppCompat_DayNight), v);
275         popup.getMenu().add(R.string.suggestion_remove).setOnMenuItemClickListener(
276                 new MenuItem.OnMenuItemClickListener() {
277             @Override
278             public boolean onMenuItemClick(MenuItem item) {
279                 MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION,
280                         DashboardAdapter.getSuggestionIdentifier(mContext, suggestion));
281                 disableSuggestion(suggestion);
282                 mSuggestions.remove(suggestion);
283                 recountItems();
284                 return true;
285             }
286         });
287         popup.show();
288     }
289 
290     public void disableSuggestion(Tile suggestion) {
291         if (mSuggestionParser == null) {
292             return;
293         }
294         if (mSuggestionParser.dismissSuggestion(suggestion)) {
295             mContext.getPackageManager().setComponentEnabledSetting(
296                     suggestion.intent.getComponent(),
297                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
298                     PackageManager.DONT_KILL_APP);
299             mSuggestionParser.markCategoryDone(suggestion.category);
300         }
301     }
302 
303     private void onBindSuggestionHeader(final DashboardItemHolder holder) {
304         holder.icon.setImageResource(hasMoreSuggestions() ? R.drawable.ic_expand_more
305                 : R.drawable.ic_expand_less);
306         holder.title.setText(mContext.getString(R.string.suggestions_title, mSuggestions.size()));
307         final int undisplayedSuggestionCount =
308                 mSuggestions.size() - getDisplayableSuggestionCount();
309         if (undisplayedSuggestionCount == 0) {
310             holder.summary.setText(null);
311         } else {
312             holder.summary.setText(
313                     mContext.getString(R.string.suggestions_summary, undisplayedSuggestionCount));
314         }
315         holder.itemView.setOnClickListener(new View.OnClickListener() {
316             @Override
317             public void onClick(View v) {
318                 if (hasMoreSuggestions()) {
319                     mSuggestionMode = SUGGESTION_MODE_EXPANDED;
320                 } else {
321                     mSuggestionMode = SUGGESTION_MODE_COLLAPSED;
322                 }
323                 recountItems();
324             }
325         });
326     }
327 
328     private boolean hasMoreSuggestions() {
329         return mSuggestionMode == SUGGESTION_MODE_COLLAPSED
330                 || (mSuggestionMode == SUGGESTION_MODE_DEFAULT
331                 && mSuggestions.size() > DEFAULT_SUGGESTION_COUNT);
332     }
333 
334     private void onBindTile(DashboardItemHolder holder, Tile tile) {
335         holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
336         holder.title.setText(tile.title);
337         if (!TextUtils.isEmpty(tile.summary)) {
338             holder.summary.setText(tile.summary);
339             holder.summary.setVisibility(View.VISIBLE);
340         } else {
341             holder.summary.setVisibility(View.GONE);
342         }
343     }
344 
345     private void onBindCategory(DashboardItemHolder holder, DashboardCategory category) {
346         holder.title.setText(category.title);
347     }
348 
349     private void onBindSeeAll(DashboardItemHolder holder) {
350         holder.title.setText(mIsShowingAll ? R.string.see_less
351                 : R.string.see_all);
352         holder.itemView.setOnClickListener(new View.OnClickListener() {
353             @Override
354             public void onClick(View v) {
355                 setShowingAll(!mIsShowingAll);
356             }
357         });
358     }
359 
360     @Override
361     public long getItemId(int position) {
362         return mIds.get(position);
363     }
364 
365     @Override
366     public int getItemViewType(int position) {
367         return mTypes.get(position);
368     }
369 
370     @Override
371     public int getItemCount() {
372         return mIds.size();
373     }
374 
375     @Override
376     public void onClick(View v) {
377         if (v.getId() == R.id.dashboard_tile) {
378             ((SettingsActivity) mContext).openTile((Tile) v.getTag());
379             return;
380         }
381         if (v.getTag() == mExpandedCondition) {
382             MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
383                     mExpandedCondition.getMetricsConstant());
384             mExpandedCondition.onPrimaryClick();
385         } else {
386             mExpandedCondition = (Condition) v.getTag();
387             MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
388                     mExpandedCondition.getMetricsConstant());
389             notifyDataSetChanged();
390         }
391     }
392 
393     public void onExpandClick(View v) {
394         if (v.getTag() == mExpandedCondition) {
395             MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_COLLAPSE,
396                     mExpandedCondition.getMetricsConstant());
397             mExpandedCondition = null;
398         } else {
399             mExpandedCondition = (Condition) v.getTag();
400             MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
401                     mExpandedCondition.getMetricsConstant());
402         }
403         notifyDataSetChanged();
404     }
405 
406     public Object getItem(long itemId) {
407         for (int i = 0; i < mIds.size(); i++) {
408             if (mIds.get(i) == itemId) {
409                 return mItems.get(i);
410             }
411         }
412         return null;
413     }
414 
415     public static String getSuggestionIdentifier(Context context, Tile suggestion) {
416         String packageName = suggestion.intent.getComponent().getPackageName();
417         if (packageName.equals(context.getPackageName())) {
418             // Since Settings provides several suggestions, fill in the class instead of the
419             // package for these.
420             packageName = suggestion.intent.getComponent().getClassName();
421         }
422         return packageName;
423     }
424 
425     void onSaveInstanceState(Bundle outState) {
426         if (mSuggestions != null) {
427             outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
428                     new ArrayList<Tile>(mSuggestions));
429         }
430         if (mCategories != null) {
431             outState.putParcelableArrayList(STATE_CATEGORY_LIST,
432                     new ArrayList<DashboardCategory>(mCategories));
433         }
434         outState.putBoolean(STATE_IS_SHOWING_ALL, mIsShowingAll);
435         outState.putInt(STATE_SUGGESTION_MODE, mSuggestionMode);
436     }
437 
438     private static class IconCache {
439 
440         private final Context mContext;
441         private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
442 
443         public IconCache(Context context) {
444             mContext = context;
445         }
446 
447         public Drawable getIcon(Icon icon) {
448             Drawable drawable = mMap.get(icon);
449             if (drawable == null) {
450                 drawable = icon.loadDrawable(mContext);
451                 mMap.put(icon, drawable);
452             }
453             return drawable;
454         }
455     }
456 
457     public static class DashboardItemHolder extends RecyclerView.ViewHolder {
458         public final ImageView icon;
459         public final TextView title;
460         public final TextView summary;
461 
462         public DashboardItemHolder(View itemView) {
463             super(itemView);
464             icon = (ImageView) itemView.findViewById(android.R.id.icon);
465             title = (TextView) itemView.findViewById(android.R.id.title);
466             summary = (TextView) itemView.findViewById(android.R.id.summary);
467         }
468     }
469 }
470