Home | History | Annotate | Download | only in suggestions
      1 /*
      2  * Copyright (C) 2017 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.suggestions;
     17 
     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;
     37 
     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;
     49 
     50 import java.util.ArrayList;
     51 import java.util.List;
     52 import java.util.Objects;
     53 
     54 public class SuggestionAdapter extends RecyclerView.Adapter<DashboardItemHolder> implements
     55     LifecycleObserver, OnSaveInstanceState {
     56     public static final String TAG = "SuggestionAdapter";
     57 
     58     private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
     59     private static final String STATE_SUGGESTION_LIST = "suggestion_list";
     60 
     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;
     69 
     70     private List<Suggestion> mSuggestions;
     71 
     72     public interface Callback {
     73         /**
     74          * Called when the close button of the suggestion card is clicked.
     75          */
     76         void onSuggestionClosed(Suggestion suggestion);
     77     }
     78 
     79     public SuggestionAdapter(Context context, SuggestionControllerMixin suggestionControllerMixin,
     80         Bundle savedInstanceState, Callback callback, Lifecycle lifecycle) {
     81         mContext = context;
     82         mSuggestionControllerMixin = suggestionControllerMixin;
     83         mCache = new IconCache(context);
     84         final FeatureFactory factory = FeatureFactory.getFactory(context);
     85         mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
     86         mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
     87         mCallback = callback;
     88         if (savedInstanceState != null) {
     89             mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
     90             mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
     91                 STATE_SUGGESTIONS_SHOWN_LOGGED);
     92         } else {
     93             mSuggestionsShownLogged = new ArrayList<>();
     94         }
     95 
     96         if (lifecycle != null) {
     97             lifecycle.addObserver(this);
     98         }
     99         mConfig = CardConfig.get(context);
    100 
    101         setHasStableIds(true);
    102     }
    103 
    104     @Override
    105     public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    106         return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
    107                 viewType, parent, false));
    108     }
    109 
    110     @Override
    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);
    119         }
    120         final Icon icon = suggestion.getIcon();
    121         final Drawable drawable = mCache.getIcon(icon);
    122         if (drawable != null && (suggestion.getFlags() & Suggestion.FLAG_ICON_TINTABLE) != 0) {
    123             drawable.setTint(Utils.getColorAccent(mContext));
    124         }
    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),
    129             Typeface.NORMAL));
    130 
    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);
    136             } else {
    137                 holder.summary.setVisibility(View.GONE);
    138             }
    139         } else {
    140             mConfig.setCardLayout(holder, position);
    141         }
    142 
    143         final View 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);
    150                 }
    151             });
    152         }
    153 
    154         View clickHandler = holder.itemView;
    155         // If a view with @android:id/primary is defined, use that as the click handler
    156         // instead.
    157         final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
    158         if (primaryAction != null) {
    159             clickHandler = primaryAction;
    160         }
    161         clickHandler.setOnClickListener(v -> {
    162             mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
    163             try {
    164                 suggestion.getPendingIntent().send();
    165                 mSuggestionControllerMixin.launchSuggestion(suggestion);
    166             } catch (PendingIntent.CanceledException e) {
    167                 Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
    168             }
    169         });
    170     }
    171 
    172     @Override
    173     public long getItemId(int position) {
    174         return Objects.hash(mSuggestions.get(position).getId());
    175     }
    176 
    177     @Override
    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;
    182         }
    183         if (getItemCount() == 1) {
    184             return R.layout.suggestion_tile;
    185         }
    186         return R.layout.suggestion_tile_two_cards;
    187     }
    188 
    189     @Override
    190     public int getItemCount() {
    191         return mSuggestions.size();
    192     }
    193 
    194     public Suggestion getSuggestion(int position) {
    195         final long itemId = getItemId(position);
    196         if (mSuggestions == null) {
    197             return null;
    198         }
    199         for (Suggestion suggestion : mSuggestions) {
    200             if (Objects.hash(suggestion.getId()) == itemId) {
    201                 return suggestion;
    202             }
    203         }
    204         return null;
    205     }
    206 
    207     public void removeSuggestion(Suggestion suggestion) {
    208         final int position = mSuggestions.indexOf(suggestion);
    209         mSuggestions.remove(suggestion);
    210         notifyItemRemoved(position);
    211     }
    212 
    213     @Override
    214     public void onSaveInstanceState(Bundle outState) {
    215         if (mSuggestions != null) {
    216             outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
    217                 new ArrayList<>(mSuggestions));
    218         }
    219         outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
    220     }
    221 
    222     public void setSuggestions(List<Suggestion> suggestions) {
    223         mSuggestions = suggestions;
    224     }
    225 
    226     public List<Suggestion> getSuggestions() {
    227         return mSuggestions;
    228     }
    229 
    230     @VisibleForTesting
    231     static class CardConfig {
    232         // Card start/end margin
    233         private final int mMarginInner;
    234         private final int mMarginOuter;
    235         private final WindowManager mWindowManager;
    236 
    237         private static CardConfig sConfig;
    238 
    239         private CardConfig(Context context) {
    240             mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    241             final Resources res = context.getResources();
    242             mMarginInner =
    243                 res.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin);
    244             mMarginOuter =
    245                 res.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin);
    246         }
    247 
    248         public static CardConfig get(Context context) {
    249             if (sConfig == null) {
    250                 sConfig = new CardConfig(context);
    251             }
    252             return sConfig;
    253         }
    254 
    255         @VisibleForTesting
    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);
    262         }
    263 
    264         private int getWidthForTwoCrads() {
    265             return (getScreenWidth() - mMarginInner - mMarginOuter * 2) / 2;
    266         }
    267 
    268         @VisibleForTesting
    269         int getScreenWidth() {
    270             final DisplayMetrics metrics = new DisplayMetrics();
    271             mWindowManager.getDefaultDisplay().getMetrics(metrics);
    272             return metrics.widthPixels;
    273         }
    274     }
    275 
    276 }
    277