1 /* 2 * Copyright (C) 2018 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; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static org.mockito.ArgumentMatchers.any; 20 import static org.mockito.ArgumentMatchers.eq; 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.reset; 25 import static org.mockito.Mockito.spy; 26 import static org.mockito.Mockito.verify; 27 import static org.mockito.Mockito.when; 28 29 import android.app.PendingIntent; 30 import android.content.Context; 31 import android.content.res.Resources; 32 import android.graphics.drawable.Drawable; 33 import android.graphics.drawable.Icon; 34 import android.os.Bundle; 35 import android.service.settings.suggestions.Suggestion; 36 import android.support.v7.widget.RecyclerView; 37 import android.util.DisplayMetrics; 38 import android.view.LayoutInflater; 39 import android.view.View; 40 import android.view.WindowManager; 41 import android.widget.TextView; 42 43 import com.android.settings.R; 44 import com.android.settings.SettingsActivity; 45 import com.android.settings.dashboard.conditional.Condition; 46 import com.android.settings.dashboard.suggestions.SuggestionAdapter; 47 import com.android.settings.testutils.FakeFeatureFactory; 48 import com.android.settings.testutils.SettingsRobolectricTestRunner; 49 import com.android.settings.testutils.shadow.SettingsShadowResources; 50 import com.android.settingslib.drawer.Tile; 51 import com.android.settingslib.drawer.TileUtils; 52 import com.android.settingslib.utils.IconCache; 53 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 import org.mockito.Answers; 58 import org.mockito.Mock; 59 import org.mockito.MockitoAnnotations; 60 import org.robolectric.RuntimeEnvironment; 61 import org.robolectric.annotation.Config; 62 import org.robolectric.util.ReflectionHelpers; 63 64 import java.util.ArrayList; 65 import java.util.List; 66 67 @RunWith(SettingsRobolectricTestRunner.class) 68 @Config(shadows = SettingsShadowResources.SettingsShadowTheme.class) 69 public class DashboardAdapterTest { 70 71 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 72 private SettingsActivity mContext; 73 @Mock 74 private View mView; 75 @Mock 76 private Condition mCondition; 77 @Mock 78 private Resources mResources; 79 @Mock 80 private WindowManager mWindowManager; 81 private FakeFeatureFactory mFactory; 82 private DashboardAdapter mDashboardAdapter; 83 private List<Condition> mConditionList; 84 85 @Before 86 public void setUp() { 87 MockitoAnnotations.initMocks(this); 88 mFactory = FakeFeatureFactory.setupForTest(); 89 when(mFactory.dashboardFeatureProvider.shouldTintIcon()).thenReturn(true); 90 91 when(mContext.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mWindowManager); 92 when(mContext.getResources()).thenReturn(mResources); 93 when(mResources.getQuantityString(any(int.class), any(int.class), any())).thenReturn(""); 94 95 mConditionList = new ArrayList<>(); 96 mConditionList.add(mCondition); 97 when(mCondition.shouldShow()).thenReturn(true); 98 mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */, 99 mConditionList, null /* suggestionControllerMixin */, null /* lifecycle */); 100 when(mView.getTag()).thenReturn(mCondition); 101 } 102 103 @Test 104 public void testSuggestionDismissed_notOnlySuggestion_updateSuggestionOnly() { 105 final DashboardAdapter adapter = 106 spy(new DashboardAdapter(mContext, null /* savedInstanceState */, 107 null /* conditions */, null /* suggestionControllerMixin */, 108 null /* lifecycle */)); 109 final List<Suggestion> suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3"); 110 adapter.setSuggestions(suggestions); 111 112 final RecyclerView data = mock(RecyclerView.class); 113 when(data.getResources()).thenReturn(mResources); 114 when(data.getContext()).thenReturn(mContext); 115 when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class)); 116 final View itemView = mock(View.class); 117 when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data); 118 when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class)); 119 when(itemView.findViewById(android.R.id.title)).thenReturn(mock(TextView.class)); 120 final DashboardAdapter.SuggestionContainerHolder holder = 121 new DashboardAdapter.SuggestionContainerHolder(itemView); 122 123 adapter.onBindSuggestion(holder, 0); 124 125 final DashboardData dashboardData = adapter.mDashboardData; 126 reset(adapter); // clear interactions tracking 127 128 final Suggestion suggestionToRemove = suggestions.get(1); 129 adapter.onSuggestionClosed(suggestionToRemove); 130 131 assertThat(suggestions.size()).isEqualTo(2); 132 assertThat(suggestions.contains(suggestionToRemove)).isFalse(); 133 verify(adapter).notifyDashboardDataChanged(any()); 134 } 135 136 @Test 137 public void testSuggestionDismissed_onlySuggestion_updateDashboardData() { 138 DashboardAdapter adapter = 139 spy(new DashboardAdapter(mContext, null /* savedInstanceState */, 140 null /* conditions */, null /* suggestionControllerMixin */, 141 null /* lifecycle */)); 142 final List<Suggestion> suggestions = makeSuggestionsV2("pkg1"); 143 adapter.setSuggestions(suggestions); 144 final DashboardData dashboardData = adapter.mDashboardData; 145 reset(adapter); // clear interactions tracking 146 147 adapter.onSuggestionClosed(suggestions.get(0)); 148 149 assertThat(adapter.mDashboardData).isNotEqualTo(dashboardData); 150 verify(adapter).notifyDashboardDataChanged(any()); 151 } 152 153 @Test 154 public void testBindSuggestion_shouldSetSuggestionAdapterAndNoCrash() { 155 mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */, 156 null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); 157 final List<Suggestion> suggestions = makeSuggestionsV2("pkg1"); 158 159 mDashboardAdapter.setSuggestions(suggestions); 160 161 final RecyclerView data = mock(RecyclerView.class); 162 when(data.getResources()).thenReturn(mResources); 163 when(data.getContext()).thenReturn(mContext); 164 when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class)); 165 final View itemView = mock(View.class); 166 when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data); 167 when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class)); 168 when(itemView.findViewById(android.R.id.title)).thenReturn(mock(TextView.class)); 169 final DashboardAdapter.SuggestionContainerHolder holder = 170 new DashboardAdapter.SuggestionContainerHolder(itemView); 171 172 mDashboardAdapter.onBindSuggestion(holder, 0); 173 174 verify(data).setAdapter(any(SuggestionAdapter.class)); 175 // should not crash 176 } 177 178 @Test 179 public void onBindTile_internalTile_shouldNotUseGenericBackgroundIcon() { 180 final Context context = RuntimeEnvironment.application; 181 final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); 182 final DashboardAdapter.DashboardItemHolder holder = 183 new DashboardAdapter.DashboardItemHolder(view); 184 final Tile tile = new Tile(); 185 tile.icon = Icon.createWithResource(context, R.drawable.ic_settings); 186 final IconCache iconCache = mock(IconCache.class); 187 when(iconCache.getIcon(tile.icon)).thenReturn(context.getDrawable(R.drawable.ic_settings)); 188 189 mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, 190 null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); 191 ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); 192 mDashboardAdapter.onBindTile(holder, tile); 193 194 verify(iconCache, never()).updateIcon(any(Icon.class), any(Drawable.class)); 195 } 196 197 @Test 198 public void onBindTile_externalTile_shouldUpdateIcon() { 199 final Context context = spy(RuntimeEnvironment.application); 200 final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); 201 final DashboardAdapter.DashboardItemHolder holder = 202 new DashboardAdapter.DashboardItemHolder(view); 203 final Tile tile = new Tile(); 204 tile.icon = Icon.createWithResource(context, R.drawable.ic_settings); 205 when(tile.icon.getResPackage()).thenReturn("another.package"); 206 207 final IconCache iconCache = new IconCache(context); 208 209 mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, 210 null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); 211 ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); 212 213 doReturn("another.package").when(context).getPackageName(); 214 mDashboardAdapter.onBindTile(holder, tile); 215 216 assertThat(iconCache.getIcon(tile.icon)).isInstanceOf(RoundedHomepageIcon.class); 217 } 218 219 @Test 220 public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { 221 final Context context = spy(RuntimeEnvironment.application); 222 final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); 223 final DashboardAdapter.DashboardItemHolder holder = 224 new DashboardAdapter.DashboardItemHolder(view); 225 final Tile tile = new Tile(); 226 tile.metaData = new Bundle(); 227 tile.metaData.putInt(TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, 228 R.color.memory_critical); 229 tile.icon = Icon.createWithResource(context, R.drawable.ic_settings); 230 final IconCache iconCache = new IconCache(context); 231 mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, 232 null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); 233 ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); 234 235 doReturn("another.package").when(context).getPackageName(); 236 mDashboardAdapter.onBindTile(holder, tile); 237 238 final RoundedHomepageIcon homepageIcon = (RoundedHomepageIcon) iconCache.getIcon(tile.icon); 239 assertThat(homepageIcon.mBackgroundColor) 240 .isEqualTo(RuntimeEnvironment.application.getColor(R.color.memory_critical)); 241 } 242 243 @Test 244 public void onBindTile_externalTile_usingRoundedHomepageIcon_shouldNotUpdateIcon() { 245 final Context context = RuntimeEnvironment.application; 246 final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); 247 final DashboardAdapter.DashboardItemHolder holder = 248 new DashboardAdapter.DashboardItemHolder(view); 249 final Tile tile = new Tile(); 250 tile.icon = mock(Icon.class); 251 when(tile.icon.getResPackage()).thenReturn("another.package"); 252 253 final IconCache iconCache = mock(IconCache.class); 254 when(iconCache.getIcon(tile.icon)).thenReturn(mock(RoundedHomepageIcon.class)); 255 256 mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, 257 null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); 258 ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); 259 260 mDashboardAdapter.onBindTile(holder, tile); 261 262 verify(iconCache, never()).updateIcon(eq(tile.icon), any(RoundedHomepageIcon.class)); 263 } 264 265 private List<Suggestion> makeSuggestionsV2(String... pkgNames) { 266 final List<Suggestion> suggestions = new ArrayList<>(); 267 for (String pkgName : pkgNames) { 268 final Suggestion suggestion = new Suggestion.Builder(pkgName) 269 .setPendingIntent(mock(PendingIntent.class)) 270 .build(); 271 suggestions.add(suggestion); 272 } 273 return suggestions; 274 } 275 } 276