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.os.Bundle;
22 import android.service.settings.suggestions.Suggestion;
23 import android.support.v7.widget.RecyclerView;
24 import android.text.TextUtils;
25 import android.util.Log;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
32 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
33 import com.android.settings.R;
34 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
35 import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder;
36 import com.android.settings.dashboard.DashboardAdapterV2.IconCache;
37 import com.android.settings.overlay.FeatureFactory;
38 import com.android.settingslib.core.lifecycle.Lifecycle;
39 import com.android.settingslib.core.lifecycle.LifecycleObserver;
40 import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Objects;
46 public class SuggestionAdapterV2 extends RecyclerView.Adapter<DashboardItemHolder> implements
47 LifecycleObserver, OnSaveInstanceState {
48 public static final String TAG = "SuggestionAdapterV2";
50 private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
51 private static final String STATE_SUGGESTION_LIST = "suggestion_list";
53 private final Context mContext;
54 private final MetricsFeatureProvider mMetricsFeatureProvider;
55 private final IconCache mCache;
56 private final ArrayList<String> mSuggestionsShownLogged;
57 private final SuggestionControllerMixin mSuggestionControllerMixin;
58 private final Callback mCallback;
59 private final CardConfig mConfig;
61 private List<Suggestion> mSuggestions;
63 public interface Callback {
65 * Called when the close button of the suggestion card is clicked.
67 void onSuggestionClosed(Suggestion suggestion);
70 public SuggestionAdapterV2(Context context, SuggestionControllerMixin suggestionControllerMixin,
71 Bundle savedInstanceState, Callback callback, Lifecycle lifecycle) {
73 mSuggestionControllerMixin = suggestionControllerMixin;
74 mCache = new IconCache(context);
75 final FeatureFactory factory = FeatureFactory.getFactory(context);
76 mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
78 if (savedInstanceState != null) {
79 mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
80 mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
81 STATE_SUGGESTIONS_SHOWN_LOGGED);
83 mSuggestionsShownLogged = new ArrayList<>();
86 if (lifecycle != null) {
87 lifecycle.addObserver(this);
89 mConfig = CardConfig.get(context);
91 setHasStableIds(true);
95 public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
96 return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
97 viewType, parent, false));
101 public void onBindViewHolder(DashboardItemHolder holder, int position) {
102 final Suggestion suggestion = mSuggestions.get(position);
103 final String id = suggestion.getId();
104 final int suggestionCount = mSuggestions.size();
105 if (!mSuggestionsShownLogged.contains(id)) {
106 mMetricsFeatureProvider.action(
107 mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
108 mSuggestionsShownLogged.add(id);
110 mConfig.setCardLayout(holder, suggestionCount, position);
111 holder.icon.setImageDrawable(mCache.getIcon(suggestion.getIcon()));
112 holder.title.setText(suggestion.getTitle());
113 holder.title.setSingleLine(suggestionCount == 1);
115 if (suggestionCount == 1) {
116 final CharSequence summary = suggestion.getSummary();
117 if (!TextUtils.isEmpty(summary)) {
118 holder.summary.setText(summary);
119 holder.summary.setVisibility(View.VISIBLE);
121 holder.summary.setVisibility(View.GONE);
124 // Do not show summary if there are more than 1 suggestions
125 holder.summary.setVisibility(View.GONE);
126 holder.title.setMaxLines(3);
129 final ImageView closeButton = holder.itemView.findViewById(R.id.close_button);
130 if (closeButton != null) {
131 if (mCallback != null) {
132 closeButton.setOnClickListener(v -> {
133 mCallback.onSuggestionClosed(suggestion);
136 closeButton.setOnClickListener(null);
140 View clickHandler = holder.itemView;
141 // If a view with @android:id/primary is defined, use that as the click handler
143 final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
144 if (primaryAction != null) {
145 clickHandler = primaryAction;
147 clickHandler.setOnClickListener(v -> {
148 mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
150 suggestion.getPendingIntent().send();
151 mSuggestionControllerMixin.launchSuggestion(suggestion);
152 } catch (PendingIntent.CanceledException e) {
153 Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
159 public long getItemId(int position) {
160 return Objects.hash(mSuggestions.get(position).getId());
164 public int getItemViewType(int position) {
165 final Suggestion suggestion = getSuggestion(position);
166 if ((suggestion.getFlags() & Suggestion.FLAG_HAS_BUTTON) != 0) {
167 return R.layout.suggestion_tile_with_button_v2;
169 return R.layout.suggestion_tile_v2;
174 public int getItemCount() {
175 return mSuggestions.size();
178 public Suggestion getSuggestion(int position) {
179 final long itemId = getItemId(position);
180 if (mSuggestions == null) {
183 for (Suggestion suggestion : mSuggestions) {
184 if (Objects.hash(suggestion.getId()) == itemId) {
191 public void removeSuggestion(Suggestion suggestion) {
192 final int position = mSuggestions.indexOf(suggestion);
193 mSuggestions.remove(suggestion);
194 notifyItemRemoved(position);
198 public void onSaveInstanceState(Bundle outState) {
199 if (mSuggestions != null) {
200 outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
201 new ArrayList<>(mSuggestions));
203 outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
206 public void setSuggestions(List<Suggestion> suggestions) {
207 mSuggestions = suggestions;
210 public List<Suggestion> getSuggestions() {
214 private static class CardConfig {
215 // Card start/end margin
216 private final int mMarginInner;
217 private final int mMarginOuter;
218 // Card width for different numbers of cards
219 private final int mWidthSingleCard;
220 private final int mWidthTwoCards;
221 private final int mWidthMultipleCards;
222 // padding between icon and title
223 private final int mPaddingTitleTopSingleCard;
224 private final int mPaddingTitleTopMultipleCards;
226 private static CardConfig sConfig;
228 private CardConfig(Context context) {
229 final Resources res = context.getResources();
231 res.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin);
233 res.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin);
234 mWidthSingleCard = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_one_card);
235 mWidthTwoCards = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_two_cards);
236 mWidthMultipleCards =
237 res.getDimensionPixelOffset(R.dimen.suggestion_card_width_multiple_cards);
238 mPaddingTitleTopSingleCard =
239 res.getDimensionPixelOffset(R.dimen.suggestion_card_title_padding_bottom_one_card);
240 mPaddingTitleTopMultipleCards = res.getDimensionPixelOffset(
241 R.dimen.suggestion_card_title_padding_bottom_multiple_cards);
244 public static CardConfig get(Context context) {
245 if (sConfig == null) {
246 sConfig = new CardConfig(context);
251 private void setCardLayout(DashboardItemHolder holder, int suggestionCount,
253 final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
255 ? mWidthSingleCard : suggestionCount == 2
256 ? mWidthTwoCards : mWidthMultipleCards,
257 LinearLayout.LayoutParams.WRAP_CONTENT);
258 if (suggestionCount == 1) {
259 params.setMarginStart(mMarginOuter);
260 params.setMarginEnd(mMarginOuter);
262 params.setMarginStart(
263 position == 0 ? mMarginOuter : mMarginInner);
264 params.setMarginEnd(position == suggestionCount - 1 ? mMarginOuter : 0);
266 holder.itemView.setLayoutParams(params);