OSDN Git Service

Merge "Fix search indexing for encryption_and_credential page" into oc-dr1-dev
[android-x86/packages-apps-Settings.git] / src / com / android / settings / dashboard / DashboardAdapter.java
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.app.Activity;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.content.res.TypedArray;
22 import android.graphics.Color;
23 import android.graphics.drawable.Drawable;
24 import android.graphics.drawable.Icon;
25 import android.os.Bundle;
26 import android.support.annotation.VisibleForTesting;
27 import android.support.v7.util.DiffUtil;
28 import android.support.v7.widget.LinearLayoutManager;
29 import android.support.v7.widget.RecyclerView;
30 import android.text.TextUtils;
31 import android.util.ArrayMap;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.ImageView;
37 import android.widget.LinearLayout;
38 import android.widget.TextView;
39
40 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
41 import com.android.settings.R;
42 import com.android.settings.R.id;
43 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
44 import com.android.settings.dashboard.DashboardData.SuggestionConditionHeaderData;
45 import com.android.settings.dashboard.conditional.Condition;
46 import com.android.settings.dashboard.conditional.ConditionAdapter;
47 import com.android.settings.dashboard.suggestions.SuggestionAdapter;
48 import com.android.settings.dashboard.suggestions.SuggestionDismissController;
49 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
50 import com.android.settings.overlay.FeatureFactory;
51 import com.android.settingslib.Utils;
52 import com.android.settingslib.drawer.DashboardCategory;
53 import com.android.settingslib.drawer.Tile;
54 import com.android.settingslib.suggestions.SuggestionParser;
55
56 import java.util.ArrayList;
57 import java.util.List;
58
59 public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
60         implements SummaryLoader.SummaryConsumer {
61     public static final String TAG = "DashboardAdapter";
62     private static final String STATE_SUGGESTION_LIST = "suggestion_list";
63     private static final String STATE_CATEGORY_LIST = "category_list";
64     private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
65
66     @VisibleForTesting
67     static final String STATE_SUGGESTION_CONDITION_MODE = "suggestion_condition_mode";
68     @VisibleForTesting
69     static final int SUGGESTION_CONDITION_HEADER_POSITION = 0;
70
71     private final IconCache mCache;
72     private final Context mContext;
73     private final MetricsFeatureProvider mMetricsFeatureProvider;
74     private final DashboardFeatureProvider mDashboardFeatureProvider;
75     private final SuggestionFeatureProvider mSuggestionFeatureProvider;
76     private final ArrayList<String> mSuggestionsShownLogged;
77     private boolean mFirstFrameDrawn;
78     private RecyclerView mRecyclerView;
79     private SuggestionParser mSuggestionParser;
80     private SuggestionAdapter mSuggestionAdapter;
81     private SuggestionDismissController mSuggestionDismissHandler;
82     private SuggestionDismissController.Callback mCallback;
83
84     @VisibleForTesting
85     DashboardData mDashboardData;
86
87     private View.OnClickListener mTileClickListener = new View.OnClickListener() {
88         @Override
89         public void onClick(View v) {
90             //TODO: get rid of setTag/getTag
91             mDashboardFeatureProvider.openTileIntent((Activity) mContext, (Tile) v.getTag());
92         }
93     };
94
95     private View.OnClickListener mConditionClickListener = new View.OnClickListener() {
96
97         @Override
98         public void onClick(View v) {
99             Condition condition = (Condition) v.getTag();
100             //TODO: get rid of setTag/getTag
101             mMetricsFeatureProvider.action(mContext,
102                     MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
103                     condition.getMetricsConstant());
104             condition.onPrimaryClick();
105         }
106     };
107
108     public DashboardAdapter(Context context, Bundle savedInstanceState,
109             List<Condition> conditions, SuggestionParser suggestionParser,
110             SuggestionDismissController.Callback callback) {
111         List<Tile> suggestions = null;
112         DashboardCategory category = null;
113         int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT;
114
115         mContext = context;
116         final FeatureFactory factory = FeatureFactory.getFactory(context);
117         mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
118         mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
119         mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
120         mCache = new IconCache(context);
121         mSuggestionParser = suggestionParser;
122         mCallback = callback;
123
124         setHasStableIds(true);
125
126         if (savedInstanceState != null) {
127             suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
128             category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);
129             suggestionConditionMode = savedInstanceState.getInt(
130                 STATE_SUGGESTION_CONDITION_MODE, suggestionConditionMode);
131             mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
132                     STATE_SUGGESTIONS_SHOWN_LOGGED);
133         } else {
134             mSuggestionsShownLogged = new ArrayList<>();
135         }
136
137         mDashboardData = new DashboardData.Builder()
138                 .setConditions(conditions)
139                 .setSuggestions(suggestions)
140                 .setCategory(category)
141                 .setSuggestionConditionMode(suggestionConditionMode)
142                 .build();
143     }
144
145     public List<Tile> getSuggestions() {
146         return mDashboardData.getSuggestions();
147     }
148
149     public void setCategoriesAndSuggestions(DashboardCategory category,
150             List<Tile> suggestions) {
151         tintIcons(category, suggestions);
152
153         final DashboardData prevData = mDashboardData;
154         mDashboardData = new DashboardData.Builder(prevData)
155                 .setSuggestions(suggestions)
156                 .setCategory(category)
157                 .build();
158         notifyDashboardDataChanged(prevData);
159         List<Tile> shownSuggestions = null;
160         final int mode = mDashboardData.getSuggestionConditionMode();
161         if (mode == DashboardData.HEADER_MODE_DEFAULT) {
162             shownSuggestions = suggestions.subList(0,
163                     Math.min(suggestions.size(), DashboardData.DEFAULT_SUGGESTION_COUNT));
164         } else if (mode != DashboardData.HEADER_MODE_COLLAPSED) {
165             shownSuggestions = suggestions;
166         }
167         if (shownSuggestions != null) {
168             for (Tile suggestion : shownSuggestions) {
169                 final String identifier = mSuggestionFeatureProvider.getSuggestionIdentifier(
170                         mContext, suggestion);
171                 mMetricsFeatureProvider.action(
172                         mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, identifier);
173                 mSuggestionsShownLogged.add(identifier);
174             }
175         }
176     }
177
178     public void setCategory(DashboardCategory category) {
179         tintIcons(category, null);
180         final DashboardData prevData = mDashboardData;
181         Log.d(TAG, "adapter setCategory called");
182         mDashboardData = new DashboardData.Builder(prevData)
183                 .setCategory(category)
184                 .build();
185         notifyDashboardDataChanged(prevData);
186     }
187
188     public void setConditions(List<Condition> conditions) {
189         final DashboardData prevData = mDashboardData;
190         Log.d(TAG, "adapter setConditions called");
191         mDashboardData = new DashboardData.Builder(prevData)
192                 .setConditions(conditions)
193                 .build();
194         notifyDashboardDataChanged(prevData);
195     }
196
197     public void onSuggestionDismissed() {
198         final List<Tile> suggestions = mDashboardData.getSuggestions();
199         if (suggestions != null && suggestions.size() == 1) {
200             // The only suggestion is dismissed, and the the empty suggestion container will
201             // remain as the dashboard item. Need to refresh the dashboard list.
202             final DashboardData prevData = mDashboardData;
203             mDashboardData = new DashboardData.Builder(prevData)
204                     .setSuggestions(null)
205                     .build();
206             notifyDashboardDataChanged(prevData);
207         }
208     }
209
210     @Override
211     public void notifySummaryChanged(Tile tile) {
212         final int position = mDashboardData.getPositionByTile(tile);
213         if (position != DashboardData.POSITION_NOT_FOUND) {
214             // Since usually tile in parameter and tile in mCategories are same instance,
215             // which is hard to be detected by DiffUtil, so we notifyItemChanged directly.
216             notifyItemChanged(position, mDashboardData.getItemTypeByPosition(position));
217         }
218     }
219
220     @Override
221     public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
222         final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
223         if (viewType == R.layout.suggestion_condition_header) {
224             return new SuggestionAndConditionHeaderHolder(view);
225         }
226         if (viewType == R.layout.suggestion_condition_container) {
227             return new SuggestionAndConditionContainerHolder(view);
228         }
229         return new DashboardItemHolder(view);
230     }
231
232     @Override
233     public void onBindViewHolder(DashboardItemHolder holder, int position) {
234         final int type = mDashboardData.getItemTypeByPosition(position);
235         switch (type) {
236             case R.layout.dashboard_tile:
237                 final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
238                 onBindTile(holder, tile);
239                 holder.itemView.setTag(tile);
240                 holder.itemView.setOnClickListener(mTileClickListener);
241                 break;
242             case R.layout.suggestion_condition_container:
243                 onBindConditionAndSuggestion(
244                     (SuggestionAndConditionContainerHolder) holder, position);
245                 break;
246             case R.layout.suggestion_condition_header:
247                 onBindSuggestionConditionHeader((SuggestionAndConditionHeaderHolder) holder,
248                         (SuggestionConditionHeaderData)
249                                 mDashboardData.getItemEntityByPosition(position));
250                 break;
251             case R.layout.suggestion_condition_footer:
252                 holder.itemView.setOnClickListener(v -> {
253                     mMetricsFeatureProvider.action(mContext,
254                             MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
255                     DashboardData prevData = mDashboardData;
256                     mDashboardData = new DashboardData.Builder(prevData).setSuggestionConditionMode(
257                         DashboardData.HEADER_MODE_COLLAPSED).build();
258                     notifyDashboardDataChanged(prevData);
259                     mRecyclerView.scrollToPosition(SUGGESTION_CONDITION_HEADER_POSITION);
260                 });
261                 break;
262         }
263     }
264
265     @Override
266     public long getItemId(int position) {
267         return mDashboardData.getItemIdByPosition(position);
268     }
269
270     @Override
271     public int getItemViewType(int position) {
272         return mDashboardData.getItemTypeByPosition(position);
273     }
274
275     @Override
276     public int getItemCount() {
277         return mDashboardData.size();
278     }
279
280     @Override
281     public void onAttachedToRecyclerView(RecyclerView recyclerView) {
282         super.onAttachedToRecyclerView(recyclerView);
283         // save the view so that we can scroll it when expanding/collapsing the suggestion and
284         // conditions.
285         mRecyclerView = recyclerView;
286     }
287
288     public void onPause() {
289         if (mDashboardData.getSuggestions() == null) {
290             return;
291         }
292         for (Tile suggestion : mDashboardData.getSuggestions()) {
293             String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
294                     mContext, suggestion);
295             if (mSuggestionsShownLogged.contains(suggestionId)) {
296                 mMetricsFeatureProvider.action(
297                         mContext, MetricsEvent.ACTION_HIDE_SETTINGS_SUGGESTION, suggestionId);
298             }
299         }
300         mSuggestionsShownLogged.clear();
301     }
302
303     public Object getItem(long itemId) {
304         return mDashboardData.getItemEntityById(itemId);
305     }
306
307     public Tile getSuggestion(int position) {
308         return mSuggestionAdapter.getSuggestion(position);
309     }
310
311     @VisibleForTesting
312     void notifyDashboardDataChanged(DashboardData prevData) {
313         if (mFirstFrameDrawn && prevData != null) {
314             final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardData
315                     .ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
316             diffResult.dispatchUpdatesTo(this);
317         } else {
318             mFirstFrameDrawn = true;
319             notifyDataSetChanged();
320         }
321     }
322
323     private void logSuggestions() {
324         for (Tile suggestion : mDashboardData.getSuggestions()) {
325             final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
326                 mContext, suggestion);
327             if (!mSuggestionsShownLogged.contains(suggestionId)) {
328                 mMetricsFeatureProvider.action(
329                     mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION,
330                     suggestionId);
331                 mSuggestionsShownLogged.add(suggestionId);
332             }
333         }
334     }
335
336     @VisibleForTesting
337     void onBindSuggestionConditionHeader(final SuggestionAndConditionHeaderHolder holder,
338             SuggestionConditionHeaderData data) {
339         final int curMode = mDashboardData.getSuggestionConditionMode();
340         final int nextMode = data.hiddenSuggestionCount > 0 && data.conditionCount > 0
341             && curMode != DashboardData.HEADER_MODE_SUGGESTION_EXPANDED
342             ? DashboardData.HEADER_MODE_SUGGESTION_EXPANDED
343             : DashboardData.HEADER_MODE_FULLY_EXPANDED;
344         final boolean moreSuggestions = data.hiddenSuggestionCount > 0;
345         final boolean hasConditions = data.conditionCount > 0;
346         if (data.conditionCount > 0) {
347             holder.icon.setImageIcon(data.conditionIcons.get(0));
348             holder.icon.setVisibility(View.VISIBLE);
349             if (data.conditionCount == 1) {
350                 holder.title.setText(data.title);
351                 holder.title.setTextColor(Utils.getColorAccent(mContext));
352                 holder.icons.setVisibility(View.INVISIBLE);
353             } else {
354                 holder.title.setText(null);
355                 updateConditionIcons(data.conditionIcons, holder.icons);
356                 holder.icons.setVisibility(View.VISIBLE);
357             }
358         } else {
359             holder.icon.setVisibility(View.INVISIBLE);
360             holder.icons.setVisibility(View.INVISIBLE);
361         }
362
363         if (data.hiddenSuggestionCount > 0) {
364             holder.summary.setTextColor(Color.BLACK);
365             if (curMode == DashboardData.HEADER_MODE_COLLAPSED) {
366                 if (data.conditionCount > 0) {
367                     holder.summary.setText(mContext.getResources().getQuantityString(
368                         R.plurals.suggestions_collapsed_summary,
369                         data.hiddenSuggestionCount, data.hiddenSuggestionCount));
370                 } else {
371                     holder.title.setText(mContext.getResources().getQuantityString(
372                         R.plurals.suggestions_collapsed_title,
373                         data.hiddenSuggestionCount, data.hiddenSuggestionCount));
374                     holder.title.setTextColor(Color.BLACK);
375                     holder.summary.setText(null);
376                 }
377             } else if (curMode == DashboardData.HEADER_MODE_DEFAULT) {
378                 if (data.conditionCount > 0) {
379                     holder.summary.setText(mContext.getString(
380                         R.string.suggestions_summary, data.hiddenSuggestionCount));
381                 } else {
382                     holder.title.setText(mContext.getString(
383                         R.string.suggestions_more_title, data.hiddenSuggestionCount));
384                     holder.title.setTextColor(Color.BLACK);
385                     holder.summary.setText(null);
386                 }
387             }
388         } else if (data.conditionCount > 1) {
389             holder.summary.setTextColor(Utils.getColorAccent(mContext));
390             holder.summary.setText(
391                 mContext.getString(R.string.condition_summary, data.conditionCount));
392         } else {
393             holder.summary.setText(null);
394         }
395
396         final Resources res = mContext.getResources();
397         final int padding = res.getDimensionPixelOffset(
398                 curMode == DashboardData.HEADER_MODE_COLLAPSED
399                         ? R.dimen.suggestion_condition_header_padding_collapsed
400                         : R.dimen.suggestion_condition_header_padding_expanded);
401         holder.itemView.setPadding(0, padding, 0, padding);
402
403         holder.itemView.setOnClickListener(v -> {
404             if (moreSuggestions ) {
405                 logSuggestions();
406             } else if (hasConditions) {
407                 mMetricsFeatureProvider.action(mContext,
408                     MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
409             }
410             DashboardData prevData = mDashboardData;
411             final boolean wasCollapsed = curMode == DashboardData.HEADER_MODE_COLLAPSED;
412             mDashboardData = new DashboardData.Builder(prevData)
413                 .setSuggestionConditionMode(nextMode).build();
414             notifyDashboardDataChanged(prevData);
415             if (wasCollapsed) {
416                 mRecyclerView.scrollToPosition(SUGGESTION_CONDITION_HEADER_POSITION);
417             }
418         });
419     }
420
421     @VisibleForTesting
422     void onBindConditionAndSuggestion(final SuggestionAndConditionContainerHolder holder,
423             int position) {
424         // If there is suggestions to show, it will be at position 0 as we don't show the suggestion
425         // header anymore.
426         final List<Tile> suggestions = mDashboardData.getSuggestions();
427         if (position == SUGGESTION_CONDITION_HEADER_POSITION
428                 && suggestions != null && suggestions.size() > 0) {
429             mSuggestionAdapter = new SuggestionAdapter(mContext, (List<Tile>)
430                 mDashboardData.getItemEntityByPosition(position), mSuggestionsShownLogged);
431             mSuggestionDismissHandler = new SuggestionDismissController(mContext,
432                 holder.data, mSuggestionParser, mCallback);
433             holder.data.setAdapter(mSuggestionAdapter);
434         } else {
435             ConditionAdapter adapter = new ConditionAdapter(mContext,
436                 (List<Condition>) mDashboardData.getItemEntityByPosition(position),
437                     mDashboardData.getSuggestionConditionMode());
438             adapter.addDismissHandling(holder.data);
439             holder.data.setAdapter(adapter);
440         }
441         holder.data.setLayoutManager(new LinearLayoutManager(mContext));
442     }
443
444     private void onBindTile(DashboardItemHolder holder, Tile tile) {
445         if (tile.remoteViews != null) {
446             final ViewGroup itemView = (ViewGroup) holder.itemView;
447             itemView.removeAllViews();
448             itemView.addView(tile.remoteViews.apply(itemView.getContext(), itemView));
449         } else {
450             holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
451             holder.title.setText(tile.title);
452             if (!TextUtils.isEmpty(tile.summary)) {
453                 holder.summary.setText(tile.summary);
454                 holder.summary.setVisibility(View.VISIBLE);
455             } else {
456                 holder.summary.setVisibility(View.GONE);
457             }
458         }
459     }
460
461     private void tintIcons(DashboardCategory category, List<Tile> suggestions) {
462         if (!mDashboardFeatureProvider.shouldTintIcon()) {
463             return;
464         }
465         // TODO: Better place for tinting?
466         final TypedArray a = mContext.obtainStyledAttributes(new int[]{
467                 android.R.attr.colorControlNormal});
468         final int tintColor = a.getColor(0, mContext.getColor(R.color.fallback_tintColor));
469         a.recycle();
470         if (category != null) {
471             for (Tile tile : category.tiles) {
472                 if (tile.isIconTintable) {
473                     // If this drawable is tintable, tint it to match the color.
474                     tile.icon.setTint(tintColor);
475                 }
476             }
477         }
478         if (suggestions != null) {
479             for (Tile suggestion : suggestions) {
480                 if (suggestion.isIconTintable) {
481                     suggestion.icon.setTint(tintColor);
482                 }
483             }
484         }
485     }
486
487     void onSaveInstanceState(Bundle outState) {
488         final List<Tile> suggestions = mDashboardData.getSuggestions();
489         final DashboardCategory category = mDashboardData.getCategory();
490         if (suggestions != null) {
491             outState.putParcelableArrayList(STATE_SUGGESTION_LIST, new ArrayList<>(suggestions));
492         }
493         if (category != null) {
494             outState.putParcelable(STATE_CATEGORY_LIST, category);
495         }
496         outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
497         outState.putInt(STATE_SUGGESTION_CONDITION_MODE,
498             mDashboardData.getSuggestionConditionMode());
499     }
500
501     private void updateConditionIcons(List<Icon> icons, ViewGroup parent) {
502         if (icons == null || icons.size() < 2) {
503             parent.setVisibility(View.INVISIBLE);
504             return;
505         }
506         final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
507         parent.removeAllViews();
508         for (int i = 1, size = icons.size(); i < size; i++) {
509             ImageView icon = (ImageView) inflater.inflate(
510                 R.layout.condition_header_icon, parent, false);
511             icon.setImageIcon(icons.get(i));
512             parent.addView(icon);
513         }
514         parent.setVisibility(View.VISIBLE);
515     }
516
517     public static class IconCache {
518         private final Context mContext;
519         private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
520
521         public IconCache(Context context) {
522             mContext = context;
523         }
524
525         public Drawable getIcon(Icon icon) {
526             Drawable drawable = mMap.get(icon);
527             if (drawable == null) {
528                 drawable = icon.loadDrawable(mContext);
529                 mMap.put(icon, drawable);
530             }
531             return drawable;
532         }
533     }
534
535     public static class DashboardItemHolder extends RecyclerView.ViewHolder {
536         public final ImageView icon;
537         public final TextView title;
538         public final TextView summary;
539
540         public DashboardItemHolder(View itemView) {
541             super(itemView);
542             icon = itemView.findViewById(android.R.id.icon);
543             title = itemView.findViewById(android.R.id.title);
544             summary = itemView.findViewById(android.R.id.summary);
545         }
546     }
547
548     public static class SuggestionAndConditionHeaderHolder extends DashboardItemHolder {
549         public final LinearLayout icons;
550         public final ImageView expandIndicator;
551
552         public SuggestionAndConditionHeaderHolder(View itemView) {
553             super(itemView);
554             icons = itemView.findViewById(id.additional_icons);
555             expandIndicator = itemView.findViewById(id.expand_indicator);
556         }
557     }
558
559     public static class SuggestionAndConditionContainerHolder extends DashboardItemHolder {
560         public final RecyclerView data;
561
562         public SuggestionAndConditionContainerHolder(View itemView) {
563             super(itemView);
564             data = itemView.findViewById(id.data);
565         }
566     }
567
568 }