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