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 
17 package com.android.settings.dashboard;
18 
19 import static android.content.Intent.EXTRA_USER;
20 
21 import static com.android.settingslib.drawer.SwitchesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR;
22 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
23 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
24 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
25 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
26 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
27 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
28 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
29 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
30 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
31 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
32 
33 import static com.google.common.truth.Truth.assertThat;
34 
35 import static org.mockito.ArgumentMatchers.any;
36 import static org.mockito.ArgumentMatchers.anyInt;
37 import static org.mockito.ArgumentMatchers.eq;
38 import static org.mockito.Mockito.doReturn;
39 import static org.mockito.Mockito.mock;
40 import static org.mockito.Mockito.never;
41 import static org.mockito.Mockito.spy;
42 import static org.mockito.Mockito.verify;
43 import static org.mockito.Mockito.verifyNoInteractions;
44 import static org.mockito.Mockito.when;
45 
46 import android.app.Application;
47 import android.app.PendingIntent;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.content.pm.ActivityInfo;
51 import android.content.pm.PackageManager;
52 import android.content.pm.ProviderInfo;
53 import android.content.pm.ResolveInfo;
54 import android.graphics.Bitmap;
55 import android.graphics.drawable.Drawable;
56 import android.graphics.drawable.Icon;
57 import android.os.Bundle;
58 import android.os.UserHandle;
59 import android.os.UserManager;
60 import android.util.Pair;
61 
62 import androidx.fragment.app.Fragment;
63 import androidx.fragment.app.FragmentActivity;
64 import androidx.preference.Preference;
65 import androidx.preference.SwitchPreference;
66 import androidx.test.core.app.ApplicationProvider;
67 
68 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
69 import com.android.settings.R;
70 import com.android.settings.SettingsActivity;
71 import com.android.settings.Utils;
72 import com.android.settings.homepage.TopLevelHighlightMixin;
73 import com.android.settings.homepage.TopLevelSettings;
74 import com.android.settings.search.SearchFeatureProviderImpl;
75 import com.android.settings.testutils.FakeFeatureFactory;
76 import com.android.settings.testutils.shadow.ShadowActivityEmbeddingUtils;
77 import com.android.settings.testutils.shadow.ShadowSplitController;
78 import com.android.settings.testutils.shadow.ShadowTileUtils;
79 import com.android.settings.testutils.shadow.ShadowUserManager;
80 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
81 import com.android.settingslib.drawer.ActivityTile;
82 import com.android.settingslib.drawer.CategoryKey;
83 import com.android.settingslib.drawer.ProviderTile;
84 import com.android.settingslib.drawer.Tile;
85 import com.android.settingslib.drawer.TileUtils;
86 
87 import org.junit.Before;
88 import org.junit.Rule;
89 import org.junit.Test;
90 import org.junit.runner.RunWith;
91 import org.mockito.Answers;
92 import org.mockito.ArgumentCaptor;
93 import org.mockito.Mock;
94 import org.mockito.junit.MockitoJUnit;
95 import org.mockito.junit.MockitoRule;
96 import org.robolectric.Robolectric;
97 import org.robolectric.RobolectricTestRunner;
98 import org.robolectric.Shadows;
99 import org.robolectric.annotation.Config;
100 import org.robolectric.shadows.ShadowActivity;
101 import org.robolectric.util.ReflectionHelpers;
102 
103 import java.util.ArrayList;
104 import java.util.List;
105 
106 @RunWith(RobolectricTestRunner.class)
107 @Config(shadows = ShadowUserManager.class)
108 public class DashboardFeatureProviderImplTest {
109     @Rule
110     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
111 
112     private static final String KEY = "key";
113     private static final String SWITCH_URI = "content://com.android.settings/tile_switch";
114 
115     private final Application mApplication = ApplicationProvider.getApplicationContext();
116 
117     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
118     private FragmentActivity mActivity;
119     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
120     private UserManager mUserManager;
121     @Mock
122     private PackageManager mPackageManager;
123     private FakeFeatureFactory mFeatureFactory;
124 
125     private Context mContext;
126     private ActivityInfo mActivityInfo;
127     private ProviderInfo mProviderInfo;
128     private Bundle mSwitchMetaData;
129     private DashboardFeatureProviderImpl mImpl;
130     private boolean mForceRoundedIcon;
131     private DashboardFragment mFragment;
132 
133     @Before
setUp()134     public void setUp() {
135         mContext = spy(mApplication);
136         doReturn(mApplication).when(mActivity).getApplicationContext();
137         mForceRoundedIcon = false;
138         mActivityInfo = new ActivityInfo();
139         mActivityInfo.packageName = mContext.getPackageName();
140         mActivityInfo.name = "class";
141         mActivityInfo.metaData = new Bundle();
142         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
143         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_SUMMARY,
144                 R.string.about_settings_summary);
145 
146         mProviderInfo = new ProviderInfo();
147         mProviderInfo.packageName = mContext.getPackageName();
148         mProviderInfo.name = "provider";
149         mProviderInfo.authority = "com.android.settings";
150         mSwitchMetaData = new Bundle();
151         mSwitchMetaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
152         mSwitchMetaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
153         mSwitchMetaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
154         mSwitchMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, SWITCH_URI);
155 
156         doReturn(mPackageManager).when(mContext).getPackageManager();
157         when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
158                 .thenReturn(new ResolveInfo());
159         mFeatureFactory = FakeFeatureFactory.setupForTest();
160         mImpl = new DashboardFeatureProviderImpl(mContext);
161         mFragment = new TestFragment();
162     }
163 
164     @Test
shouldHoldAppContext()165     public void shouldHoldAppContext() {
166         assertThat(mImpl.mContext).isEqualTo(mContext.getApplicationContext());
167     }
168 
169     @Test
bindPreference_shouldBindAllData()170     public void bindPreference_shouldBindAllData() {
171         final Preference preference = new Preference(mApplication);
172         final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE));
173         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10);
174         doReturn(Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565)))
175                 .when(tile).getIcon(any(Context.class));
176         mActivityInfo.metaData.putString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS, "HI");
177         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
178                 preference, tile, "123", Preference.DEFAULT_ORDER);
179 
180         assertThat(preference.getTitle()).isEqualTo(mContext.getText(R.string.settings_label));
181         assertThat(preference.getSummary())
182                 .isEqualTo(mContext.getText(R.string.about_settings_summary));
183         assertThat(preference.getIcon()).isNotNull();
184         assertThat(preference.getFragment()).isEqualTo(
185                 mActivityInfo.metaData.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS));
186         assertThat(preference.getOrder()).isEqualTo(tile.getOrder());
187     }
188 
189     @Test
bindPreference_shouldBindAllSwitchData()190     public void bindPreference_shouldBindAllSwitchData() {
191         final Preference preference = new SwitchPreference(mApplication);
192         final Tile tile = spy(new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE,
193                 mSwitchMetaData));
194         mSwitchMetaData.putInt(META_DATA_KEY_ORDER, 10);
195         doReturn(Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565)))
196                 .when(tile).getIcon(any(Context.class));
197         final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
198                 mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key*/,
199                 Preference.DEFAULT_ORDER);
200 
201         assertThat(preference.getTitle()).isEqualTo(mContext.getText(R.string.settings_label));
202         assertThat(preference.getSummary())
203                 .isEqualTo(mContext.getText(R.string.about_settings_summary));
204         assertThat(preference.getKey()).isEqualTo(KEY);
205         assertThat(preference.getIcon()).isNotNull();
206         assertThat(preference.getOrder()).isEqualTo(tile.getOrder());
207         assertThat(observers.get(0).getUri().toString()).isEqualTo(SWITCH_URI);
208     }
209 
210     @Test
bindPreference_providerTileWithPendingIntent_shouldBindIntent()211     public void bindPreference_providerTileWithPendingIntent_shouldBindIntent() {
212         final Preference preference = new SwitchPreference(mApplication);
213         Bundle metaData = new Bundle();
214         metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
215         metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
216         metaData.putInt(META_DATA_KEY_ORDER, 10);
217         metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
218         final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
219         PendingIntent pendingIntent =
220                 PendingIntent.getActivity(mApplication, 0, new Intent("test"), 0);
221         tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);
222 
223         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
224                 preference, tile, "123", Preference.DEFAULT_ORDER);
225 
226         assertThat(preference.getFragment()).isNull();
227         assertThat(preference.getOnPreferenceClickListener()).isNotNull();
228         assertThat(preference.getOrder()).isEqualTo(tile.getOrder());
229     }
230 
231     @Test
bindPreference_noFragmentMetadata_shouldBindIntent()232     public void bindPreference_noFragmentMetadata_shouldBindIntent() {
233         final Preference preference = new Preference(mApplication);
234         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10);
235         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
236 
237         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
238                 preference, tile, "123", Preference.DEFAULT_ORDER);
239 
240         assertThat(preference.getFragment()).isNull();
241         assertThat(preference.getOnPreferenceClickListener()).isNotNull();
242         assertThat(preference.getOrder()).isEqualTo(tile.getOrder());
243     }
244 
245     @Test
bindPreference_noFragmentMetadata_shouldBindToProfileSelector()246     public void bindPreference_noFragmentMetadata_shouldBindToProfileSelector() {
247         final Preference preference = new Preference(mApplication);
248         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
249         tile.userHandle = new ArrayList<>();
250         tile.userHandle.add(mock(UserHandle.class));
251         tile.userHandle.add(mock(UserHandle.class));
252 
253         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
254                 preference, tile, "123", Preference.DEFAULT_ORDER);
255         preference.getOnPreferenceClickListener().onPreferenceClick(null);
256 
257         verify(mActivity).getSupportFragmentManager();
258     }
259 
260     @Test
bindPreference_noFragmentMetadataSingleUser_shouldBindToDirectLaunchIntent()261     public void bindPreference_noFragmentMetadataSingleUser_shouldBindToDirectLaunchIntent() {
262         final Preference preference = new Preference(mApplication);
263         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
264         tile.userHandle = new ArrayList<>();
265         tile.userHandle.add(mock(UserHandle.class));
266 
267         when(mActivity.getSystemService(Context.USER_SERVICE))
268                 .thenReturn(mUserManager);
269 
270         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
271                 preference, tile, "123", Preference.DEFAULT_ORDER);
272         preference.getOnPreferenceClickListener().onPreferenceClick(null);
273 
274         verify(mFeatureFactory.metricsFeatureProvider).logStartedIntent(
275                 any(Intent.class),
276                 eq(MetricsEvent.SETTINGS_GESTURES));
277         verify(mActivity)
278                 .startActivityAsUser(any(Intent.class), any(UserHandle.class));
279     }
280 
281     @Test
bindPreference_toInternalSettingActivity_shouldBindToDirectLaunchIntentAndNotLog()282     public void bindPreference_toInternalSettingActivity_shouldBindToDirectLaunchIntentAndNotLog() {
283         final Preference preference = new Preference(mApplication);
284         mActivityInfo.packageName = mApplication.getPackageName();
285         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
286         tile.userHandle = new ArrayList<>();
287         tile.userHandle.add(mock(UserHandle.class));
288 
289         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
290                 preference, tile, "123", Preference.DEFAULT_ORDER);
291         preference.getOnPreferenceClickListener().onPreferenceClick(null);
292         verify(mFeatureFactory.metricsFeatureProvider).logStartedIntent(
293                 any(Intent.class),
294                 anyInt());
295         verify(mActivity)
296                 .startActivityAsUser(any(Intent.class), any(UserHandle.class));
297     }
298 
299     @Test
bindPreference_nullPreference_shouldIgnore()300     public void bindPreference_nullPreference_shouldIgnore() {
301         final Tile tile = mock(Tile.class);
302         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
303                 null /* keys */, tile, "123", Preference.DEFAULT_ORDER);
304 
305         verifyNoInteractions(tile);
306     }
307 
308     @Test
bindPreference_withNullKeyNullPriority_shouldGenerateKeyAndPriority()309     public void bindPreference_withNullKeyNullPriority_shouldGenerateKeyAndPriority() {
310         final Preference preference = new Preference(mApplication);
311         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
312         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
313                 preference, tile, null /* key */,
314                 Preference.DEFAULT_ORDER);
315 
316         assertThat(preference.getKey()).isNotNull();
317         assertThat(preference.getOrder()).isEqualTo(Preference.DEFAULT_ORDER);
318     }
319 
320     @Test
bindPreference_noSummary_shouldSetNullSummary()321     public void bindPreference_noSummary_shouldSetNullSummary() {
322         final Preference preference = new Preference(mApplication);
323         mActivityInfo.metaData.remove(META_DATA_PREFERENCE_SUMMARY);
324 
325         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
326 
327         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
328                 preference, tile, null /* key */, Preference.DEFAULT_ORDER);
329 
330         assertThat(preference.getSummary()).isNull();
331     }
332 
333     @Test
334     @Config(shadows = {ShadowTileUtils.class})
bindPreference_hasSummaryUri_shouldLoadSummaryFromContentProviderAndHaveObserver()335     public void bindPreference_hasSummaryUri_shouldLoadSummaryFromContentProviderAndHaveObserver() {
336         final Preference preference = new Preference(mApplication);
337         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
338         final String uriString = "content://com.android.settings/tile_summary";
339         mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, uriString);
340 
341         final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
342                 mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */,
343                 Preference.DEFAULT_ORDER);
344 
345         assertThat(observers.get(0).getUri().toString()).isEqualTo(uriString);
346         assertThat(preference.getSummary()).isNotEqualTo(ShadowTileUtils.MOCK_TEXT);
347 
348         observers.get(0).updateUi();
349 
350         assertThat(preference.getSummary()).isEqualTo(ShadowTileUtils.MOCK_TEXT);
351     }
352 
353     @Test
354     @Config(shadows = {ShadowTileUtils.class})
bindPreference_hasTitleUri_shouldLoadFromContentProviderAndHaveObserver()355     public void bindPreference_hasTitleUri_shouldLoadFromContentProviderAndHaveObserver() {
356         final Preference preference = new Preference(mApplication);
357         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
358         final String uriString =  "content://com.android.settings/tile_title";
359         mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE_URI, uriString);
360 
361         final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
362                 mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */,
363                 Preference.DEFAULT_ORDER);
364 
365         assertThat(observers.get(0).getUri().toString()).isEqualTo(uriString);
366         assertThat(preference.getTitle()).isNotEqualTo(ShadowTileUtils.MOCK_TEXT);
367 
368         observers.get(0).updateUi();
369 
370         assertThat(preference.getTitle()).isEqualTo(ShadowTileUtils.MOCK_TEXT);
371     }
372 
373     @Test
374     @Config(shadows = {ShadowTileUtils.class})
bindPreference_onCheckedChanged_shouldPutStateToContentProvider()375     public void bindPreference_onCheckedChanged_shouldPutStateToContentProvider() {
376         final SwitchPreference preference = new SwitchPreference(mApplication);
377         final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE,
378                 mSwitchMetaData);
379         final Bundle bundle = new Bundle();
380         bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, false);
381         ShadowTileUtils.setResultBundle(bundle);
382         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
383                 preference, tile, null /* key */, Preference.DEFAULT_ORDER);
384 
385         preference.callChangeListener(false);
386 
387         assertThat(ShadowTileUtils.getProviderChecked()).isFalse();
388 
389         preference.callChangeListener(true);
390 
391         assertThat(ShadowTileUtils.getProviderChecked()).isTrue();
392     }
393 
394     @Test
395     @Config(shadows = {ShadowTileUtils.class})
bindPreference_onCheckedChangedError_shouldRevertCheckedState()396     public void bindPreference_onCheckedChangedError_shouldRevertCheckedState() {
397         final SwitchPreference preference = new SwitchPreference(mApplication);
398         final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE,
399                 mSwitchMetaData);
400         final Bundle bundle = new Bundle();
401         bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, true);
402         ShadowTileUtils.setResultBundle(bundle);
403         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
404                 preference, tile, null /* key */, Preference.DEFAULT_ORDER);
405 
406         preference.callChangeListener(true);
407 
408         assertThat(preference.isChecked()).isFalse();
409 
410         preference.callChangeListener(false);
411 
412         assertThat(preference.isChecked()).isTrue();
413     }
414 
415     @Test
416     @Config(shadows = {ShadowTileUtils.class})
bindPreference_callbackOnChanged_shouldLoadFromContentProvider()417     public void bindPreference_callbackOnChanged_shouldLoadFromContentProvider() {
418         final SwitchPreference preference = new SwitchPreference(mApplication);
419         final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE,
420                 mSwitchMetaData);
421         final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
422                 mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */,
423                 Preference.DEFAULT_ORDER);
424         observers.get(0).updateUi();
425 
426         ShadowTileUtils.setProviderChecked(false);
427         observers.get(0).onDataChanged();
428 
429         assertThat(preference.isChecked()).isFalse();
430 
431         ShadowTileUtils.setProviderChecked(true);
432         observers.get(0).onDataChanged();
433 
434         assertThat(preference.isChecked()).isTrue();
435     }
436 
437     @Test
bindPreference_withNullKeyTileKey_shouldUseTileKey()438     public void bindPreference_withNullKeyTileKey_shouldUseTileKey() {
439         final Preference preference = new Preference(mApplication);
440         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
441         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
442         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
443                 preference, tile, null /* key */, Preference.DEFAULT_ORDER);
444 
445         assertThat(preference.getKey()).isEqualTo(tile.getKey(mContext));
446     }
447 
448     @Test
bindIcon_withStaticIcon_shouldLoadStaticIcon()449     public void bindIcon_withStaticIcon_shouldLoadStaticIcon() {
450         final Preference preference = new Preference(mApplication);
451         mActivityInfo.packageName = mApplication.getPackageName();
452         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
453         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
454         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON, R.drawable.ic_add_40dp);
455 
456         mImpl.bindIcon(preference, tile, false /* forceRoundedIcon */);
457 
458         final Bitmap preferenceBmp = Utils.createIconWithDrawable(preference.getIcon()).getBitmap();
459         final Drawable staticIcon = Icon.createWithResource(mActivityInfo.packageName,
460                 R.drawable.ic_add_40dp).loadDrawable(preference.getContext());
461         final Bitmap staticIconBmp = Utils.createIconWithDrawable(staticIcon).getBitmap();
462         assertThat(preferenceBmp.sameAs(staticIconBmp)).isTrue();
463     }
464 
465     @Test
466     @Config(shadows = {ShadowTileUtils.class})
bindIcon_withIconUri_shouldLoadIconFromContentProvider()467     public void bindIcon_withIconUri_shouldLoadIconFromContentProvider() {
468         final Preference preference = new Preference(mApplication);
469         mActivityInfo.packageName = mApplication.getPackageName();
470         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
471         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
472         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_ICON_URI,
473                 "content://com.android.settings/tile_icon");
474 
475         mImpl.bindIcon(preference, tile, false /* forceRoundedIcon */);
476 
477         assertThat(preference.getIcon()).isNotNull();
478     }
479 
480     @Test
481     @Config(shadows = {ShadowTileUtils.class})
bindIcon_withStaticIconAndIconUri_shouldLoadIconFromContentProvider()482     public void bindIcon_withStaticIconAndIconUri_shouldLoadIconFromContentProvider() {
483         final Preference preference = new Preference(mApplication);
484         mActivityInfo.packageName = mApplication.getPackageName();
485         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
486         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
487         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON, R.drawable.ic_add_40dp);
488         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_ICON_URI,
489                 "content://com.android.settings/tile_icon");
490 
491         mImpl.bindIcon(preference, tile, false /* forceRoundedIcon */);
492 
493         final Bitmap preferenceBmp = Utils.createIconWithDrawable(preference.getIcon()).getBitmap();
494         final Drawable staticIcon = Icon.createWithResource(mActivityInfo.packageName,
495                 R.drawable.ic_add_40dp).loadDrawable(preference.getContext());
496         final Bitmap staticIconBmp = Utils.createIconWithDrawable(staticIcon).getBitmap();
497         assertThat(preferenceBmp.sameAs(staticIconBmp)).isFalse();
498 
499         final Pair<String, Integer> iconInfo = TileUtils.getIconFromUri(
500                 mContext, "pkg", null /* uri */, null /* providerMap */);
501         final Drawable iconFromUri = Icon.createWithResource(iconInfo.first, iconInfo.second)
502                 .loadDrawable(preference.getContext());
503         final Bitmap iconBmpFromUri = Utils.createIconWithDrawable(iconFromUri).getBitmap();
504         assertThat(preferenceBmp.sameAs(iconBmpFromUri)).isTrue();
505     }
506 
507     @Test
508     @Config(shadows = {ShadowTileUtils.class})
bindIcon_noIcon_shouldNotLoadIcon()509     public void bindIcon_noIcon_shouldNotLoadIcon() {
510         final Preference preference = new Preference(mApplication);
511         mActivityInfo.packageName = mApplication.getPackageName();
512         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
513         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
514 
515         mImpl.bindIcon(preference, tile, false /* forceRoundedIcon */);
516 
517         assertThat(preference.getIcon()).isNull();
518     }
519 
520     @Test
bindPreference_withBaseOrder_shouldOffsetOrder()521     public void bindPreference_withBaseOrder_shouldOffsetOrder() {
522         final int baseOrder = 100;
523         final Preference preference = new Preference(mApplication);
524         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10);
525         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
526 
527         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
528                 preference, tile, "123", baseOrder);
529 
530         assertThat(preference.getOrder()).isEqualTo(tile.getOrder() + baseOrder);
531     }
532 
533     @Test
bindPreference_withOrderMetadata_shouldUseOrderInMetadata()534     public void bindPreference_withOrderMetadata_shouldUseOrderInMetadata() {
535         final Preference preference = new Preference(mApplication);
536         final int testOrder = -30;
537         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10);
538         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
539         mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, testOrder);
540         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
541                 preference, tile, "123", Preference.DEFAULT_ORDER);
542 
543         assertThat(preference.getOrder()).isEqualTo(testOrder);
544     }
545 
546     @Test
bindPreference_invalidOrderMetadata_shouldIgnore()547     public void bindPreference_invalidOrderMetadata_shouldIgnore() {
548         final Preference preference = new Preference(mApplication);
549         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
550         mActivityInfo.metaData.putString(META_DATA_KEY_ORDER, "hello");
551 
552         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
553                 preference, tile, "123", Preference.DEFAULT_ORDER);
554 
555         assertThat(preference.getOrder()).isEqualTo(Preference.DEFAULT_ORDER);
556     }
557 
558     @Test
bindPreference_withIntentActionMetadata_shouldSetLaunchAction()559     public void bindPreference_withIntentActionMetadata_shouldSetLaunchAction() {
560         FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).get();
561         final Preference preference = new Preference(mApplication);
562         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
563         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
564         mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction");
565         tile.userHandle = null;
566         mImpl.bindPreferenceToTileAndGetObservers(activity, mFragment, mForceRoundedIcon,
567                 preference, tile, "123", Preference.DEFAULT_ORDER);
568         preference.performClick();
569         ShadowActivity shadowActivity = Shadows.shadowOf(activity);
570 
571         final Intent launchIntent = shadowActivity.getNextStartedActivityForResult().intent;
572         assertThat(launchIntent.getAction())
573                 .isEqualTo("TestAction");
574         assertThat(
575                 launchIntent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
576                 .isEqualTo(MetricsEvent.SETTINGS_GESTURES);
577     }
578 
579     /** This test is for large screen devices Activity embedding. */
580     @Test
581     @Config(shadows = {ShadowActivityEmbeddingUtils.class, ShadowSplitController.class})
bindPreference_clickHighlightedPreference_shouldNotStartActivity()582     public void bindPreference_clickHighlightedPreference_shouldNotStartActivity() {
583         ShadowSplitController.setIsActivityEmbedded(true);
584         ShadowActivityEmbeddingUtils.setIsEmbeddingActivityEnabled(true);
585         mFeatureFactory.searchFeatureProvider = new SearchFeatureProviderImpl();
586 
587         String clickPrefKey = "highlight_pref_key";
588         String highlightMixinPrefKey = "highlight_pref_key";
589         FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).get();
590         Preference preference = new Preference(mApplication);
591         Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
592         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
593         mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction");
594         tile.userHandle = null;
595 
596         TopLevelSettings largeScreenTopLevelSettings = spy(new TopLevelSettings(
597                 new TestTopLevelHighlightMixin(highlightMixinPrefKey,
598                 true /* activityEmbedded */)));
599         doReturn(true).when(largeScreenTopLevelSettings).isActivityEmbedded();
600         largeScreenTopLevelSettings.setHighlightPreferenceKey(clickPrefKey);
601 
602         mImpl.bindPreferenceToTileAndGetObservers(activity, largeScreenTopLevelSettings,
603                 mForceRoundedIcon, preference, tile, clickPrefKey, Preference.DEFAULT_ORDER);
604         preference.performClick();
605 
606         ShadowActivity shadowActivity = Shadows.shadowOf(activity);
607         assertThat(shadowActivity.getNextStartedActivityForResult()).isEqualTo(null);
608     }
609 
610     /** This test is for large screen devices Activity embedding. */
611     @Test
612     @Config(shadows = {ShadowActivityEmbeddingUtils.class, ShadowSplitController.class})
bindPreference_clickNotHighlightedPreference_shouldStartActivity()613     public void bindPreference_clickNotHighlightedPreference_shouldStartActivity() {
614         ShadowSplitController.setIsActivityEmbedded(true);
615         ShadowActivityEmbeddingUtils.setIsEmbeddingActivityEnabled(true);
616         mFeatureFactory.searchFeatureProvider = new SearchFeatureProviderImpl();
617 
618         String clickPrefKey = "not_highlight_pref_key";
619         String highlightMixinPrefKey = "highlight_pref_key";
620         FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).get();
621         Preference preference = new Preference(mApplication);
622         Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
623         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
624         mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction");
625         tile.userHandle = null;
626 
627         TopLevelSettings largeScreenTopLevelSettings = spy(new TopLevelSettings(
628                 new TestTopLevelHighlightMixin(highlightMixinPrefKey,
629                 true /* activityEmbedded */)));
630         doReturn(true).when(largeScreenTopLevelSettings).isActivityEmbedded();
631         largeScreenTopLevelSettings.setHighlightPreferenceKey(clickPrefKey);
632 
633         mImpl.bindPreferenceToTileAndGetObservers(activity, largeScreenTopLevelSettings,
634                 mForceRoundedIcon, preference, tile, clickPrefKey, Preference.DEFAULT_ORDER);
635         preference.performClick();
636 
637         Intent launchIntent = Shadows.shadowOf(activity).getNextStartedActivityForResult().intent;
638         assertThat(launchIntent.getAction()).isEqualTo("TestAction");
639     }
640 
641     @Test
clickPreference_withUnresolvableIntent_shouldNotLaunchAnything()642     public void clickPreference_withUnresolvableIntent_shouldNotLaunchAnything() {
643         ReflectionHelpers.setField(
644                 mImpl, "mPackageManager", mApplication.getPackageManager());
645         FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).get();
646         final Preference preference = new Preference(mApplication);
647         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
648         mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key");
649         mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction");
650         tile.userHandle = null;
651 
652         mImpl.bindPreferenceToTileAndGetObservers(activity, mFragment, mForceRoundedIcon,
653                 preference, tile, "123", Preference.DEFAULT_ORDER);
654         preference.performClick();
655 
656         final ShadowActivity.IntentForResult launchIntent =
657                 Shadows.shadowOf(activity).getNextStartedActivityForResult();
658 
659         assertThat(launchIntent).isNull();
660     }
661 
662     @Test
clickPreference_providerTileWithPendingIntent_singleUser_executesPendingIntent()663     public void clickPreference_providerTileWithPendingIntent_singleUser_executesPendingIntent() {
664         final Preference preference = new SwitchPreference(mApplication);
665         Bundle metaData = new Bundle();
666         metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
667         metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
668         metaData.putInt(META_DATA_KEY_ORDER, 10);
669         metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
670         final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
671         PendingIntent pendingIntent =
672                 PendingIntent.getActivity(mApplication, 0, new Intent("test"), 0);
673         tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);
674 
675         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
676                 preference, tile, "123", Preference.DEFAULT_ORDER);
677         preference.performClick();
678 
679         Intent nextStartedActivity =
680                 Shadows.shadowOf(mApplication).peekNextStartedActivity();
681         assertThat(nextStartedActivity).isNotNull();
682         assertThat(nextStartedActivity.getAction()).isEqualTo("test");
683     }
684 
685     @Test
clickPreference_providerTileWithPendingIntent_multiUser_showsProfileDialog()686     public void clickPreference_providerTileWithPendingIntent_multiUser_showsProfileDialog() {
687         final Preference preference = new SwitchPreference(mApplication);
688         Bundle metaData = new Bundle();
689         metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
690         metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
691         metaData.putInt(META_DATA_KEY_ORDER, 10);
692         metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
693         final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
694         PendingIntent pendingIntent =
695                 PendingIntent.getActivity(mApplication, 0, new Intent("test"), 0);
696         tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);
697         tile.pendingIntentMap.put(new UserHandle(10), pendingIntent);
698 
699         mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
700                 preference, tile, "123", Preference.DEFAULT_ORDER);
701         preference.performClick();
702 
703         Fragment dialogFragment =
704                 mActivity.getSupportFragmentManager().findFragmentByTag("select_profile");
705         assertThat(dialogFragment).isNotNull();
706         Intent nextStartedActivity =
707                 Shadows.shadowOf(mApplication).peekNextStartedActivity();
708         assertThat(nextStartedActivity).isNull();
709     }
710 
711     @Test
openTileIntent_profileSelectionDialog_shouldShow()712     public void openTileIntent_profileSelectionDialog_shouldShow() {
713         ShadowUserManager.getShadow().addUser(10, "Someone", 0);
714 
715         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
716         final ArrayList<UserHandle> handles = new ArrayList<>();
717         handles.add(new UserHandle(0));
718         handles.add(new UserHandle(10));
719         tile.userHandle = handles;
720         mImpl.openTileIntent(mActivity, tile);
721 
722         verify(mActivity, never())
723                 .startActivity(any(Intent.class));
724         verify(mActivity).getSupportFragmentManager();
725     }
726 
727     @Test
openTileIntent_profileSelectionDialog_explicitMetadataShouldShow()728     public void openTileIntent_profileSelectionDialog_explicitMetadataShouldShow() {
729         ShadowUserManager.getShadow().addUser(10, "Someone", 0);
730 
731         mActivityInfo.metaData.putString(META_DATA_KEY_PROFILE, PROFILE_ALL);
732         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
733         final ArrayList<UserHandle> handles = new ArrayList<>();
734         handles.add(new UserHandle(0));
735         handles.add(new UserHandle(10));
736         tile.userHandle = handles;
737         mImpl.openTileIntent(mActivity, tile);
738 
739         verify(mActivity, never())
740                 .startActivity(any(Intent.class));
741         verify(mActivity).getSupportFragmentManager();
742     }
743 
744     @Test
openTileIntent_profileSelectionDialog_shouldNotShow()745     public void openTileIntent_profileSelectionDialog_shouldNotShow() {
746         ShadowUserManager.getShadow().addUser(10, "Someone", 0);
747 
748         mActivityInfo.metaData.putString(META_DATA_KEY_PROFILE, PROFILE_PRIMARY);
749         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
750         final ArrayList<UserHandle> handles = new ArrayList<>();
751         handles.add(new UserHandle(0));
752         handles.add(new UserHandle(10));
753         tile.userHandle = handles;
754         mImpl.openTileIntent(mActivity, tile);
755 
756         verify(mActivity)
757                 .startActivity(any(Intent.class));
758         verify(mActivity, never()).getSupportFragmentManager();
759     }
760 
761     @Test
openTileIntent_profileSelectionDialog_validUserHandleShouldNotShow()762     public void openTileIntent_profileSelectionDialog_validUserHandleShouldNotShow() {
763         final int userId = 10;
764         ShadowUserManager.getShadow().addUser(userId, "Someone", 0);
765 
766         final UserHandle userHandle = new UserHandle(userId);
767         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
768         tile.getIntent().putExtra(EXTRA_USER, userHandle);
769         final ArrayList<UserHandle> handles = new ArrayList<>();
770         handles.add(new UserHandle(0));
771         handles.add(userHandle);
772         tile.userHandle = handles;
773 
774         mImpl.openTileIntent(mActivity, tile);
775 
776         final ArgumentCaptor<UserHandle> argument = ArgumentCaptor.forClass(UserHandle.class);
777         verify(mActivity)
778                 .startActivityAsUser(any(Intent.class), argument.capture());
779         assertThat(argument.getValue().getIdentifier()).isEqualTo(userId);
780         verify(mActivity, never()).getSupportFragmentManager();
781     }
782 
783     @Test
openTileIntent_profileSelectionDialog_invalidUserHandleShouldShow()784     public void openTileIntent_profileSelectionDialog_invalidUserHandleShouldShow() {
785         ShadowUserManager.getShadow().addUser(10, "Someone", 0);
786 
787         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
788         tile.getIntent().putExtra(EXTRA_USER, new UserHandle(30));
789         final ArrayList<UserHandle> handles = new ArrayList<>();
790         handles.add(new UserHandle(0));
791         handles.add(new UserHandle(10));
792         tile.userHandle = handles;
793 
794         mImpl.openTileIntent(mActivity, tile);
795 
796         verify(mActivity, never())
797                 .startActivityAsUser(any(Intent.class), any(UserHandle.class));
798         verify(mActivity).getSupportFragmentManager();
799     }
800 
801     @Test
openTileIntent_profileSelectionDialog_unresolvableWorkProfileIntentShouldNotShow()802     public void openTileIntent_profileSelectionDialog_unresolvableWorkProfileIntentShouldNotShow() {
803         final int userId = 10;
804         ShadowUserManager.getShadow().addUser(userId, "Someone", 0);
805         final UserHandle userHandle = new UserHandle(userId);
806         final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
807         final ArrayList<UserHandle> handles = new ArrayList<>();
808         handles.add(new UserHandle(0));
809         handles.add(userHandle);
810         tile.userHandle = handles;
811         when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), eq(0)))
812                 .thenReturn(new ResolveInfo());
813         when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), eq(userId)))
814                 .thenReturn(null);
815 
816         mImpl.openTileIntent(mActivity, tile);
817 
818         final ArgumentCaptor<UserHandle> argument = ArgumentCaptor.forClass(UserHandle.class);
819         verify(mActivity)
820                 .startActivityAsUser(any(Intent.class), argument.capture());
821         assertThat(argument.getValue().getIdentifier()).isEqualTo(0);
822         verify(mActivity, never()).getSupportFragmentManager();
823     }
824 
825     private static class TestFragment extends DashboardFragment {
826 
827         @Override
getMetricsCategory()828         public int getMetricsCategory() {
829             return MetricsEvent.SETTINGS_GESTURES;
830         }
831 
832         @Override
getPreferenceScreenResId()833         protected int getPreferenceScreenResId() {
834             return R.xml.gestures;
835         }
836 
837         @Override
getLogTag()838         protected String getLogTag() {
839             return "TestFragment";
840         }
841     }
842 
843     private static class TestTopLevelHighlightMixin extends TopLevelHighlightMixin {
844         private final String mHighlightPreferenceKey;
845 
TestTopLevelHighlightMixin(String highlightPreferenceKey, boolean activityEmbedded)846         TestTopLevelHighlightMixin(String highlightPreferenceKey,
847                 boolean activityEmbedded) {
848             super(activityEmbedded);
849             mHighlightPreferenceKey = highlightPreferenceKey;
850         }
851 
852         @Override
getHighlightPreferenceKey()853         public String getHighlightPreferenceKey() {
854             return mHighlightPreferenceKey;
855         }
856     }
857 }
858