2 * Copyright (C) 2015 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.content.ComponentName;
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.graphics.drawable.Drawable;
22 import android.graphics.drawable.Icon;
23 import android.os.Bundle;
24 import android.support.v7.widget.PopupMenu;
25 import android.support.v7.widget.RecyclerView;
26 import android.text.TextUtils;
27 import android.util.ArrayMap;
28 import android.util.TypedValue;
29 import android.view.ContextThemeWrapper;
30 import android.view.LayoutInflater;
31 import android.view.MenuItem;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.ImageView;
35 import android.widget.TextView;
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;
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.Objects;
51 public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
52 implements View.OnClickListener {
53 public static final String TAG = "DashboardAdapter";
54 private static final String STATE_SUGGESTION_LIST = "suggestion_list";
55 private static final String STATE_CATEGORY_LIST = "category_list";
56 private static final String STATE_IS_SHOWING_ALL = "is_showing_all";
57 private static final String STATE_SUGGESTION_MODE = "suggestion_mode";
58 private static final int NS_SPACER = 0;
59 private static final int NS_SUGGESTION = 1000;
60 private static final int NS_ITEMS = 2000;
61 private static final int NS_CONDITION = 3000;
63 private static int SUGGESTION_MODE_DEFAULT = 0;
64 private static int SUGGESTION_MODE_COLLAPSED = 1;
65 private static int SUGGESTION_MODE_EXPANDED = 2;
67 private static final int DEFAULT_SUGGESTION_COUNT = 2;
69 private final List<Object> mItems = new ArrayList<>();
70 private final List<Integer> mTypes = new ArrayList<>();
71 private final List<Integer> mIds = new ArrayList<>();
72 private final IconCache mCache;
74 private final Context mContext;
76 private List<DashboardCategory> mCategories;
77 private List<Condition> mConditions;
78 private List<Tile> mSuggestions;
80 private boolean mIsShowingAll;
81 // Used for counting items;
84 private int mSuggestionMode = SUGGESTION_MODE_DEFAULT;
86 private Condition mExpandedCondition = null;
87 private SuggestionParser mSuggestionParser;
89 public DashboardAdapter(Context context, SuggestionParser parser, Bundle savedInstanceState,
90 List<Condition> conditions) {
92 mCache = new IconCache(context);
93 mSuggestionParser = parser;
94 mConditions = conditions;
96 setHasStableIds(true);
98 boolean showAll = true;
99 if (savedInstanceState != null) {
100 mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
101 mCategories = savedInstanceState.getParcelableArrayList(STATE_CATEGORY_LIST);
102 showAll = savedInstanceState.getBoolean(STATE_IS_SHOWING_ALL, true);
103 mSuggestionMode = savedInstanceState.getInt(
104 STATE_SUGGESTION_MODE, SUGGESTION_MODE_DEFAULT);
106 setShowingAll(showAll);
109 public List<Tile> getSuggestions() {
113 public Tile getTile(ComponentName component) {
114 if (mCategories == null) {
117 for (int i = 0; i < mCategories.size(); i++) {
118 for (int j = 0; j < mCategories.get(i).tiles.size(); j++) {
119 Tile tile = mCategories.get(i).tiles.get(j);
120 if (component.equals(tile.intent.getComponent())) {
128 public void setCategoriesAndSuggestions(List<DashboardCategory> categories,
129 List<Tile> suggestions) {
130 mSuggestions = suggestions;
131 mCategories = categories;
133 // TODO: Better place for tinting?
134 TypedValue tintColor = new TypedValue();
135 mContext.getTheme().resolveAttribute(com.android.internal.R.attr.colorAccent,
137 for (int i = 0; i < categories.size(); i++) {
138 for (int j = 0; j < categories.get(i).tiles.size(); j++) {
139 Tile tile = categories.get(i).tiles.get(j);
141 if (!mContext.getPackageName().equals(
142 tile.intent.getComponent().getPackageName())) {
143 // If this drawable is coming from outside Settings, tint it to match the
145 tile.icon.setTint(tintColor.data);
152 public void setConditions(List<Condition> conditions) {
153 mConditions = conditions;
157 public boolean isShowingAll() {
158 return mIsShowingAll;
161 public void notifyChanged(Tile tile) {
162 notifyDataSetChanged();
165 public void setShowingAll(boolean showingAll) {
166 mIsShowingAll = showingAll;
170 private void recountItems() {
172 boolean hasConditions = false;
173 for (int i = 0; mConditions != null && i < mConditions.size(); i++) {
174 boolean shouldShow = mConditions.get(i).shouldShow();
175 hasConditions |= shouldShow;
176 countItem(mConditions.get(i), R.layout.condition_card, shouldShow, NS_CONDITION);
178 boolean hasSuggestions = mSuggestions != null && mSuggestions.size() != 0;
179 countItem(null, R.layout.dashboard_spacer, hasConditions && hasSuggestions, NS_SPACER);
180 countItem(null, R.layout.suggestion_header, hasSuggestions, NS_SPACER);
182 if (mSuggestions != null) {
183 int maxSuggestions = getDisplayableSuggestionCount();
184 for (int i = 0; i < mSuggestions.size(); i++) {
185 countItem(mSuggestions.get(i), R.layout.suggestion_tile, i < maxSuggestions,
189 countItem(null, R.layout.dashboard_spacer, true, NS_SPACER);
191 for (int i = 0; mCategories != null && i < mCategories.size(); i++) {
192 DashboardCategory category = mCategories.get(i);
193 countItem(category, R.layout.dashboard_category, mIsShowingAll, NS_ITEMS);
194 for (int j = 0; j < category.tiles.size(); j++) {
195 Tile tile = category.tiles.get(j);
196 countItem(tile, R.layout.dashboard_tile, mIsShowingAll
197 || ArrayUtils.contains(DashboardSummary.INITIAL_ITEMS,
198 tile.intent.getComponent().getClassName()), NS_ITEMS);
201 notifyDataSetChanged();
204 private void resetCount() {
208 private void reset() {
215 private void countItem(Object object, int type, boolean add, int nameSpace) {
219 // TODO: Counting namespaces for handling of suggestions/conds appearing/disappearing.
220 mIds.add(mId + nameSpace);
225 private int getDisplayableSuggestionCount() {
226 final int suggestionSize = mSuggestions.size();
227 return mSuggestionMode == SUGGESTION_MODE_DEFAULT
228 ? Math.min(DEFAULT_SUGGESTION_COUNT, suggestionSize)
229 : mSuggestionMode == SUGGESTION_MODE_EXPANDED
230 ? suggestionSize : 0;
234 public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
235 return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
236 viewType, parent, false));
240 public void onBindViewHolder(DashboardItemHolder holder, int position) {
241 switch (mTypes.get(position)) {
242 case R.layout.dashboard_category:
243 onBindCategory(holder, (DashboardCategory) mItems.get(position));
245 case R.layout.dashboard_tile:
246 final Tile tile = (Tile) mItems.get(position);
247 onBindTile(holder, tile);
248 holder.itemView.setTag(tile);
249 holder.itemView.setOnClickListener(this);
251 case R.layout.suggestion_header:
252 onBindSuggestionHeader(holder);
254 case R.layout.suggestion_tile:
255 final Tile suggestion = (Tile) mItems.get(position);
256 onBindTile(holder, suggestion);
257 holder.itemView.setOnClickListener(new View.OnClickListener() {
259 public void onClick(View v) {
260 MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION,
261 DashboardAdapter.getSuggestionIdentifier(mContext, suggestion));
262 ((SettingsActivity) mContext).startSuggestion(suggestion.intent);
265 holder.itemView.findViewById(R.id.overflow).setOnClickListener(
266 new View.OnClickListener() {
268 public void onClick(View v) {
269 showRemoveOption(v, suggestion);
273 case R.layout.see_all:
274 onBindSeeAll(holder);
276 case R.layout.condition_card:
277 ConditionAdapterUtils.bindViews((Condition) mItems.get(position), holder,
278 mItems.get(position) == mExpandedCondition, this,
279 new View.OnClickListener() {
281 public void onClick(View v) {
289 private void showRemoveOption(View v, final Tile suggestion) {
290 PopupMenu popup = new PopupMenu(
291 new ContextThemeWrapper(mContext, R.style.Theme_AppCompat_DayNight), v);
292 popup.getMenu().add(R.string.suggestion_remove).setOnMenuItemClickListener(
293 new MenuItem.OnMenuItemClickListener() {
295 public boolean onMenuItemClick(MenuItem item) {
296 MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_DISMISS_SUGGESTION,
297 DashboardAdapter.getSuggestionIdentifier(mContext, suggestion));
298 disableSuggestion(suggestion);
299 mSuggestions.remove(suggestion);
307 public void disableSuggestion(Tile suggestion) {
308 if (mSuggestionParser == null) {
311 if (mSuggestionParser.dismissSuggestion(suggestion)) {
312 mContext.getPackageManager().setComponentEnabledSetting(
313 suggestion.intent.getComponent(),
314 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
315 PackageManager.DONT_KILL_APP);
316 mSuggestionParser.markCategoryDone(suggestion.category);
320 private void onBindSuggestionHeader(final DashboardItemHolder holder) {
321 holder.icon.setImageResource(hasMoreSuggestions() ? R.drawable.ic_expand_more
322 : R.drawable.ic_expand_less);
323 holder.title.setText(mContext.getString(R.string.suggestions_title, mSuggestions.size()));
324 final int undisplayedSuggestionCount =
325 mSuggestions.size() - getDisplayableSuggestionCount();
326 if (undisplayedSuggestionCount == 0) {
327 holder.summary.setText(null);
329 holder.summary.setText(
330 mContext.getString(R.string.suggestions_summary, undisplayedSuggestionCount));
332 holder.itemView.setOnClickListener(new View.OnClickListener() {
334 public void onClick(View v) {
335 if (hasMoreSuggestions()) {
336 mSuggestionMode = SUGGESTION_MODE_EXPANDED;
338 mSuggestionMode = SUGGESTION_MODE_COLLAPSED;
345 private boolean hasMoreSuggestions() {
346 return mSuggestionMode == SUGGESTION_MODE_COLLAPSED
347 || (mSuggestionMode == SUGGESTION_MODE_DEFAULT
348 && mSuggestions.size() > DEFAULT_SUGGESTION_COUNT);
351 private void onBindTile(DashboardItemHolder holder, Tile tile) {
352 holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
353 holder.title.setText(tile.title);
354 if (!TextUtils.isEmpty(tile.summary)) {
355 holder.summary.setText(tile.summary);
356 holder.summary.setVisibility(View.VISIBLE);
358 holder.summary.setVisibility(View.GONE);
362 private void onBindCategory(DashboardItemHolder holder, DashboardCategory category) {
363 holder.title.setText(category.title);
366 private void onBindSeeAll(DashboardItemHolder holder) {
367 holder.title.setText(mIsShowingAll ? R.string.see_less
369 holder.itemView.setOnClickListener(new View.OnClickListener() {
371 public void onClick(View v) {
372 setShowingAll(!mIsShowingAll);
378 public long getItemId(int position) {
379 return mIds.get(position);
383 public int getItemViewType(int position) {
384 return mTypes.get(position);
388 public int getItemCount() {
393 public void onClick(View v) {
394 if (v.getId() == R.id.dashboard_tile) {
395 ((SettingsActivity) mContext).openTile((Tile) v.getTag());
398 if (v.getTag() == mExpandedCondition) {
399 MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
400 mExpandedCondition.getMetricsConstant());
401 mExpandedCondition.onPrimaryClick();
403 mExpandedCondition = (Condition) v.getTag();
404 MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
405 mExpandedCondition.getMetricsConstant());
406 notifyDataSetChanged();
410 public void onExpandClick(View v) {
411 if (v.getTag() == mExpandedCondition) {
412 MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_COLLAPSE,
413 mExpandedCondition.getMetricsConstant());
414 mExpandedCondition = null;
416 mExpandedCondition = (Condition) v.getTag();
417 MetricsLogger.action(mContext, MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND,
418 mExpandedCondition.getMetricsConstant());
420 notifyDataSetChanged();
423 public Object getItem(long itemId) {
424 for (int i = 0; i < mIds.size(); i++) {
425 if (mIds.get(i) == itemId) {
426 return mItems.get(i);
432 public static String getSuggestionIdentifier(Context context, Tile suggestion) {
433 String packageName = suggestion.intent.getComponent().getPackageName();
434 if (packageName.equals(context.getPackageName())) {
435 // Since Settings provides several suggestions, fill in the class instead of the
436 // package for these.
437 packageName = suggestion.intent.getComponent().getClassName();
442 void onSaveInstanceState(Bundle outState) {
443 if (mSuggestions != null) {
444 outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
445 new ArrayList<Tile>(mSuggestions));
447 if (mCategories != null) {
448 outState.putParcelableArrayList(STATE_CATEGORY_LIST,
449 new ArrayList<DashboardCategory>(mCategories));
451 outState.putBoolean(STATE_IS_SHOWING_ALL, mIsShowingAll);
452 outState.putInt(STATE_SUGGESTION_MODE, mSuggestionMode);
455 private static class IconCache {
457 private final Context mContext;
458 private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
460 public IconCache(Context context) {
464 public Drawable getIcon(Icon icon) {
465 Drawable drawable = mMap.get(icon);
466 if (drawable == null) {
467 drawable = icon.loadDrawable(mContext);
468 mMap.put(icon, drawable);
474 public static class DashboardItemHolder extends RecyclerView.ViewHolder {
475 public final ImageView icon;
476 public final TextView title;
477 public final TextView summary;
479 public DashboardItemHolder(View itemView) {
481 icon = (ImageView) itemView.findViewById(android.R.id.icon);
482 title = (TextView) itemView.findViewById(android.R.id.title);
483 summary = (TextView) itemView.findViewById(android.R.id.summary);