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  */
17 
18 package com.android.settings.slices;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 
22 import static org.mockito.Mockito.spy;
23 
24 import android.app.ApplicationPackageManager;
25 import android.content.ComponentName;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.database.sqlite.SQLiteDatabase;
29 import android.net.Uri;
30 import android.view.accessibility.AccessibilityManager;
31 
32 import com.android.settings.search.SearchFeatureProvider;
33 import com.android.settings.search.SearchFeatureProviderImpl;
34 import com.android.settings.testutils.DatabaseTestUtils;
35 import com.android.settings.testutils.FakeFeatureFactory;
36 import com.android.settings.testutils.FakeIndexProvider;
37 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
38 import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
39 import com.android.settings.testutils.shadow.ShadowUserManager;
40 import com.android.settings.testutils.shadow.ShadowUtils;
41 
42 import org.junit.After;
43 import org.junit.Before;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 import org.robolectric.RobolectricTestRunner;
47 import org.robolectric.RuntimeEnvironment;
48 import org.robolectric.annotation.Config;
49 import org.robolectric.annotation.Implementation;
50 import org.robolectric.annotation.Implements;
51 import org.robolectric.shadow.api.Shadow;
52 import org.robolectric.shadows.ShadowAccessibilityManager;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.Locale;
57 
58 @RunWith(RobolectricTestRunner.class)
59 @Config(shadows = {ShadowUserManager.class, ShadowUtils.class,
60         SlicesDatabaseAccessorTest.ShadowApplicationPackageManager.class,
61         ShadowBluetoothAdapter.class, ShadowLockPatternUtils.class})
62 public class SlicesDatabaseAccessorTest {
63 
64     private final String FAKE_TITLE = "title";
65     private final String FAKE_SUMMARY = "summary";
66     private final String FAKE_SCREEN_TITLE = "screen_title";
67     private final String FAKE_KEYWORDS = "a, b, c";
68     private final int FAKE_ICON = 1234;
69     private final String FAKE_FRAGMENT_NAME = FakeIndexProvider.class.getName();
70     private final String FAKE_CONTROLLER_NAME = FakePreferenceController.class.getName();
71 
72     private Context mContext;
73     private SQLiteDatabase mDb;
74     private SlicesDatabaseAccessor mAccessor;
75 
76     @Before
setUp()77     public void setUp() {
78         mContext = RuntimeEnvironment.application;
79         ShadowUserManager.getShadow().setIsAdminUser(true);
80         mAccessor = spy(new SlicesDatabaseAccessor(mContext));
81         mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase();
82         SlicesDatabaseHelper.getInstance(mContext).setIndexedState();
83 
84         // Register the fake a11y Service
85         ShadowAccessibilityManager shadowAccessibilityManager = Shadow.extract(
86                 RuntimeEnvironment.application.getSystemService(AccessibilityManager.class));
87         shadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>());
88     }
89 
90     @After
cleanUp()91     public void cleanUp() {
92         DatabaseTestUtils.clearDb(mContext);
93     }
94 
95     @Test
testGetSliceDataFromKey_validKey_validSliceReturned()96     public void testGetSliceDataFromKey_validKey_validSliceReturned() {
97         String key = "key";
98         insertSpecialCase(key);
99 
100         SliceData data = mAccessor.getSliceDataFromKey(key);
101 
102         assertThat(data.getKey()).isEqualTo(key);
103         assertThat(data.getTitle()).isEqualTo(FAKE_TITLE);
104         assertThat(data.getSummary()).isEqualTo(FAKE_SUMMARY);
105         assertThat(data.getScreenTitle()).isEqualTo(FAKE_SCREEN_TITLE);
106         assertThat(data.getKeywords()).isEqualTo(FAKE_KEYWORDS);
107         assertThat(data.getIconResource()).isEqualTo(FAKE_ICON);
108         assertThat(data.getFragmentClassName()).isEqualTo(FAKE_FRAGMENT_NAME);
109         assertThat(data.getUri()).isNull();
110         assertThat(data.getPreferenceController()).isEqualTo(FAKE_CONTROLLER_NAME);
111         assertThat(data.getUnavailableSliceSubtitle()).isNull();
112     }
113 
114     @Test
testGetSliceDataFromKey_allowDynamicSummary_validSliceReturned()115     public void testGetSliceDataFromKey_allowDynamicSummary_validSliceReturned() {
116         String key = "key";
117         insertSpecialCase(key, true /* isPlatformSlice */,
118                 null /* customizedUnavailableSliceSubtitle */);
119 
120         SliceData data = mAccessor.getSliceDataFromKey(key);
121 
122         assertThat(data.getKey()).isEqualTo(key);
123         assertThat(data.getTitle()).isEqualTo(FAKE_TITLE);
124         assertThat(data.getSummary()).isEqualTo(FAKE_SUMMARY);
125         assertThat(data.getScreenTitle()).isEqualTo(FAKE_SCREEN_TITLE);
126         assertThat(data.getKeywords()).isEqualTo(FAKE_KEYWORDS);
127         assertThat(data.getIconResource()).isEqualTo(FAKE_ICON);
128         assertThat(data.getFragmentClassName()).isEqualTo(FAKE_FRAGMENT_NAME);
129         assertThat(data.getUri()).isNull();
130         assertThat(data.getPreferenceController()).isEqualTo(FAKE_CONTROLLER_NAME);
131     }
132 
133     @Test(expected = IllegalStateException.class)
testGetSliceDataFromKey_invalidKey_errorThrown()134     public void testGetSliceDataFromKey_invalidKey_errorThrown() {
135         String key = "key";
136 
137         mAccessor.getSliceDataFromKey(key);
138     }
139 
140     @Test
testGetSliceFromUri_validUri_validSliceReturned()141     public void testGetSliceFromUri_validUri_validSliceReturned() {
142         String key = "key";
143         String path = "intent/" + key;
144         insertSpecialCase(key);
145         Uri uri = SliceBuilderUtils.getUri(path, false);
146 
147         SliceData data = mAccessor.getSliceDataFromUri(uri);
148 
149         assertThat(data.getKey()).isEqualTo(key);
150         assertThat(data.getTitle()).isEqualTo(FAKE_TITLE);
151         assertThat(data.getSummary()).isEqualTo(FAKE_SUMMARY);
152         assertThat(data.getScreenTitle()).isEqualTo(FAKE_SCREEN_TITLE);
153         assertThat(data.getKeywords()).isEqualTo(FAKE_KEYWORDS);
154         assertThat(data.getIconResource()).isEqualTo(FAKE_ICON);
155         assertThat(data.getFragmentClassName()).isEqualTo(FAKE_FRAGMENT_NAME);
156         assertThat(data.getUri()).isEqualTo(uri);
157         assertThat(data.getPreferenceController()).isEqualTo(FAKE_CONTROLLER_NAME);
158     }
159 
160     @Test(expected = IllegalStateException.class)
testGetSliceFromUri_invalidUri_errorThrown()161     public void testGetSliceFromUri_invalidUri_errorThrown() {
162         Uri uri = SliceBuilderUtils.getUri("intent/durr", false);
163         mAccessor.getSliceDataFromUri(uri);
164     }
165 
166     @Test
getDescendantUris_platformSlice_doesNotReturnOEMSlice()167     public void getDescendantUris_platformSlice_doesNotReturnOEMSlice() {
168         final String key = "oem_key";
169         final boolean isPlatformSlice = false;
170         insertSpecialCase(key, isPlatformSlice);
171         final List<String> keys = mAccessor.getSliceKeys(!isPlatformSlice);
172 
173         assertThat(keys).isEmpty();
174     }
175 
176     @Test
getDescendantUris_oemSlice_doesNotReturnPlatformSlice()177     public void getDescendantUris_oemSlice_doesNotReturnPlatformSlice() {
178         final String key = "platform_key";
179         final boolean isPlatformSlice = true;
180         insertSpecialCase(key, isPlatformSlice);
181         final List<String> keys = mAccessor.getSliceKeys(!isPlatformSlice);
182 
183         assertThat(keys).isEmpty();
184     }
185 
186     @Test
getDescendantUris_oemSlice_returnsOEMUriDescendant()187     public void getDescendantUris_oemSlice_returnsOEMUriDescendant() {
188         final String key = "oem_key";
189         final boolean isPlatformSlice = false;
190         insertSpecialCase(key, isPlatformSlice);
191         final List<String> keys = mAccessor.getSliceKeys(isPlatformSlice);
192 
193         assertThat(keys).containsExactly(key);
194     }
195 
196     @Test
getDescendantUris_platformSlice_returnsPlatformUriDescendant()197     public void getDescendantUris_platformSlice_returnsPlatformUriDescendant() {
198         final String key = "platform_key";
199         final boolean isPlatformSlice = true;
200         insertSpecialCase(key, isPlatformSlice);
201         final List<String> keys = mAccessor.getSliceKeys(isPlatformSlice);
202 
203         assertThat(keys).containsExactly(key);
204     }
205 
206     @Test
207     @Config(qualifiers = "mcc999")
getSliceKeys_indexesDatabase()208     public void getSliceKeys_indexesDatabase() {
209         // Force new indexing
210         Locale.setDefault(new Locale("ca"));
211         final SearchFeatureProvider provider = new SearchFeatureProviderImpl();
212         final SlicesFeatureProvider sliceProvider = spy(new SlicesFeatureProviderImpl());
213         final FakeFeatureFactory factory = FakeFeatureFactory.setupForTest();
214         factory.searchFeatureProvider = provider;
215         factory.slicesFeatureProvider = sliceProvider;
216         // Fake the indexable list.
217         provider.getSearchIndexableResources().getProviderValues().clear();
218         provider.getSearchIndexableResources().getProviderValues().add(
219                 FakeIndexProvider.class);
220 
221         final SlicesDatabaseAccessor accessor = new SlicesDatabaseAccessor(mContext);
222         final List<String> keys = accessor.getSliceKeys(true);
223 
224         assertThat(keys).isNotEmpty();
225     }
226 
227     @Test
testGetSliceDataFromKey_defaultUnavailableSlice_validSliceReturned()228     public void testGetSliceDataFromKey_defaultUnavailableSlice_validSliceReturned() {
229         String key = "key";
230         insertSpecialCase(key, true /* isPlatformSlice */,
231                 null /* customizedUnavailableSliceSubtitle */);
232 
233         SliceData data = mAccessor.getSliceDataFromKey(key);
234 
235         assertThat(data.getKey()).isEqualTo(key);
236         assertThat(data.getTitle()).isEqualTo(FAKE_TITLE);
237         assertThat(data.getSummary()).isEqualTo(FAKE_SUMMARY);
238         assertThat(data.getScreenTitle()).isEqualTo(FAKE_SCREEN_TITLE);
239         assertThat(data.getKeywords()).isEqualTo(FAKE_KEYWORDS);
240         assertThat(data.getIconResource()).isEqualTo(FAKE_ICON);
241         assertThat(data.getFragmentClassName()).isEqualTo(FAKE_FRAGMENT_NAME);
242         assertThat(data.getUri()).isNull();
243         assertThat(data.getPreferenceController()).isEqualTo(FAKE_CONTROLLER_NAME);
244         assertThat(data.getUnavailableSliceSubtitle()).isNull();
245     }
246 
247     @Test
testGetSliceDataFromKey_customizeSubtitleOfUnavailableSlice_validSliceReturned()248     public void testGetSliceDataFromKey_customizeSubtitleOfUnavailableSlice_validSliceReturned() {
249         String key = "key";
250         String subtitle = "subtitle";
251         insertSpecialCase(key, true /* isPlatformSlice */, subtitle);
252 
253         SliceData data = mAccessor.getSliceDataFromKey(key);
254 
255         assertThat(data.getKey()).isEqualTo(key);
256         assertThat(data.getTitle()).isEqualTo(FAKE_TITLE);
257         assertThat(data.getSummary()).isEqualTo(FAKE_SUMMARY);
258         assertThat(data.getScreenTitle()).isEqualTo(FAKE_SCREEN_TITLE);
259         assertThat(data.getKeywords()).isEqualTo(FAKE_KEYWORDS);
260         assertThat(data.getIconResource()).isEqualTo(FAKE_ICON);
261         assertThat(data.getFragmentClassName()).isEqualTo(FAKE_FRAGMENT_NAME);
262         assertThat(data.getUri()).isNull();
263         assertThat(data.getPreferenceController()).isEqualTo(FAKE_CONTROLLER_NAME);
264         assertThat(data.getUnavailableSliceSubtitle()).isEqualTo(subtitle);
265     }
266 
insertSpecialCase(String key)267     private void insertSpecialCase(String key) {
268         insertSpecialCase(key, true);
269     }
270 
insertSpecialCase(String key, boolean isPlatformSlice)271     private void insertSpecialCase(String key, boolean isPlatformSlice) {
272         insertSpecialCase(key, isPlatformSlice, null /*customizedUnavailableSliceSubtitle*/);
273     }
274 
insertSpecialCase(String key, boolean isPlatformSlice, String customizedUnavailableSliceSubtitle)275     private void insertSpecialCase(String key, boolean isPlatformSlice,
276             String customizedUnavailableSliceSubtitle) {
277         ContentValues values = new ContentValues();
278         values.put(SlicesDatabaseHelper.IndexColumns.KEY, key);
279         values.put(SlicesDatabaseHelper.IndexColumns.TITLE, FAKE_TITLE);
280         values.put(SlicesDatabaseHelper.IndexColumns.SUMMARY, FAKE_SUMMARY);
281         values.put(SlicesDatabaseHelper.IndexColumns.SCREENTITLE, FAKE_SCREEN_TITLE);
282         values.put(SlicesDatabaseHelper.IndexColumns.KEYWORDS, FAKE_KEYWORDS);
283         values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, FAKE_ICON);
284         values.put(SlicesDatabaseHelper.IndexColumns.FRAGMENT, FAKE_FRAGMENT_NAME);
285         values.put(SlicesDatabaseHelper.IndexColumns.CONTROLLER, FAKE_CONTROLLER_NAME);
286         values.put(SlicesDatabaseHelper.IndexColumns.PLATFORM_SLICE, isPlatformSlice);
287         values.put(SlicesDatabaseHelper.IndexColumns.SLICE_TYPE, SliceData.SliceType.INTENT);
288         values.put(SlicesDatabaseHelper.IndexColumns.UNAVAILABLE_SLICE_SUBTITLE,
289                 customizedUnavailableSliceSubtitle);
290 
291         mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values);
292     }
293 
294     @Implements(ApplicationPackageManager.class)
295     public static class ShadowApplicationPackageManager extends
296             org.robolectric.shadows.ShadowApplicationPackageManager {
297 
298         @Implementation
getInstantAppResolverSettingsComponent()299         protected ComponentName getInstantAppResolverSettingsComponent() {
300             return null;
301         }
302     }
303 }
304