2 * Copyright (C) 2017 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.Typeface;
22 import android.graphics.drawable.Drawable;
23 import android.graphics.drawable.Icon;
24 import android.os.Bundle;
25 import android.service.settings.suggestions.Suggestion;
26 import android.support.annotation.VisibleForTesting;
27 import android.support.v7.widget.RecyclerView;
28 import android.text.TextUtils;
29 import android.util.DisplayMetrics;
30 import android.util.Log;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.WindowManager;
35 import android.widget.ImageView;
36 import android.widget.LinearLayout;
38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
39 import com.android.settings.R;
40 import com.android.settings.dashboard.DashboardAdapter.DashboardItemHolder;
41 import com.android.settings.overlay.FeatureFactory;
42 import com.android.settingslib.Utils;
43 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
44 import com.android.settingslib.core.lifecycle.Lifecycle;
45 import com.android.settingslib.core.lifecycle.LifecycleObserver;
46 import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
47 import com.android.settingslib.suggestions.SuggestionControllerMixin;
48 import com.android.settingslib.utils.IconCache;
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.Objects;
54 public class SuggestionAdapter extends RecyclerView.Adapter<DashboardItemHolder> implements
55 LifecycleObserver, OnSaveInstanceState {
56 public static final String TAG = "SuggestionAdapter";
58 private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
59 private static final String STATE_SUGGESTION_LIST = "suggestion_list";
61 private final Context mContext;
62 private final MetricsFeatureProvider mMetricsFeatureProvider;
63 private final IconCache mCache;
64 private final ArrayList<String> mSuggestionsShownLogged;
65 private final SuggestionFeatureProvider mSuggestionFeatureProvider;
66 private final SuggestionControllerMixin mSuggestionControllerMixin;
67 private final Callback mCallback;
68 private final CardConfig mConfig;
70 private List<Suggestion> mSuggestions;
72 public interface Callback {
74 * Called when the close button of the suggestion card is clicked.
76 void onSuggestionClosed(Suggestion suggestion);
79 public SuggestionAdapter(Context context, SuggestionControllerMixin suggestionControllerMixin,
80 Bundle savedInstanceState, Callback callback, Lifecycle lifecycle) {
82 mSuggestionControllerMixin = suggestionControllerMixin;
83 mCache = new IconCache(context);
84 final FeatureFactory factory = FeatureFactory.getFactory(context);
85 mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
86 mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
88 if (savedInstanceState != null) {
89 mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
90 mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
91 STATE_SUGGESTIONS_SHOWN_LOGGED);
93 mSuggestionsShownLogged = new ArrayList<>();
96 if (lifecycle != null) {
97 lifecycle.addObserver(this);
99 mConfig = CardConfig.get(context);
101 setHasStableIds(true);
105 public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
106 return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
107 viewType, parent, false));
111 public void onBindViewHolder(DashboardItemHolder holder, int position) {
112 final Suggestion suggestion = mSuggestions.get(position);
113 final String id = suggestion.getId();
114 final int suggestionCount = mSuggestions.size();
115 if (!mSuggestionsShownLogged.contains(id)) {
116 mMetricsFeatureProvider.action(
117 mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
118 mSuggestionsShownLogged.add(id);
120 final Icon icon = suggestion.getIcon();
121 final Drawable drawable = mCache.getIcon(icon);
122 if (drawable != null) {
123 drawable.setTint(Utils.getColorAccent(mContext));
125 holder.icon.setImageDrawable(drawable);
126 holder.title.setText(suggestion.getTitle());
127 holder.title.setTypeface(Typeface.create(
128 mContext.getString(com.android.internal.R.string.config_headlineFontFamily),
131 if (suggestionCount == 1) {
132 final CharSequence summary = suggestion.getSummary();
133 if (!TextUtils.isEmpty(summary)) {
134 holder.summary.setText(summary);
135 holder.summary.setVisibility(View.VISIBLE);
137 holder.summary.setVisibility(View.GONE);
140 mConfig.setCardLayout(holder, position);
143 final ImageView closeButton = holder.itemView.findViewById(R.id.close_button);
144 if (closeButton != null) {
145 closeButton.setOnClickListener(v -> {
146 mSuggestionFeatureProvider.dismissSuggestion(
147 mContext, mSuggestionControllerMixin, suggestion);
148 if (mCallback != null) {
149 mCallback.onSuggestionClosed(suggestion);
154 View clickHandler = holder.itemView;
155 // If a view with @android:id/primary is defined, use that as the click handler
157 final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
158 if (primaryAction != null) {
159 clickHandler = primaryAction;
161 clickHandler.setOnClickListener(v -> {
162 mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
164 suggestion.getPendingIntent().send();
165 mSuggestionControllerMixin.launchSuggestion(suggestion);
166 } catch (PendingIntent.CanceledException e) {
167 Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
173 public long getItemId(int position) {
174 return Objects.hash(mSuggestions.get(position).getId());
178 public int getItemViewType(int position) {
179 final Suggestion suggestion = getSuggestion(position);
180 if ((suggestion.getFlags() & Suggestion.FLAG_HAS_BUTTON) != 0) {
181 return R.layout.suggestion_tile_with_button;
183 if (getItemCount() == 1) {
184 return R.layout.suggestion_tile;
186 return R.layout.suggestion_tile_two_cards;
190 public int getItemCount() {
191 return mSuggestions.size();
194 public Suggestion getSuggestion(int position) {
195 final long itemId = getItemId(position);
196 if (mSuggestions == null) {
199 for (Suggestion suggestion : mSuggestions) {
200 if (Objects.hash(suggestion.getId()) == itemId) {
207 public void removeSuggestion(Suggestion suggestion) {
208 final int position = mSuggestions.indexOf(suggestion);
209 mSuggestions.remove(suggestion);
210 notifyItemRemoved(position);
214 public void onSaveInstanceState(Bundle outState) {
215 if (mSuggestions != null) {
216 outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
217 new ArrayList<>(mSuggestions));
219 outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
222 public void setSuggestions(List<Suggestion> suggestions) {
223 mSuggestions = suggestions;
226 public List<Suggestion> getSuggestions() {
231 static class CardConfig {
232 // Card start/end margin
233 private final int mMarginInner;
234 private final int mMarginOuter;
235 private final WindowManager mWindowManager;
237 private static CardConfig sConfig;
239 private CardConfig(Context context) {
240 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
241 final Resources res = context.getResources();
243 res.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin);
245 res.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin);
248 public static CardConfig get(Context context) {
249 if (sConfig == null) {
250 sConfig = new CardConfig(context);
256 void setCardLayout(DashboardItemHolder holder, int position) {
257 final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
258 getWidthForTwoCrads(), LinearLayout.LayoutParams.WRAP_CONTENT);
259 params.setMarginStart(position == 0 ? mMarginOuter : mMarginInner);
260 params.setMarginEnd(position != 0 ? mMarginOuter : 0);
261 holder.itemView.setLayoutParams(params);
264 private int getWidthForTwoCrads() {
265 return (getScreenWidth() - mMarginInner - mMarginOuter * 2) / 2;
269 int getScreenWidth() {
270 final DisplayMetrics metrics = new DisplayMetrics();
271 mWindowManager.getDefaultDisplay().getMetrics(metrics);
272 return metrics.widthPixels;