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 static com.google.common.truth.Truth.assertThat;
     19 import static org.mockito.ArgumentMatchers.any;
     20 import static org.mockito.ArgumentMatchers.anyInt;
     21 import static org.mockito.Mockito.doReturn;
     22 import static org.mockito.Mockito.mock;
     23 import static org.mockito.Mockito.never;
     24 import static org.mockito.Mockito.spy;
     25 import static org.mockito.Mockito.verify;
     26 import static org.mockito.Mockito.when;
     27 
     28 import android.app.PendingIntent;
     29 import android.content.Context;
     30 import android.content.res.Resources;
     31 import android.content.res.TypedArray;
     32 import android.graphics.drawable.Drawable;
     33 import android.graphics.drawable.Icon;
     34 import android.service.settings.suggestions.Suggestion;
     35 import android.view.LayoutInflater;
     36 import android.view.View;
     37 import android.view.WindowManager;
     38 import android.widget.FrameLayout;
     39 import android.widget.LinearLayout;
     40 
     41 import com.android.internal.logging.nano.MetricsProto;
     42 import com.android.settings.R;
     43 import com.android.settings.SettingsActivity;
     44 import com.android.settings.dashboard.DashboardAdapter;
     45 import com.android.settings.testutils.FakeFeatureFactory;
     46 import com.android.settings.testutils.SettingsRobolectricTestRunner;
     47 import com.android.settings.testutils.shadow.ShadowCardView;
     48 import com.android.settingslib.suggestions.SuggestionControllerMixin;
     49 import com.android.settingslib.utils.IconCache;
     50 
     51 import org.junit.Before;
     52 import org.junit.Test;
     53 import org.junit.runner.RunWith;
     54 import org.mockito.Answers;
     55 import org.mockito.Mock;
     56 import org.mockito.MockitoAnnotations;
     57 import org.robolectric.RuntimeEnvironment;
     58 import org.robolectric.annotation.Config;
     59 import org.robolectric.util.ReflectionHelpers;
     60 
     61 import java.util.ArrayList;
     62 import java.util.List;
     63 
     64 @RunWith(SettingsRobolectricTestRunner.class)
     65 @Config(shadows = ShadowCardView.class)
     66 public class SuggestionAdapterTest {
     67 
     68     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     69     private SettingsActivity mActivity;
     70     @Mock
     71     private SuggestionControllerMixin mSuggestionControllerMixin;
     72     @Mock
     73     private Resources mResources;
     74     @Mock
     75     private WindowManager mWindowManager;
     76 
     77     private FakeFeatureFactory mFeatureFactory;
     78     private Context mContext;
     79     private SuggestionAdapter mSuggestionAdapter;
     80     private DashboardAdapter.DashboardItemHolder mSuggestionHolder;
     81     private List<Suggestion> mOneSuggestion;
     82     private List<Suggestion> mTwoSuggestions;
     83     private SuggestionAdapter.CardConfig mConfig;
     84 
     85     @Before
     86     public void setUp() {
     87         MockitoAnnotations.initMocks(this);
     88         mContext = spy(RuntimeEnvironment.application);
     89         when(mActivity.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mWindowManager);
     90         when(mActivity.getResources()).thenReturn(mResources);
     91         when(mResources.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin))
     92             .thenReturn(10);
     93         when(mResources.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin))
     94             .thenReturn(20);
     95         mConfig = spy(SuggestionAdapter.CardConfig.get(mActivity));
     96 
     97         mFeatureFactory = FakeFeatureFactory.setupForTest();
     98 
     99         final Suggestion suggestion1 = new Suggestion.Builder("id1")
    100                 .setTitle("Test suggestion 1")
    101                 .build();
    102         final Suggestion suggestion2 = new Suggestion.Builder("id2")
    103                 .setTitle("Test suggestion 2")
    104                 .build();
    105         mOneSuggestion = new ArrayList<>();
    106         mOneSuggestion.add(suggestion1);
    107         mTwoSuggestions = new ArrayList<>();
    108         mTwoSuggestions.add(suggestion1);
    109         mTwoSuggestions.add(suggestion2);
    110     }
    111 
    112     @Test
    113     public void getItemCount_shouldReturnListSize() {
    114         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
    115             null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
    116         mSuggestionAdapter.setSuggestions(mOneSuggestion);
    117         assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
    118 
    119         mSuggestionAdapter.setSuggestions(mTwoSuggestions);
    120         assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
    121     }
    122 
    123     @Test
    124     public void getItemViewType_shouldReturnSuggestionTile() {
    125         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
    126             null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
    127         mSuggestionAdapter.setSuggestions(mOneSuggestion);
    128         assertThat(mSuggestionAdapter.getItemViewType(0))
    129             .isEqualTo(R.layout.suggestion_tile);
    130     }
    131 
    132     @Test
    133     public void getItemType_hasButton_shouldReturnSuggestionWithButton() {
    134         final List<Suggestion> suggestions = new ArrayList<>();
    135         suggestions.add(new Suggestion.Builder("id")
    136                 .setFlags(Suggestion.FLAG_HAS_BUTTON)
    137                 .setTitle("123")
    138                 .setSummary("456")
    139                 .build());
    140         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
    141             null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
    142         mSuggestionAdapter.setSuggestions(suggestions);
    143 
    144         assertThat(mSuggestionAdapter.getItemViewType(0))
    145             .isEqualTo(R.layout.suggestion_tile_with_button);
    146     }
    147 
    148     @Test
    149     public void onBindViewHolder_shouldLog() {
    150         final View view = spy(LayoutInflater.from(mContext).inflate(
    151             R.layout.suggestion_tile, new LinearLayout(mContext), true));
    152         mSuggestionHolder = new DashboardAdapter.DashboardItemHolder(view);
    153         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
    154             null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
    155         mSuggestionAdapter.setSuggestions(mOneSuggestion);
    156         doReturn("sans").when(mContext).getString(anyInt());
    157 
    158         // Bind twice
    159         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
    160         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
    161 
    162         // Log once
    163         verify(mFeatureFactory.metricsFeatureProvider).action(
    164                 mContext, MetricsProto.MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION,
    165                 mOneSuggestion.get(0).getId());
    166     }
    167 
    168     @Test
    169     public void onBindViewHolder_itemViewShouldHandleClick()
    170             throws PendingIntent.CanceledException {
    171         final List<Suggestion> suggestions = makeSuggestions("pkg1");
    172         setupSuggestions(mActivity, suggestions);
    173 
    174         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
    175         mSuggestionHolder.itemView.performClick();
    176 
    177         verify(mSuggestionControllerMixin).launchSuggestion(suggestions.get(0));
    178         verify(suggestions.get(0).getPendingIntent()).send();
    179     }
    180 
    181     @Test
    182     public void onBindViewHolder_hasButton_buttonShouldHandleClick()
    183         throws PendingIntent.CanceledException {
    184         final List<Suggestion> suggestions = new ArrayList<>();
    185         final PendingIntent pendingIntent = mock(PendingIntent.class);
    186         suggestions.add(new Suggestion.Builder("id")
    187             .setFlags(Suggestion.FLAG_HAS_BUTTON)
    188             .setTitle("123")
    189             .setSummary("456")
    190             .setPendingIntent(pendingIntent)
    191             .build());
    192         mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
    193             null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
    194         mSuggestionAdapter.setSuggestions(suggestions);
    195         mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
    196             new FrameLayout(RuntimeEnvironment.application),
    197             mSuggestionAdapter.getItemViewType(0));
    198         doReturn("sans").when(mContext).getString(anyInt());
    199 
    200         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
    201         mSuggestionHolder.itemView.findViewById(android.R.id.primary).performClick();
    202 
    203         verify(mSuggestionControllerMixin).launchSuggestion(suggestions.get(0));
    204         verify(pendingIntent).send();
    205     }
    206 
    207     @Test
    208     public void getSuggestions_shouldReturnSuggestionWhenMatch() {
    209         final List<Suggestion> suggestions = makeSuggestions("pkg1");
    210         setupSuggestions(mActivity, suggestions);
    211 
    212         assertThat(mSuggestionAdapter.getSuggestion(0)).isNotNull();
    213     }
    214 
    215     @Test
    216     public void onBindViewHolder_closeButtonShouldHandleClick()
    217         throws PendingIntent.CanceledException {
    218         final List<Suggestion> suggestions = makeSuggestions("pkg1");
    219         final SuggestionAdapter.Callback callback = mock(SuggestionAdapter.Callback.class);
    220         mSuggestionAdapter = new SuggestionAdapter(mActivity, mSuggestionControllerMixin,
    221             null /* savedInstanceState */, callback, null /* lifecycle */);
    222         mSuggestionAdapter.setSuggestions(suggestions);
    223         mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
    224             new FrameLayout(RuntimeEnvironment.application),
    225             mSuggestionAdapter.getItemViewType(0));
    226 
    227         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
    228         mSuggestionHolder.itemView.findViewById(R.id.close_button).performClick();
    229 
    230         final Suggestion suggestion = suggestions.get(0);
    231         verify(mFeatureFactory.suggestionsFeatureProvider).dismissSuggestion(
    232             mActivity, mSuggestionControllerMixin, suggestion);
    233         verify(callback).onSuggestionClosed(suggestion);
    234     }
    235 
    236     @Test
    237     public void onBindViewHolder_iconNotTintable_shouldNotTintIcon()
    238             throws PendingIntent.CanceledException {
    239         final Icon icon = mock(Icon.class);
    240         final Suggestion suggestion = new Suggestion.Builder("pkg1")
    241             .setPendingIntent(mock(PendingIntent.class))
    242             .setIcon(icon)
    243             .build();
    244         final List<Suggestion> suggestions = new ArrayList<>();
    245         suggestions.add(suggestion);
    246         mSuggestionAdapter = new SuggestionAdapter(mActivity, mSuggestionControllerMixin,
    247             null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
    248         mSuggestionAdapter.setSuggestions(suggestions);
    249         mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
    250             new FrameLayout(RuntimeEnvironment.application),
    251             mSuggestionAdapter.getItemViewType(0));
    252         IconCache cache = mock(IconCache.class);
    253         final Drawable drawable = mock(Drawable.class);
    254         when(cache.getIcon(icon)).thenReturn(drawable);
    255         ReflectionHelpers.setField(mSuggestionAdapter, "mCache", cache);
    256 
    257         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
    258 
    259         verify(drawable, never()).setTint(anyInt());
    260     }
    261 
    262     @Test
    263     public void onBindViewHolder_iconTintable_shouldTintIcon()
    264             throws PendingIntent.CanceledException {
    265         final Icon icon = mock(Icon.class);
    266         final int FLAG_ICON_TINTABLE = 1 << 1;
    267         final Suggestion suggestion = new Suggestion.Builder("pkg1")
    268             .setPendingIntent(mock(PendingIntent.class))
    269             .setIcon(icon)
    270             .setFlags(FLAG_ICON_TINTABLE)
    271             .build();
    272         final List<Suggestion> suggestions = new ArrayList<>();
    273         suggestions.add(suggestion);
    274         mSuggestionAdapter = new SuggestionAdapter(mActivity, mSuggestionControllerMixin,
    275             null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
    276         mSuggestionAdapter.setSuggestions(suggestions);
    277         mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
    278             new FrameLayout(RuntimeEnvironment.application),
    279             mSuggestionAdapter.getItemViewType(0));
    280         IconCache cache = mock(IconCache.class);
    281         final Drawable drawable = mock(Drawable.class);
    282         when(cache.getIcon(icon)).thenReturn(drawable);
    283         ReflectionHelpers.setField(mSuggestionAdapter, "mCache", cache);
    284         TypedArray typedArray = mock(TypedArray.class);
    285         final int colorAccent = 1234;
    286         when(mActivity.obtainStyledAttributes(any())).thenReturn(typedArray);
    287         when(typedArray.getColor(anyInt(), anyInt())).thenReturn(colorAccent);
    288 
    289         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
    290 
    291         verify(drawable).setTint(colorAccent);
    292     }
    293 
    294     @Test
    295     public void onBindViewHolder_closeButtonShouldHaveContentDescription()
    296         throws PendingIntent.CanceledException {
    297         final List<Suggestion> suggestions = makeSuggestions("pkg1");
    298         setupSuggestions(mActivity, suggestions);
    299 
    300         mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
    301 
    302         assertThat(
    303             mSuggestionHolder.itemView.findViewById(R.id.close_button).getContentDescription())
    304             .isNotNull();
    305     }
    306 
    307     @Test
    308     public void setCardLayout_twoCards_shouldSetCardWidthToHalfScreenMinusPadding() {
    309         final List<Suggestion> suggestions = makeSuggestions("pkg1");
    310         setupSuggestions(mContext, suggestions);
    311         doReturn(200).when(mConfig).getScreenWidth();
    312 
    313         mConfig.setCardLayout(mSuggestionHolder, 0);
    314 
    315         /*
    316          * card width = (screen width - left margin - inner margin - right margin) / 2
    317          *            = (200 - 20 - 10 - 20) / 2
    318          *            = 75
    319          */
    320         assertThat(mSuggestionHolder.itemView.getLayoutParams().width).isEqualTo(75);
    321     }
    322 
    323     private void setupSuggestions(Context context, List<Suggestion> suggestions) {
    324         mSuggestionAdapter = new SuggestionAdapter(context, mSuggestionControllerMixin,
    325             null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
    326         mSuggestionAdapter.setSuggestions(suggestions);
    327         mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
    328                 new FrameLayout(RuntimeEnvironment.application),
    329                 mSuggestionAdapter.getItemViewType(0));
    330     }
    331 
    332     private List<Suggestion> makeSuggestions(String... pkgNames) {
    333         final List<Suggestion> suggestions = new ArrayList<>();
    334         for (String pkgName : pkgNames) {
    335             final Suggestion suggestion = new Suggestion.Builder(pkgName)
    336                     .setPendingIntent(mock(PendingIntent.class))
    337                     .build();
    338             suggestions.add(suggestion);
    339         }
    340         return suggestions;
    341     }
    342 }
    343