1 /*
2  * Copyright (C) 2016 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.android.internal.logging.nano.MetricsProto.MetricsEvent.DASHBOARD_CONTAINER;
19 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
20 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.nullable;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.spy;
29 import static org.mockito.Mockito.times;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Mockito.when;
32 
33 import android.app.settings.SettingsEnums;
34 import android.content.ContentResolver;
35 import android.content.Context;
36 import android.content.pm.ActivityInfo;
37 import android.content.pm.ProviderInfo;
38 import android.net.Uri;
39 import android.os.Bundle;
40 
41 import androidx.preference.Preference;
42 import androidx.preference.PreferenceFragmentCompat;
43 import androidx.preference.PreferenceManager;
44 import androidx.preference.PreferenceScreen;
45 import androidx.preference.SwitchPreference;
46 
47 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
48 import com.android.settings.core.PreferenceControllerMixin;
49 import com.android.settings.slices.BlockingSlicePrefController;
50 import com.android.settings.testutils.FakeFeatureFactory;
51 import com.android.settings.widget.PrimarySwitchPreference;
52 import com.android.settingslib.core.AbstractPreferenceController;
53 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
54 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
55 import com.android.settingslib.drawer.ActivityTile;
56 import com.android.settingslib.drawer.DashboardCategory;
57 import com.android.settingslib.drawer.ProviderTile;
58 
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.Mock;
63 import org.mockito.MockitoAnnotations;
64 import org.robolectric.RobolectricTestRunner;
65 import org.robolectric.RuntimeEnvironment;
66 import org.robolectric.annotation.Config;
67 import org.robolectric.annotation.Implementation;
68 import org.robolectric.annotation.Implements;
69 import org.robolectric.util.ReflectionHelpers;
70 
71 import java.util.ArrayList;
72 import java.util.Arrays;
73 import java.util.HashMap;
74 import java.util.List;
75 import java.util.Map;
76 
77 @RunWith(RobolectricTestRunner.class)
78 public class DashboardFragmentTest {
79 
80     @Mock
81     private FakeFeatureFactory mFakeFeatureFactory;
82     private DashboardCategory mDashboardCategory;
83     private Context mContext;
84     private TestFragment mTestFragment;
85     private List<AbstractPreferenceController> mControllers;
86     private ActivityTile mActivityTile;
87     private ProviderTile mProviderTile;
88 
89     @Before
setUp()90     public void setUp() {
91         MockitoAnnotations.initMocks(this);
92         mContext = spy(RuntimeEnvironment.application);
93         final ActivityInfo activityInfo = new ActivityInfo();
94         activityInfo.packageName = "pkg";
95         activityInfo.name = "class";
96         activityInfo.metaData = new Bundle();
97         activityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key");
98         mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
99         mDashboardCategory = new DashboardCategory("key");
100         mActivityTile = new ActivityTile(activityInfo, mDashboardCategory.key);
101         mDashboardCategory.addTile(mActivityTile);
102 
103         final ProviderInfo providerInfo = new ProviderInfo();
104         providerInfo.packageName = "pkg";
105         providerInfo.name = "provider";
106         providerInfo.authority = "authority";
107         final Bundle metaData = new Bundle();
108         metaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key2");
109         metaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "uri");
110         mProviderTile = new ProviderTile(providerInfo, mDashboardCategory.key, metaData);
111         mDashboardCategory.addTile(mProviderTile);
112 
113         mTestFragment = new TestFragment(RuntimeEnvironment.application);
114         when(mFakeFeatureFactory.dashboardFeatureProvider
115                 .getTilesForCategory(nullable(String.class)))
116                 .thenReturn(mDashboardCategory);
117         mTestFragment.onAttach(RuntimeEnvironment.application);
118         when(mContext.getPackageName()).thenReturn("TestPackage");
119         mControllers = new ArrayList<>();
120     }
121 
122     @Test
testPreferenceControllerGetterSetter_shouldAddAndGetProperly()123     public void testPreferenceControllerGetterSetter_shouldAddAndGetProperly() {
124         final TestPreferenceController controller = new TestPreferenceController(mContext);
125         mTestFragment.addPreferenceController(controller);
126 
127         final TestPreferenceController retrievedController = mTestFragment.use
128                 (TestPreferenceController.class);
129 
130         assertThat(controller).isSameInstanceAs(retrievedController);
131     }
132 
133     @Test
testPreferenceControllerSetter_shouldAddAndNotReplace()134     public void testPreferenceControllerSetter_shouldAddAndNotReplace() {
135         final TestPreferenceController controller1 = new TestPreferenceController(mContext);
136         mTestFragment.addPreferenceController(controller1);
137         final TestPreferenceController controller2 = new TestPreferenceController(mContext);
138         mTestFragment.addPreferenceController(controller2);
139 
140         final TestPreferenceController retrievedController = mTestFragment.use
141                 (TestPreferenceController.class);
142 
143         assertThat(controller1).isSameInstanceAs(retrievedController);
144     }
145 
146     @Test
displayTilesAsPreference_shouldAddTilesWithIntent()147     public void displayTilesAsPreference_shouldAddTilesWithIntent() {
148         when(mFakeFeatureFactory.dashboardFeatureProvider
149                 .getTilesForCategory(nullable(String.class)))
150                 .thenReturn(mDashboardCategory);
151         when(mFakeFeatureFactory.dashboardFeatureProvider
152                 .getDashboardKeyForTile(any(ActivityTile.class)))
153                 .thenReturn("test_key");
154         when(mFakeFeatureFactory.dashboardFeatureProvider
155                 .getDashboardKeyForTile(any(ProviderTile.class)))
156                 .thenReturn("test_key2");
157         mTestFragment.onCreatePreferences(new Bundle(), "rootKey");
158 
159         verify(mTestFragment.mScreen, times(2)).addPreference(nullable(Preference.class));
160     }
161 
162     @Test
displayTilesAsPreference_shouldNotAddTilesWithoutIntent()163     public void displayTilesAsPreference_shouldNotAddTilesWithoutIntent() {
164         mTestFragment.onCreatePreferences(new Bundle(), "rootKey");
165 
166         verify(mTestFragment.mScreen, never()).addPreference(nullable(Preference.class));
167     }
168 
169     @Test
displayTilesAsPreference_withEmptyCategory_shouldNotAddTiles()170     public void displayTilesAsPreference_withEmptyCategory_shouldNotAddTiles() {
171         mDashboardCategory.removeTile(0);
172         mTestFragment.onCreatePreferences(new Bundle(), "rootKey");
173 
174         verify(mTestFragment.mScreen, never()).addPreference(nullable(Preference.class));
175     }
176 
177     @Test
178     @Config(qualifiers = "mcc999")
displayTilesAsPreference_shouldNotAddSuppressedTiles()179     public void displayTilesAsPreference_shouldNotAddSuppressedTiles() {
180         when(mFakeFeatureFactory.dashboardFeatureProvider
181                 .getTilesForCategory(nullable(String.class)))
182                 .thenReturn(mDashboardCategory);
183         when(mFakeFeatureFactory.dashboardFeatureProvider
184                 .getDashboardKeyForTile(any(ActivityTile.class)))
185                 .thenReturn("test_key");
186         when(mFakeFeatureFactory.dashboardFeatureProvider
187                 .getDashboardKeyForTile(any(ProviderTile.class)))
188                 .thenReturn("test_key2");
189         mTestFragment.onCreatePreferences(new Bundle(), "rootKey");
190 
191         verify(mTestFragment.mScreen, never()).addPreference(nullable(Preference.class));
192     }
193 
194     @Test
onAttach_shouldCreatePlaceholderPreferenceController()195     public void onAttach_shouldCreatePlaceholderPreferenceController() {
196         final AbstractPreferenceController controller = mTestFragment.use(
197                 DashboardTilePlaceholderPreferenceController.class);
198 
199         assertThat(controller).isNotNull();
200     }
201 
202     @Test
203     @Config(shadows = ShadowPreferenceFragmentCompat.class)
onStart_shouldRegisterDynamicDataObservers()204     public void onStart_shouldRegisterDynamicDataObservers() {
205         final DynamicDataObserver observer = new TestDynamicDataObserver();
206         mTestFragment.mDashboardTilePrefKeys.put("key", Arrays.asList(observer));
207 
208         mTestFragment.onStart();
209 
210         verify(mTestFragment.getContentResolver()).registerContentObserver(observer.getUri(), false,
211                 observer);
212     }
213 
214     @Test
215     @Config(shadows = ShadowPreferenceFragmentCompat.class)
onStop_shouldUnregisterDynamicDataObservers()216     public void onStop_shouldUnregisterDynamicDataObservers() {
217         final DynamicDataObserver observer = new TestDynamicDataObserver();
218         mTestFragment.registerDynamicDataObservers(Arrays.asList(observer));
219 
220         mTestFragment.onStop();
221 
222         verify(mTestFragment.getContentResolver()).unregisterContentObserver(observer);
223     }
224 
225     @Test
updateState_skipUnavailablePrefs()226     public void updateState_skipUnavailablePrefs() {
227         final List<AbstractPreferenceController> preferenceControllers = mTestFragment.mControllers;
228         final AbstractPreferenceController mockController1 =
229                 mock(AbstractPreferenceController.class);
230         final AbstractPreferenceController mockController2 =
231                 mock(AbstractPreferenceController.class);
232         when(mockController1.getPreferenceKey()).thenReturn("key1");
233         when(mockController2.getPreferenceKey()).thenReturn("key2");
234         preferenceControllers.add(mockController1);
235         preferenceControllers.add(mockController2);
236         when(mockController1.isAvailable()).thenReturn(false);
237         when(mockController2.isAvailable()).thenReturn(true);
238         mTestFragment.onAttach(RuntimeEnvironment.application);
239         mTestFragment.onResume();
240 
241         verify(mockController1).getPreferenceKey();
242         verify(mockController2, times(2)).getPreferenceKey();
243     }
244 
245     @Test
updateState_doesNotSkipControllersOfSameClass()246     public void updateState_doesNotSkipControllersOfSameClass() {
247         final AbstractPreferenceController mockController1 =
248                 mock(AbstractPreferenceController.class);
249         final AbstractPreferenceController mockController2 =
250                 mock(AbstractPreferenceController.class);
251         mTestFragment.addPreferenceController(mockController1);
252         mTestFragment.addPreferenceController(mockController2);
253         when(mockController1.isAvailable()).thenReturn(true);
254         when(mockController2.isAvailable()).thenReturn(true);
255 
256         mTestFragment.updatePreferenceStates();
257 
258         verify(mockController1).getPreferenceKey();
259         verify(mockController2).getPreferenceKey();
260     }
261 
262     @Test
onExpandButtonClick_shouldLogAdvancedButtonExpand()263     public void onExpandButtonClick_shouldLogAdvancedButtonExpand() {
264         final MetricsFeatureProvider metricsFeatureProvider
265                 = mFakeFeatureFactory.getMetricsFeatureProvider();
266         mTestFragment.onExpandButtonClick();
267 
268         verify(metricsFeatureProvider).action(SettingsEnums.PAGE_UNKNOWN,
269                 MetricsEvent.ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND,
270                 DASHBOARD_CONTAINER, null, 0);
271     }
272 
273     @Test
updatePreferenceVisibility_prefKeyNull_shouldNotCrash()274     public void updatePreferenceVisibility_prefKeyNull_shouldNotCrash() {
275         final Map<Class, List<AbstractPreferenceController>> prefControllers = new HashMap<>();
276         final List<AbstractPreferenceController> controllerList = new ArrayList<>();
277         controllerList.add(new TestPreferenceController(mContext));
278         prefControllers.put(TestPreferenceController.class, controllerList);
279         mTestFragment.mBlockerController = new UiBlockerController(Arrays.asList("pref_key"));
280 
281         // Should not crash
282         mTestFragment.updatePreferenceVisibility(prefControllers);
283     }
284 
285     @Test
checkUiBlocker_noUiBlocker_controllerIsNull()286     public void checkUiBlocker_noUiBlocker_controllerIsNull() {
287         mTestFragment.mBlockerController = null;
288         mControllers.add(new TestPreferenceController(mContext));
289 
290         mTestFragment.checkUiBlocker(mControllers);
291 
292         assertThat(mTestFragment.mBlockerController).isNull();
293     }
294 
295     @Test
checkUiBlocker_hasUiBlockerAndControllerIsAvailable_controllerNotNull()296     public void checkUiBlocker_hasUiBlockerAndControllerIsAvailable_controllerNotNull() {
297         final BlockingSlicePrefController controller =
298                 new BlockingSlicePrefController(mContext, "pref_key");
299         controller.setSliceUri(Uri.parse("testUri"));
300         mTestFragment.mBlockerController = null;
301         mControllers.add(new TestPreferenceController(mContext));
302         mControllers.add(controller);
303 
304         mTestFragment.checkUiBlocker(mControllers);
305 
306         assertThat(mTestFragment.mBlockerController).isNotNull();
307     }
308 
309     @Test
checkUiBlocker_hasUiBlockerAndControllerIsNotAvailable_controllerIsNull()310     public void checkUiBlocker_hasUiBlockerAndControllerIsNotAvailable_controllerIsNull() {
311         mTestFragment.mBlockerController = null;
312         mControllers.add(new TestPreferenceController(mContext));
313         mControllers.add(new BlockingSlicePrefController(mContext, "pref_key"));
314 
315         mTestFragment.checkUiBlocker(mControllers);
316 
317         assertThat(mTestFragment.mBlockerController).isNull();
318     }
319 
320     @Test
createPreference_isProviderTile_returnSwitchPreference()321     public void createPreference_isProviderTile_returnSwitchPreference() {
322         final Preference pref = mTestFragment.createPreference(mProviderTile);
323 
324         assertThat(pref).isInstanceOf(SwitchPreference.class);
325     }
326 
327     @Test
createPreference_isActivityTileAndHasSwitch_returnPrimarySwitchPreference()328     public void createPreference_isActivityTileAndHasSwitch_returnPrimarySwitchPreference() {
329         mActivityTile.getMetaData().putString(META_DATA_PREFERENCE_SWITCH_URI, "uri");
330 
331         final Preference pref = mTestFragment.createPreference(mActivityTile);
332 
333         assertThat(pref).isInstanceOf(PrimarySwitchPreference.class);
334     }
335 
336     public static class TestPreferenceController extends AbstractPreferenceController
337             implements PreferenceControllerMixin {
338 
TestPreferenceController(Context context)339         private TestPreferenceController(Context context) {
340             super(context);
341         }
342 
343         @Override
handlePreferenceTreeClick(Preference preference)344         public boolean handlePreferenceTreeClick(Preference preference) {
345             return false;
346         }
347 
348         @Override
isAvailable()349         public boolean isAvailable() {
350             return false;
351         }
352 
353         @Override
getPreferenceKey()354         public String getPreferenceKey() {
355             return null;
356         }
357 
358         @Override
updateNonIndexableKeys(List<String> keys)359         public void updateNonIndexableKeys(List<String> keys) {
360         }
361     }
362 
363     private static class TestFragment extends DashboardFragment {
364 
365         private final PreferenceManager mPreferenceManager;
366         private final Context mContext;
367         private final List<AbstractPreferenceController> mControllers;
368         private final ContentResolver mContentResolver;
369 
370         public final PreferenceScreen mScreen;
371         private boolean mIsParalleled;
372 
TestFragment(Context context)373         public TestFragment(Context context) {
374             mContext = context;
375             mPreferenceManager = mock(PreferenceManager.class);
376             mScreen = mock(PreferenceScreen.class);
377             mContentResolver = mock(ContentResolver.class);
378             mControllers = new ArrayList<>();
379             mIsParalleled = true;
380 
381             when(mPreferenceManager.getContext()).thenReturn(mContext);
382             ReflectionHelpers.setField(
383                     this, "mVisibilityLoggerMixin", mock(VisibilityLoggerMixin.class));
384         }
385 
386         @Override
getContext()387         public Context getContext() {
388             return mContext;
389         }
390 
391         @Override
getMetricsCategory()392         public int getMetricsCategory() {
393             return DASHBOARD_CONTAINER;
394         }
395 
396         @Override
getPreferenceScreen()397         public PreferenceScreen getPreferenceScreen() {
398             return mScreen;
399         }
400 
401         @Override
getLogTag()402         protected String getLogTag() {
403             return "TEST_FRAG";
404         }
405 
406         @Override
getPreferenceScreenResId()407         protected int getPreferenceScreenResId() {
408             return 0;
409         }
410 
411         @Override
createPreferenceControllers(Context context)412         protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
413             return mControllers;
414         }
415 
416         @Override
getPreferenceManager()417         public PreferenceManager getPreferenceManager() {
418             return mPreferenceManager;
419         }
420 
421         @Override
getContentResolver()422         protected ContentResolver getContentResolver() {
423             return mContentResolver;
424         }
425 
isParalleledControllers()426         protected boolean isParalleledControllers() {
427             return mIsParalleled;
428         }
429 
setUsingControllerEnhancement(boolean isParalleled)430         void setUsingControllerEnhancement(boolean isParalleled) {
431             mIsParalleled = isParalleled;
432         }
433     }
434 
435     private static class TestDynamicDataObserver extends DynamicDataObserver {
436 
437         @Override
getUri()438         public Uri getUri() {
439             return Uri.parse("content://abc");
440         }
441 
442         @Override
onDataChanged()443         public void onDataChanged() {
444         }
445     }
446 
447     @Implements(PreferenceFragmentCompat.class)
448     public static class ShadowPreferenceFragmentCompat {
449 
450         @Implementation
onStart()451         public void onStart() {
452             // do nothing
453         }
454 
455         @Implementation
onStop()456         public void onStop() {
457             // do nothing
458         }
459     }
460 }
461