OSDN Git Service

Update suggestion card layout
[android-x86/packages-apps-Settings.git] / src / com / android / settings / dashboard / DashboardAdapterV2.java
1 /*
2  * Copyright (C) 2018 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.TypedArray;
21 import android.graphics.drawable.Drawable;
22 import android.graphics.drawable.Icon;
23 import android.os.Bundle;
24 import android.service.settings.suggestions.Suggestion;
25 import android.support.annotation.VisibleForTesting;
26 import android.support.v7.util.DiffUtil;
27 import android.support.v7.widget.LinearLayoutManager;
28 import android.support.v7.widget.RecyclerView;
29 import android.text.TextUtils;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.ImageView;
36 import android.widget.LinearLayout;
37 import android.widget.TextView;
38
39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
40 import com.android.settings.R;
41 import com.android.settings.R.id;
42 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
43 import com.android.settings.dashboard.DashboardDataV2.ConditionHeaderData;
44 import com.android.settings.dashboard.conditional.Condition;
45 import com.android.settings.dashboard.conditional.ConditionAdapterV2;
46 import com.android.settings.dashboard.suggestions.SuggestionAdapterV2;
47 import com.android.settings.dashboard.suggestions.SuggestionControllerMixin;
48 import com.android.settings.overlay.FeatureFactory;
49 import com.android.settingslib.core.lifecycle.Lifecycle;
50 import com.android.settingslib.core.lifecycle.LifecycleObserver;
51 import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
52 import com.android.settingslib.drawer.DashboardCategory;
53 import com.android.settingslib.drawer.Tile;
54
55 import java.util.List;
56
57 public class DashboardAdapterV2 extends RecyclerView.Adapter<DashboardAdapterV2.DashboardItemHolder>
58         implements SummaryLoader.SummaryConsumer, SuggestionAdapterV2.Callback, LifecycleObserver,
59         OnSaveInstanceState {
60     public static final String TAG = "DashboardAdapterV2";
61     private static final String STATE_CATEGORY_LIST = "category_list";
62
63     @VisibleForTesting
64     static final String STATE_CONDITION_EXPANDED = "condition_expanded";
65
66     private final IconCache mCache;
67     private final Context mContext;
68     private final MetricsFeatureProvider mMetricsFeatureProvider;
69     private final DashboardFeatureProvider mDashboardFeatureProvider;
70     private boolean mFirstFrameDrawn;
71     private RecyclerView mRecyclerView;
72     private SuggestionAdapterV2 mSuggestionAdapter;
73
74     @VisibleForTesting
75     DashboardDataV2 mDashboardData;
76
77     private View.OnClickListener mTileClickListener = new View.OnClickListener() {
78         @Override
79         public void onClick(View v) {
80             //TODO: get rid of setTag/getTag
81             mDashboardFeatureProvider.openTileIntent((Activity) mContext, (Tile) v.getTag());
82         }
83     };
84
85     public DashboardAdapterV2(Context context, Bundle savedInstanceState,
86             List<Condition> conditions, SuggestionControllerMixin suggestionControllerMixin,
87             Lifecycle lifecycle) {
88
89         DashboardCategory category = null;
90         boolean conditionExpanded = false;
91
92         mContext = context;
93         final FeatureFactory factory = FeatureFactory.getFactory(context);
94         mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
95         mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
96         mCache = new IconCache(context);
97         mSuggestionAdapter = new SuggestionAdapterV2(mContext, suggestionControllerMixin,
98             savedInstanceState, this /* callback */, lifecycle);
99
100         setHasStableIds(true);
101
102         if (savedInstanceState != null) {
103             category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);
104             conditionExpanded = savedInstanceState.getBoolean(
105                     STATE_CONDITION_EXPANDED, conditionExpanded);
106         }
107
108         if (lifecycle != null) {
109             lifecycle.addObserver(this);
110         }
111
112         mDashboardData = new DashboardDataV2.Builder()
113             .setConditions(conditions)
114             .setSuggestions(mSuggestionAdapter.getSuggestions())
115             .setCategory(category)
116             .setConditionExpanded(conditionExpanded)
117             .build();
118     }
119
120     public void setSuggestions(List<Suggestion> data) {
121         final DashboardDataV2 prevData = mDashboardData;
122         mDashboardData = new DashboardDataV2.Builder(prevData)
123                 .setSuggestions(data)
124                 .build();
125         notifyDashboardDataChanged(prevData);
126     }
127
128     public void setCategory(DashboardCategory category) {
129         tintIcons(category);
130         final DashboardDataV2 prevData = mDashboardData;
131         Log.d(TAG, "adapter setCategory called");
132         mDashboardData = new DashboardDataV2.Builder(prevData)
133                 .setCategory(category)
134                 .build();
135         notifyDashboardDataChanged(prevData);
136     }
137
138     public void setConditions(List<Condition> conditions) {
139         final DashboardDataV2 prevData = mDashboardData;
140         Log.d(TAG, "adapter setConditions called");
141         mDashboardData = new DashboardDataV2.Builder(prevData)
142                 .setConditions(conditions)
143                 .build();
144         notifyDashboardDataChanged(prevData);
145     }
146
147     @Override
148     public void onSuggestionClosed(Suggestion suggestion) {
149         final List<Suggestion> list = mDashboardData.getSuggestions();
150         if (list == null || list.size() == 0) {
151             return;
152         }
153         if (list.size() == 1) {
154             // The only suggestion is dismissed, and the the empty suggestion container will
155             // remain as the dashboard item. Need to refresh the dashboard list.
156             final DashboardDataV2 prevData = mDashboardData;
157             mDashboardData = new DashboardDataV2.Builder(prevData)
158                 .setSuggestions(null)
159                 .build();
160             notifyDashboardDataChanged(prevData);
161         } else {
162             mSuggestionAdapter.removeSuggestion(suggestion);
163         }
164     }
165
166     @Override
167     public void notifySummaryChanged(Tile tile) {
168         final int position = mDashboardData.getPositionByTile(tile);
169         if (position != DashboardDataV2.POSITION_NOT_FOUND) {
170             // Since usually tile in parameter and tile in mCategories are same instance,
171             // which is hard to be detected by DiffUtil, so we notifyItemChanged directly.
172             notifyItemChanged(position, mDashboardData.getItemTypeByPosition(position));
173         }
174     }
175
176     @Override
177     public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
178         final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
179         if (viewType == R.layout.suggestion_condition_header) {
180             return new ConditionHeaderHolder(view);
181         }
182         if (viewType == R.layout.condition_container) {
183             return new ConditionContainerHolder(view);
184         }
185         if (viewType == R.layout.suggestion_container) {
186             return new SuggestionContainerHolder(view);
187         }
188         return new DashboardItemHolder(view);
189     }
190
191     @Override
192     public void onBindViewHolder(DashboardItemHolder holder, int position) {
193         final int type = mDashboardData.getItemTypeByPosition(position);
194         switch (type) {
195             case R.layout.dashboard_tile:
196                 final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
197                 onBindTile((DashboardItemHolder) holder, tile);
198                 holder.itemView.setTag(tile);
199                 holder.itemView.setOnClickListener(mTileClickListener);
200                 break;
201             case R.layout.suggestion_container:
202                 onBindSuggestion((SuggestionContainerHolder) holder, position);
203                 break;
204             case R.layout.condition_container:
205                 onBindCondition((ConditionContainerHolder) holder, position);
206                 break;
207             case R.layout.suggestion_condition_header:
208                 onBindConditionHeader((ConditionHeaderHolder) holder,
209                         (ConditionHeaderData) mDashboardData.getItemEntityByPosition(position));
210                 break;
211             case R.layout.suggestion_condition_footer:
212                 holder.itemView.setOnClickListener(v -> {
213                     mMetricsFeatureProvider.action(mContext,
214                             MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
215                     DashboardDataV2 prevData = mDashboardData;
216                     mDashboardData = new DashboardDataV2.Builder(prevData).
217                         setConditionExpanded(false).build();
218                     notifyDashboardDataChanged(prevData);
219                     scrollToTopOfConditions();
220                 });
221                 break;
222         }
223     }
224
225     @Override
226     public long getItemId(int position) {
227         return mDashboardData.getItemIdByPosition(position);
228     }
229
230     @Override
231     public int getItemViewType(int position) {
232         return mDashboardData.getItemTypeByPosition(position);
233     }
234
235     @Override
236     public int getItemCount() {
237         return mDashboardData.size();
238     }
239
240     @Override
241     public void onAttachedToRecyclerView(RecyclerView recyclerView) {
242         super.onAttachedToRecyclerView(recyclerView);
243         // save the view so that we can scroll it when expanding/collapsing the suggestion and
244         // conditions.
245         mRecyclerView = recyclerView;
246     }
247
248     public Object getItem(long itemId) {
249         return mDashboardData.getItemEntityById(itemId);
250     }
251
252     public Suggestion getSuggestion(int position) {
253         return mSuggestionAdapter.getSuggestion(position);
254     }
255
256     @VisibleForTesting
257     void notifyDashboardDataChanged(DashboardDataV2 prevData) {
258         if (mFirstFrameDrawn && prevData != null) {
259             final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardDataV2
260                     .ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
261             diffResult.dispatchUpdatesTo(this);
262         } else {
263             mFirstFrameDrawn = true;
264             notifyDataSetChanged();
265         }
266     }
267
268     @VisibleForTesting
269     void onBindConditionHeader(final ConditionHeaderHolder holder, ConditionHeaderData data) {
270         holder.icon.setImageIcon(data.conditionIcons.get(0));
271         if (data.conditionCount == 1) {
272             holder.title.setText(data.title);
273             holder.summary.setText(null);
274             holder.icons.setVisibility(View.INVISIBLE);
275         } else {
276             holder.title.setText(null);
277             holder.summary.setText(
278                 mContext.getString(R.string.condition_summary, data.conditionCount));
279             updateConditionIcons(data.conditionIcons, holder.icons);
280             holder.icons.setVisibility(View.VISIBLE);
281         }
282
283         holder.itemView.setOnClickListener(v -> {
284             mMetricsFeatureProvider.action(mContext,
285                 MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
286             final DashboardDataV2 prevData = mDashboardData;
287             mDashboardData = new DashboardDataV2.Builder(prevData)
288                     .setConditionExpanded(true).build();
289             notifyDashboardDataChanged(prevData);
290             scrollToTopOfConditions();
291         });
292     }
293
294     @VisibleForTesting
295     void onBindCondition(final ConditionContainerHolder holder, int position) {
296         final ConditionAdapterV2 adapter = new ConditionAdapterV2(mContext,
297             (List<Condition>) mDashboardData.getItemEntityByPosition(position),
298             mDashboardData.isConditionExpanded());
299         adapter.addDismissHandling(holder.data);
300         holder.data.setAdapter(adapter);
301         holder.data.setLayoutManager(new LinearLayoutManager(mContext));
302     }
303
304     @VisibleForTesting
305     void onBindSuggestion(final SuggestionContainerHolder holder, int position) {
306         // If there is suggestions to show, it will be at position 0 as we don't show the suggestion
307         // header anymore.
308         final List<Suggestion> suggestions =
309             (List<Suggestion>) mDashboardData.getItemEntityByPosition(position);
310         final int suggestionCount = suggestions.size();
311         if (suggestions != null && suggestionCount > 0) {
312             holder.summary.setText(""+suggestionCount);
313             mSuggestionAdapter.setSuggestions(suggestions);
314             holder.data.setAdapter(mSuggestionAdapter);
315         }
316         final LinearLayoutManager layoutManager = new LinearLayoutManager(mContext);
317         layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
318         holder.data.setLayoutManager(layoutManager);
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 tintIcons(DashboardCategory category) {
333         if (!mDashboardFeatureProvider.shouldTintIcon()) {
334             return;
335         }
336         // TODO: Better place for tinting?
337         final TypedArray a = mContext.obtainStyledAttributes(new int[]{
338                 android.R.attr.colorControlNormal});
339         final int tintColor = a.getColor(0, mContext.getColor(R.color.fallback_tintColor));
340         a.recycle();
341         if (category != null) {
342             for (Tile tile : category.getTiles()) {
343                 if (tile.isIconTintable) {
344                     // If this drawable is tintable, tint it to match the color.
345                     tile.icon.setTint(tintColor);
346                 }
347             }
348         }
349     }
350
351     @Override
352     public void onSaveInstanceState(Bundle outState) {
353         final DashboardCategory category = mDashboardData.getCategory();
354         if (category != null) {
355             outState.putParcelable(STATE_CATEGORY_LIST, category);
356         }
357         outState.putBoolean(STATE_CONDITION_EXPANDED, mDashboardData.isConditionExpanded());
358     }
359
360     private void updateConditionIcons(List<Icon> icons, ViewGroup parent) {
361         if (icons == null || icons.size() < 2) {
362             parent.setVisibility(View.INVISIBLE);
363             return;
364         }
365         final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
366         parent.removeAllViews();
367         for (int i = 1, size = icons.size(); i < size; i++) {
368             ImageView icon = (ImageView) inflater.inflate(
369                     R.layout.condition_header_icon, parent, false);
370             icon.setImageIcon(icons.get(i));
371             parent.addView(icon);
372         }
373         parent.setVisibility(View.VISIBLE);
374     }
375
376     private void scrollToTopOfConditions() {
377         mRecyclerView.scrollToPosition(mDashboardData.hasSuggestion() ? 1 : 0);
378     }
379
380     public static class IconCache {
381         private final Context mContext;
382         private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
383
384         public IconCache(Context context) {
385             mContext = context;
386         }
387
388         public Drawable getIcon(Icon icon) {
389             if (icon == null) {
390                 return null;
391             }
392             Drawable drawable = mMap.get(icon);
393             if (drawable == null) {
394                 drawable = icon.loadDrawable(mContext);
395                 mMap.put(icon, drawable);
396             }
397             return drawable;
398         }
399     }
400
401     public static class DashboardItemHolder extends RecyclerView.ViewHolder {
402         public final ImageView icon;
403         public final TextView title;
404         public final TextView summary;
405
406         public DashboardItemHolder(View itemView) {
407             super(itemView);
408             icon = itemView.findViewById(android.R.id.icon);
409             title = itemView.findViewById(android.R.id.title);
410             summary = itemView.findViewById(android.R.id.summary);
411         }
412     }
413
414     public static class ConditionHeaderHolder extends DashboardItemHolder {
415         public final LinearLayout icons;
416         public final ImageView expandIndicator;
417
418         public ConditionHeaderHolder(View itemView) {
419             super(itemView);
420             icons = itemView.findViewById(id.additional_icons);
421             expandIndicator = itemView.findViewById(id.expand_indicator);
422         }
423     }
424
425     public static class ConditionContainerHolder extends DashboardItemHolder {
426         public final RecyclerView data;
427
428         public ConditionContainerHolder(View itemView) {
429             super(itemView);
430             data = itemView.findViewById(id.data);
431         }
432     }
433
434     public static class SuggestionContainerHolder extends DashboardItemHolder {
435         public final RecyclerView data;
436
437         public SuggestionContainerHolder(View itemView) {
438             super(itemView);
439             data = itemView.findViewById(id.suggestion_list);
440         }
441     }
442
443 }