blob: c027f85caeb61b6001261f62cf49565b60f94f08 [file] [log] [blame]
Doris Linga213bf02018-01-10 17:26:48 -08001/*
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 */
16package com.android.settings.dashboard.suggestions;
17
18import android.app.PendingIntent;
19import android.content.Context;
20import android.content.res.Resources;
Doris Lingc04163a2018-01-29 13:11:57 -080021import android.graphics.drawable.Drawable;
22import android.graphics.drawable.Icon;
Doris Linga213bf02018-01-10 17:26:48 -080023import android.os.Bundle;
24import android.service.settings.suggestions.Suggestion;
25import android.support.v7.widget.RecyclerView;
26import android.text.TextUtils;
27import android.util.Log;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.ImageView;
32import android.widget.LinearLayout;
33
34import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
35import com.android.settings.R;
Doris Linga213bf02018-01-10 17:26:48 -080036import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder;
37import com.android.settings.dashboard.DashboardAdapterV2.IconCache;
38import com.android.settings.overlay.FeatureFactory;
Doris Lingc04163a2018-01-29 13:11:57 -080039import com.android.settingslib.Utils;
Leif Hendrik Wilden28dee1f2018-01-11 10:15:36 -080040import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
Doris Linga213bf02018-01-10 17:26:48 -080041import com.android.settingslib.core.lifecycle.Lifecycle;
42import com.android.settingslib.core.lifecycle.LifecycleObserver;
43import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
Zhizhi Liucaeb1722018-01-18 14:05:58 -080044import com.android.settingslib.suggestions.SuggestionControllerMixin;
Doris Linga213bf02018-01-10 17:26:48 -080045
46import java.util.ArrayList;
47import java.util.List;
48import java.util.Objects;
49
50public class SuggestionAdapterV2 extends RecyclerView.Adapter<DashboardItemHolder> implements
51 LifecycleObserver, OnSaveInstanceState {
52 public static final String TAG = "SuggestionAdapterV2";
53
54 private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
55 private static final String STATE_SUGGESTION_LIST = "suggestion_list";
56
57 private final Context mContext;
58 private final MetricsFeatureProvider mMetricsFeatureProvider;
59 private final IconCache mCache;
60 private final ArrayList<String> mSuggestionsShownLogged;
61 private final SuggestionControllerMixin mSuggestionControllerMixin;
62 private final Callback mCallback;
Doris Ling964b36d2018-01-18 11:36:21 -080063 private final CardConfig mConfig;
Doris Linga213bf02018-01-10 17:26:48 -080064
65 private List<Suggestion> mSuggestions;
66
67 public interface Callback {
68 /**
69 * Called when the close button of the suggestion card is clicked.
70 */
71 void onSuggestionClosed(Suggestion suggestion);
72 }
73
74 public SuggestionAdapterV2(Context context, SuggestionControllerMixin suggestionControllerMixin,
75 Bundle savedInstanceState, Callback callback, Lifecycle lifecycle) {
76 mContext = context;
77 mSuggestionControllerMixin = suggestionControllerMixin;
78 mCache = new IconCache(context);
79 final FeatureFactory factory = FeatureFactory.getFactory(context);
80 mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
81 mCallback = callback;
82 if (savedInstanceState != null) {
83 mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
84 mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
85 STATE_SUGGESTIONS_SHOWN_LOGGED);
86 } else {
87 mSuggestionsShownLogged = new ArrayList<>();
88 }
89
90 if (lifecycle != null) {
91 lifecycle.addObserver(this);
92 }
Doris Ling964b36d2018-01-18 11:36:21 -080093 mConfig = CardConfig.get(context);
Doris Linga213bf02018-01-10 17:26:48 -080094
95 setHasStableIds(true);
96 }
97
98 @Override
99 public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
100 return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
101 viewType, parent, false));
102 }
103
104 @Override
105 public void onBindViewHolder(DashboardItemHolder holder, int position) {
106 final Suggestion suggestion = mSuggestions.get(position);
107 final String id = suggestion.getId();
108 final int suggestionCount = mSuggestions.size();
109 if (!mSuggestionsShownLogged.contains(id)) {
110 mMetricsFeatureProvider.action(
111 mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
112 mSuggestionsShownLogged.add(id);
113 }
Doris Ling964b36d2018-01-18 11:36:21 -0800114 mConfig.setCardLayout(holder, suggestionCount, position);
Doris Lingc04163a2018-01-29 13:11:57 -0800115 final Icon icon = suggestion.getIcon();
116 final Drawable drawable = mCache.getIcon(icon);
117 if (drawable != null && TextUtils.equals(icon.getResPackage(), mContext.getPackageName())) {
118 drawable.setTint(Utils.getColorAccent(mContext));
119 }
120 holder.icon.setImageDrawable(drawable);
Doris Linga213bf02018-01-10 17:26:48 -0800121 holder.title.setText(suggestion.getTitle());
122 holder.title.setSingleLine(suggestionCount == 1);
123
124 if (suggestionCount == 1) {
125 final CharSequence summary = suggestion.getSummary();
126 if (!TextUtils.isEmpty(summary)) {
127 holder.summary.setText(summary);
128 holder.summary.setVisibility(View.VISIBLE);
129 } else {
130 holder.summary.setVisibility(View.GONE);
131 }
132 } else {
133 // Do not show summary if there are more than 1 suggestions
134 holder.summary.setVisibility(View.GONE);
135 holder.title.setMaxLines(3);
136 }
137
138 final ImageView closeButton = holder.itemView.findViewById(R.id.close_button);
139 if (closeButton != null) {
140 if (mCallback != null) {
141 closeButton.setOnClickListener(v -> {
142 mCallback.onSuggestionClosed(suggestion);
143 });
144 } else {
145 closeButton.setOnClickListener(null);
146 }
147 }
148
149 View clickHandler = holder.itemView;
150 // If a view with @android:id/primary is defined, use that as the click handler
151 // instead.
152 final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
153 if (primaryAction != null) {
154 clickHandler = primaryAction;
155 }
156 clickHandler.setOnClickListener(v -> {
157 mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
158 try {
159 suggestion.getPendingIntent().send();
160 mSuggestionControllerMixin.launchSuggestion(suggestion);
161 } catch (PendingIntent.CanceledException e) {
162 Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
163 }
164 });
165 }
166
167 @Override
168 public long getItemId(int position) {
169 return Objects.hash(mSuggestions.get(position).getId());
170 }
171
172 @Override
173 public int getItemViewType(int position) {
174 final Suggestion suggestion = getSuggestion(position);
175 if ((suggestion.getFlags() & Suggestion.FLAG_HAS_BUTTON) != 0) {
176 return R.layout.suggestion_tile_with_button_v2;
177 } else {
178 return R.layout.suggestion_tile_v2;
179 }
180 }
181
182 @Override
183 public int getItemCount() {
184 return mSuggestions.size();
185 }
186
187 public Suggestion getSuggestion(int position) {
188 final long itemId = getItemId(position);
189 if (mSuggestions == null) {
190 return null;
191 }
192 for (Suggestion suggestion : mSuggestions) {
193 if (Objects.hash(suggestion.getId()) == itemId) {
194 return suggestion;
195 }
196 }
197 return null;
198 }
199
200 public void removeSuggestion(Suggestion suggestion) {
201 final int position = mSuggestions.indexOf(suggestion);
202 mSuggestions.remove(suggestion);
203 notifyItemRemoved(position);
204 }
205
206 @Override
207 public void onSaveInstanceState(Bundle outState) {
208 if (mSuggestions != null) {
209 outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
210 new ArrayList<>(mSuggestions));
211 }
212 outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
213 }
214
215 public void setSuggestions(List<Suggestion> suggestions) {
216 mSuggestions = suggestions;
217 }
218
219 public List<Suggestion> getSuggestions() {
220 return mSuggestions;
221 }
222
Doris Ling964b36d2018-01-18 11:36:21 -0800223 private static class CardConfig {
224 // Card start/end margin
225 private final int mMarginInner;
226 private final int mMarginOuter;
227 // Card width for different numbers of cards
228 private final int mWidthSingleCard;
229 private final int mWidthTwoCards;
230 private final int mWidthMultipleCards;
231 // padding between icon and title
232 private final int mPaddingTitleTopSingleCard;
233 private final int mPaddingTitleTopMultipleCards;
234
235 private static CardConfig sConfig;
236
237 private CardConfig(Context context) {
238 final Resources res = context.getResources();
239 mMarginInner =
240 res.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin);
241 mMarginOuter =
242 res.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin);
243 mWidthSingleCard = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_one_card);
244 mWidthTwoCards = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_two_cards);
245 mWidthMultipleCards =
246 res.getDimensionPixelOffset(R.dimen.suggestion_card_width_multiple_cards);
247 mPaddingTitleTopSingleCard =
248 res.getDimensionPixelOffset(R.dimen.suggestion_card_title_padding_bottom_one_card);
249 mPaddingTitleTopMultipleCards = res.getDimensionPixelOffset(
250 R.dimen.suggestion_card_title_padding_bottom_multiple_cards);
251 }
252
253 public static CardConfig get(Context context) {
254 if (sConfig == null) {
255 sConfig = new CardConfig(context);
256 }
257 return sConfig;
258 }
259
260 private void setCardLayout(DashboardItemHolder holder, int suggestionCount,
261 int position) {
262 final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
263 suggestionCount == 1
264 ? mWidthSingleCard : suggestionCount == 2
265 ? mWidthTwoCards : mWidthMultipleCards,
266 LinearLayout.LayoutParams.WRAP_CONTENT);
267 if (suggestionCount == 1) {
268 params.setMarginStart(mMarginOuter);
269 params.setMarginEnd(mMarginOuter);
270 } else {
271 params.setMarginStart(
272 position == 0 ? mMarginOuter : mMarginInner);
273 params.setMarginEnd(position == suggestionCount - 1 ? mMarginOuter : 0);
274 }
275 holder.itemView.setLayoutParams(params);
276 }
277
Doris Linga213bf02018-01-10 17:26:48 -0800278 }
Doris Ling964b36d2018-01-18 11:36:21 -0800279
Doris Linga213bf02018-01-10 17:26:48 -0800280}