2 * Copyright (C) 2018 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.suggestions;
18 import android.app.PendingIntent;
19 import android.content.Context;
20 import android.content.res.Resources;
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.v7.widget.RecyclerView;
26 import android.text.TextUtils;
27 import android.util.Log;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.ImageView;
32 import android.widget.LinearLayout;
34 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
35 import com.android.settings.R;
36 import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder;
37 import com.android.settings.dashboard.DashboardAdapterV2.IconCache;
38 import com.android.settings.overlay.FeatureFactory;
39 import com.android.settingslib.Utils;
40 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
41 import com.android.settingslib.core.lifecycle.Lifecycle;
42 import com.android.settingslib.core.lifecycle.LifecycleObserver;
43 import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
44 import com.android.settingslib.suggestions.SuggestionControllerMixin;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Objects;
50 public class SuggestionAdapterV2 extends RecyclerView.Adapter<DashboardItemHolder> implements
51 LifecycleObserver, OnSaveInstanceState {
52 public static final String TAG = "SuggestionAdapterV2";
54 private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
55 private static final String STATE_SUGGESTION_LIST = "suggestion_list";
57 private final Context mContext;
58 private final MetricsFeatureProvider mMetricsFeatureProvider;
59 private final IconCache mCache;
60 private final ArrayList<String> mSuggestionsShownLogged;
61 private final SuggestionFeatureProvider mSuggestionFeatureProvider;
62 private final SuggestionControllerMixin mSuggestionControllerMixin;
63 private final Callback mCallback;
64 private final CardConfig mConfig;
66 private List<Suggestion> mSuggestions;
68 public interface Callback {
70 * Called when the close button of the suggestion card is clicked.
72 void onSuggestionClosed(Suggestion suggestion);
75 public SuggestionAdapterV2(Context context, SuggestionControllerMixin suggestionControllerMixin,
76 Bundle savedInstanceState, Callback callback, Lifecycle lifecycle) {
78 mSuggestionControllerMixin = suggestionControllerMixin;
79 mCache = new IconCache(context);
80 final FeatureFactory factory = FeatureFactory.getFactory(context);
81 mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
82 mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
84 if (savedInstanceState != null) {
85 mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
86 mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
87 STATE_SUGGESTIONS_SHOWN_LOGGED);
89 mSuggestionsShownLogged = new ArrayList<>();
92 if (lifecycle != null) {
93 lifecycle.addObserver(this);
95 mConfig = CardConfig.get(context);
97 setHasStableIds(true);
101 public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
102 return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
103 viewType, parent, false));
107 public void onBindViewHolder(DashboardItemHolder holder, int position) {
108 final Suggestion suggestion = mSuggestions.get(position);
109 final String id = suggestion.getId();
110 final int suggestionCount = mSuggestions.size();
111 if (!mSuggestionsShownLogged.contains(id)) {
112 mMetricsFeatureProvider.action(
113 mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
114 mSuggestionsShownLogged.add(id);
116 mConfig.setCardLayout(holder, suggestionCount, position);
117 final Icon icon = suggestion.getIcon();
118 final Drawable drawable = mCache.getIcon(icon);
119 if (drawable != null && TextUtils.equals(icon.getResPackage(), mContext.getPackageName())) {
120 drawable.setTint(Utils.getColorAccent(mContext));
122 holder.icon.setImageDrawable(drawable);
123 holder.title.setText(suggestion.getTitle());
124 holder.title.setSingleLine(suggestionCount == 1);
126 if (suggestionCount == 1) {
127 final CharSequence summary = suggestion.getSummary();
128 if (!TextUtils.isEmpty(summary)) {
129 holder.summary.setText(summary);
130 holder.summary.setVisibility(View.VISIBLE);
132 holder.summary.setVisibility(View.GONE);
135 // Do not show summary if there are more than 1 suggestions
136 holder.summary.setVisibility(View.GONE);
137 holder.title.setMaxLines(3);
140 final ImageView closeButton = holder.itemView.findViewById(R.id.close_button);
141 if (closeButton != null) {
142 closeButton.setOnClickListener(v -> {
143 mSuggestionFeatureProvider.dismissSuggestion(
144 mContext, mSuggestionControllerMixin, suggestion);
145 if (mCallback != null) {
146 mCallback.onSuggestionClosed(suggestion);
151 View clickHandler = holder.itemView;
152 // If a view with @android:id/primary is defined, use that as the click handler
154 final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
155 if (primaryAction != null) {
156 clickHandler = primaryAction;
158 clickHandler.setOnClickListener(v -> {
159 mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
161 suggestion.getPendingIntent().send();
162 mSuggestionControllerMixin.launchSuggestion(suggestion);
163 } catch (PendingIntent.CanceledException e) {
164 Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
170 public long getItemId(int position) {
171 return Objects.hash(mSuggestions.get(position).getId());
175 public int getItemViewType(int position) {
176 final Suggestion suggestion = getSuggestion(position);
177 if ((suggestion.getFlags() & Suggestion.FLAG_HAS_BUTTON) != 0) {
178 return R.layout.suggestion_tile_with_button_v2;
180 return R.layout.suggestion_tile_v2;
185 public int getItemCount() {
186 return mSuggestions.size();
189 public Suggestion getSuggestion(int position) {
190 final long itemId = getItemId(position);
191 if (mSuggestions == null) {
194 for (Suggestion suggestion : mSuggestions) {
195 if (Objects.hash(suggestion.getId()) == itemId) {
202 public void removeSuggestion(Suggestion suggestion) {
203 final int position = mSuggestions.indexOf(suggestion);
204 mSuggestions.remove(suggestion);
205 notifyItemRemoved(position);
209 public void onSaveInstanceState(Bundle outState) {
210 if (mSuggestions != null) {
211 outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
212 new ArrayList<>(mSuggestions));
214 outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
217 public void setSuggestions(List<Suggestion> suggestions) {
218 mSuggestions = suggestions;
221 public List<Suggestion> getSuggestions() {
225 private static class CardConfig {
226 // Card start/end margin
227 private final int mMarginInner;
228 private final int mMarginOuter;
229 // Card width for different numbers of cards
230 private final int mWidthSingleCard;
231 private final int mWidthTwoCards;
232 private final int mWidthMultipleCards;
233 // padding between icon and title
234 private final int mPaddingTitleTopSingleCard;
235 private final int mPaddingTitleTopMultipleCards;
237 private static CardConfig sConfig;
239 private CardConfig(Context context) {
240 final Resources res = context.getResources();
242 res.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin);
244 res.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin);
245 mWidthSingleCard = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_one_card);
246 mWidthTwoCards = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_two_cards);
247 mWidthMultipleCards =
248 res.getDimensionPixelOffset(R.dimen.suggestion_card_width_multiple_cards);
249 mPaddingTitleTopSingleCard =
250 res.getDimensionPixelOffset(R.dimen.suggestion_card_title_padding_bottom_one_card);
251 mPaddingTitleTopMultipleCards = res.getDimensionPixelOffset(
252 R.dimen.suggestion_card_title_padding_bottom_multiple_cards);
255 public static CardConfig get(Context context) {
256 if (sConfig == null) {
257 sConfig = new CardConfig(context);
262 private void setCardLayout(DashboardItemHolder holder, int suggestionCount,
264 final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
266 ? mWidthSingleCard : suggestionCount == 2
267 ? mWidthTwoCards : mWidthMultipleCards,
268 LinearLayout.LayoutParams.WRAP_CONTENT);
269 if (suggestionCount == 1) {
270 params.setMarginStart(mMarginOuter);
271 params.setMarginEnd(mMarginOuter);
273 params.setMarginStart(
274 position == 0 ? mMarginOuter : mMarginInner);
275 params.setMarginEnd(position == suggestionCount - 1 ? mMarginOuter : 0);
277 holder.itemView.setLayoutParams(params);