126515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen/*
226515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * Copyright (C) 2016 The Android Open Source Project
326515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen *
426515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * Licensed under the Apache License, Version 2.0 (the "License");
526515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * you may not use this file except in compliance with the License.
626515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * You may obtain a copy of the License at
726515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen *
826515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen *      http://www.apache.org/licenses/LICENSE-2.0
926515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen *
1026515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * Unless required by applicable law or agreed to in writing, software
1126515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * distributed under the License is distributed on an "AS IS" BASIS,
1226515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1326515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * See the License for the specific language governing permissions and
1426515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen * limitations under the License
1526515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen */
1626515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen
1726515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenpackage com.android.settings.search;
1826515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen
19cbcc76e8d32f95690d08258a91d58136cd9fb056Martijn Coenenimport static com.google.common.truth.Truth.assertThat;
2026515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen
2139b467482d1bf256a111c757e9b7621c6f523271Jason Monkimport static org.mockito.Matchers.any;
2239b467482d1bf256a111c757e9b7621c6f523271Jason Monkimport static org.mockito.Matchers.anyBoolean;
230e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport static org.mockito.Matchers.anyInt;
240e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport static org.mockito.Matchers.anyString;
250e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport static org.mockito.Matchers.eq;
2626515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport static org.mockito.Mockito.mock;
27da6c0ba382c1c2c9b98f11a1ef9e572472654e27Martijn Coenenimport static org.mockito.Mockito.never;
2839b467482d1bf256a111c757e9b7621c6f523271Jason Monkimport static org.mockito.Mockito.only;
298a963babe2e36b7a41f77b8d2598c97658196e58Chris Wrenimport static org.mockito.Mockito.reset;
3026515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport static org.mockito.Mockito.times;
3126515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport static org.mockito.Mockito.verify;
3226515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen
3326515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.accessibilityservice.AccessibilityServiceInfo;
3426515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.app.Activity;
3526515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.app.Application;
36fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport android.app.LoaderManager;
3726515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.content.BroadcastReceiver;
3826515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.content.Context;
39da6c0ba382c1c2c9b98f11a1ef9e572472654e27Martijn Coenenimport android.content.Intent;
4026515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.content.Loader;
418a963babe2e36b7a41f77b8d2598c97658196e58Chris Wrenimport android.content.pm.ActivityInfo;
428a963babe2e36b7a41f77b8d2598c97658196e58Chris Wrenimport android.content.pm.PackageManager;
438a963babe2e36b7a41f77b8d2598c97658196e58Chris Wrenimport android.content.pm.ResolveInfo;
448a963babe2e36b7a41f77b8d2598c97658196e58Chris Wrenimport android.content.pm.ServiceInfo;
458a963babe2e36b7a41f77b8d2598c97658196e58Chris Wrenimport android.database.ContentObserver;
4626515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.hardware.input.InputManager;
4726515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.net.Uri;
4826515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.os.Bundle;
4926515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.print.PrintManager;
500e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport android.print.PrintServicesLoader;
5126515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.printservice.PrintServiceInfo;
5226515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport android.provider.Settings;
53fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport android.provider.UserDictionary;
54fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport android.view.inputmethod.InputMethodInfo;
55fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenen
56fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport com.android.internal.content.PackageMonitor;
57fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport com.android.settings.SettingsRobolectricTestRunner;
58fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport com.android.settings.TestConfig;
59fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport com.android.settings.accessibility.AccessibilitySettings;
6039b467482d1bf256a111c757e9b7621c6f523271Jason Monkimport com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
61fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport com.android.settings.inputmethod.PhysicalKeyboardFragment;
6226515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport com.android.settings.inputmethod.VirtualKeyboardFragment;
6326515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport com.android.settings.language.LanguageAndInputSettings;
6400dbb74b8d699510ece683fc7ab12fc2343fa5d5Martijn Coenenimport com.android.settings.print.PrintSettingsFragment;
6526515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport com.android.settings.search2.DatabaseIndexingManager;
6626515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport com.android.settings.testutils.DatabaseTestUtils;
67fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport com.android.settings.testutils.shadow.ShadowActivityWithLoadManager;
6839b467482d1bf256a111c757e9b7621c6f523271Jason Monkimport com.android.settings.testutils.shadow.ShadowContextImplWithRegisterReceiver;
6939b467482d1bf256a111c757e9b7621c6f523271Jason Monkimport com.android.settings.testutils.shadow.ShadowInputManager;
70fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport com.android.settings.testutils.shadow.ShadowInputMethodManagerWithMethodList;
7139b467482d1bf256a111c757e9b7621c6f523271Jason Monkimport com.android.settings.testutils.shadow.ShadowPackageMonitor;
72fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenen
7300dbb74b8d699510ece683fc7ab12fc2343fa5d5Martijn Coenenimport org.junit.After;
74da6c0ba382c1c2c9b98f11a1ef9e572472654e27Martijn Coenenimport org.junit.Before;
750e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport org.junit.Test;
76da6c0ba382c1c2c9b98f11a1ef9e572472654e27Martijn Coenenimport org.junit.runner.RunWith;
77da6c0ba382c1c2c9b98f11a1ef9e572472654e27Martijn Coenenimport org.mockito.Mock;
78da6c0ba382c1c2c9b98f11a1ef9e572472654e27Martijn Coenenimport org.mockito.MockitoAnnotations;
7926515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport org.robolectric.Robolectric;
8026515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport org.robolectric.RuntimeEnvironment;
81fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport org.robolectric.annotation.Config;
8226515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport org.robolectric.internal.ShadowExtractor;
8326515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenenimport org.robolectric.res.builder.RobolectricPackageManager;
840e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport org.robolectric.shadows.ShadowAccessibilityManager;
850e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport org.robolectric.shadows.ShadowApplication;
860e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport org.robolectric.shadows.ShadowContentResolver;
87fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport org.xmlpull.v1.XmlPullParserException;
880e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenen
890e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport java.io.IOException;
900e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport java.util.ArrayList;
910e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport java.util.Collection;
920e940d6c00d06578c4c0f89a95e5d31c8073fbc1Martijn Coenenimport java.util.Collections;
93fe58b534f6a1bf63dadb18dae13c59ed8a014eecMartijn Coenenimport java.util.List;
94cbcc76e8d32f95690d08258a91d58136cd9fb056Martijn Coenen
95cbcc76e8d32f95690d08258a91d58136cd9fb056Martijn Coenen@RunWith(SettingsRobolectricTestRunner.class)
96cbcc76e8d32f95690d08258a91d58136cd9fb056Martijn Coenen@Config(
9726515da08707b6f8182cc8b0ed5e01e97aa92a96Martijn Coenen        manifest = TestConfig.MANIFEST_PATH,
98b412e18296cf3958585efdaec15c8b9d28bd2c50Martijn Coenen        sdk = TestConfig.SDK_VERSION,
99        shadows = {
100                ShadowActivityWithLoadManager.class,
101                ShadowContextImplWithRegisterReceiver.class,
102                ShadowInputManager.class,
103                ShadowInputMethodManagerWithMethodList.class,
104                ShadowPackageMonitor.class,
105        }
106)
107public class DynamicIndexableContentMonitorTest {
108
109    private static final int LOADER_ID = 1234;
110    private static final String A11Y_PACKAGE_1 = "a11y-1";
111    private static final String A11Y_PACKAGE_2 = "a11y-2";
112    private static final String IME_PACKAGE_1 = "ime-1";
113    private static final String IME_PACKAGE_2 = "ime-2";
114
115    @Mock
116    private LoaderManager mLoaderManager;
117    @Mock
118    private DatabaseIndexingManager mIndexManager;
119
120    private Activity mActivity;
121    private InputManager mInputManager;
122
123    private ShadowContextImplWithRegisterReceiver mShadowContextImpl;
124    private ShadowActivityWithLoadManager mShadowActivity;
125    private ShadowAccessibilityManager mShadowAccessibilityManager;
126    private ShadowInputMethodManagerWithMethodList mShadowInputMethodManager;
127    private RobolectricPackageManager mRobolectricPackageManager;
128
129    private final DynamicIndexableContentMonitor mMonitor = new DynamicIndexableContentMonitor();
130
131    @Before
132    public void setUp() {
133        MockitoAnnotations.initMocks(this);
134        mActivity = Robolectric.buildActivity(Activity.class).get();
135        mInputManager = InputManager.getInstance();
136
137        // Robolectric shadows.
138        mShadowContextImpl = (ShadowContextImplWithRegisterReceiver) ShadowExtractor.extract(
139                ((Application) ShadowApplication.getInstance().getApplicationContext())
140                .getBaseContext());
141        mShadowActivity = (ShadowActivityWithLoadManager) ShadowExtractor.extract(mActivity);
142        mShadowAccessibilityManager = (ShadowAccessibilityManager) ShadowExtractor.extract(
143                mActivity.getSystemService(Context.ACCESSIBILITY_SERVICE));
144        mShadowInputMethodManager = (ShadowInputMethodManagerWithMethodList) ShadowExtractor
145                .extract(mActivity.getSystemService(Context.INPUT_METHOD_SERVICE));
146        mRobolectricPackageManager = RuntimeEnvironment.getRobolectricPackageManager();
147
148        // Setup shadows.
149        mShadowContextImpl.setSystemService(Context.PRINT_SERVICE, mock(PrintManager.class));
150        mShadowContextImpl.setSystemService(Context.INPUT_SERVICE, mInputManager);
151        mShadowActivity.setLoaderManager(mLoaderManager);
152        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(Collections.emptyList());
153        mShadowInputMethodManager.setInputMethodList(Collections.emptyList());
154        mRobolectricPackageManager.setSystemFeature(PackageManager.FEATURE_PRINTING, true);
155        mRobolectricPackageManager.setSystemFeature(PackageManager.FEATURE_INPUT_METHODS, true);
156    }
157
158    @After
159    public void shutDown() {
160        mMonitor.unregister(mActivity, LOADER_ID);
161        // BroadcastReceiver must be unregistered.
162        assertThat(extractPackageMonitor()).isNull();
163
164        DynamicIndexableContentMonitor.resetForTesting();
165        mRobolectricPackageManager.reset();
166
167        DatabaseTestUtils.clearDb();
168    }
169
170    @Test
171    public void testLockedUser() {
172        mMonitor.register(mActivity, LOADER_ID, mIndexManager, false /* isUserUnlocked */);
173
174        // No loader procedure happens.
175        verify(mLoaderManager, never()).initLoader(
176                anyInt(), any(Bundle.class), any(LoaderManager.LoaderCallbacks.class));
177        // No indexing happens.
178        verify(mIndexManager, never()).updateFromClassNameResource(
179                anyString(), anyBoolean());
180
181        mMonitor.unregister(mActivity, LOADER_ID);
182
183        // No destroy loader should happen.
184        verify(mLoaderManager, never()).destroyLoader(anyInt());
185    }
186
187    @Test
188    public void testWithNoPrintingFeature() {
189        mRobolectricPackageManager.setSystemFeature(PackageManager.FEATURE_PRINTING, false);
190
191        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
192
193        // No loader procedure happens.
194        verify(mLoaderManager, never()).initLoader(
195                anyInt(), any(Bundle.class), any(LoaderManager.LoaderCallbacks.class));
196        verifyNoIndexing(PrintSettingsFragment.class);
197
198        mMonitor.unregister(mActivity, LOADER_ID);
199
200        // No destroy loader should happen.
201        verify(mLoaderManager, never()).destroyLoader(anyInt());
202        // BroadcastReceiver must be unregistered.
203        assertThat(extractPackageMonitor()).isNull();
204
205        // To suppress spurious test fail in {@link #shutDown()}.
206        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
207    }
208
209    @Test
210    public void testPrinterServiceIndex() {
211        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
212
213        // Loader procedure happens.
214        verify(mLoaderManager, only()).initLoader(LOADER_ID, null, mMonitor);
215
216        // Loading print services happens.
217        final Loader<List<PrintServiceInfo>> loader =
218                mMonitor.onCreateLoader(LOADER_ID, null /* args */);
219        assertThat(loader).isInstanceOf(PrintServicesLoader.class);
220        verifyNoIndexing(PrintSettingsFragment.class);
221
222        mMonitor.onLoadFinished(loader, Collections.emptyList());
223
224        verifyIncrementalIndexing(PrintSettingsFragment.class);
225    }
226
227    @Test
228    public void testInputDevicesMonitor() {
229        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
230
231        // Rebuild indexing should happen.
232        verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
233        // Input monitor should be registered to InputManager.
234        final InputManager.InputDeviceListener listener = extactInputDeviceListener();
235        assertThat(listener).isNotNull();
236
237        /*
238         * Nothing happens on successive register calls.
239         */
240        mMonitor.unregister(mActivity, LOADER_ID);
241        reset(mIndexManager);
242
243        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
244
245        verifyNoIndexing(PhysicalKeyboardFragment.class);
246        assertThat(extactInputDeviceListener()).isEqualTo(listener);
247
248        /*
249         * A device is added.
250         */
251        reset(mIndexManager);
252
253        listener.onInputDeviceAdded(1 /* deviceId */);
254
255        verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
256
257        /*
258         * A device is removed.
259         */
260        reset(mIndexManager);
261
262        listener.onInputDeviceRemoved(2 /* deviceId */);
263
264        verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
265
266        /*
267         * A device is changed.
268         */
269        reset(mIndexManager);
270
271        listener.onInputDeviceChanged(3 /* deviceId */);
272
273        verifyIncrementalIndexing(PhysicalKeyboardFragment.class);
274    }
275
276    @Test
277    public void testAccessibilityServicesMonitor() throws Exception {
278        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
279
280        verifyIncrementalIndexing(AccessibilitySettings.class);
281
282        /*
283         * When an accessibility service package is installed, incremental indexing happen.
284         */
285        reset(mIndexManager);
286
287        installAccessibilityService(A11Y_PACKAGE_1);
288
289        verifyIncrementalIndexing(AccessibilitySettings.class);
290
291        /*
292         * When another accessibility service package is installed, incremental indexing happens.
293         */
294        reset(mIndexManager);
295
296        installAccessibilityService(A11Y_PACKAGE_2);
297
298        verifyIncrementalIndexing(AccessibilitySettings.class);
299
300        /*
301         * When an accessibility service is disabled, rebuild indexing happens.
302         */
303        reset(mIndexManager);
304
305        disableInstalledPackage(A11Y_PACKAGE_1);
306
307        verifyIncrementalIndexing(AccessibilitySettings.class);
308
309        /*
310         * When an accessibility service is enabled, incremental indexing happens.
311         */
312        reset(mIndexManager);
313
314        enableInstalledPackage(A11Y_PACKAGE_1);
315
316        verifyIncrementalIndexing(AccessibilitySettings.class);
317
318        /*
319         * When an accessibility service package is uninstalled, rebuild indexing happens.
320         */
321        reset(mIndexManager);
322
323        uninstallAccessibilityService(A11Y_PACKAGE_1);
324
325        verifyIncrementalIndexing(AccessibilitySettings.class);
326
327        /*
328         * When an input method service package is installed, nothing happens.
329         */
330        reset(mIndexManager);
331
332        installInputMethodService(IME_PACKAGE_1);
333
334        verifyNoIndexing(AccessibilitySettings.class);
335    }
336
337    @Test
338    public void testInputMethodServicesMonitor() throws Exception {
339        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
340
341        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
342        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
343
344        final Uri enabledInputMethodsContentUri = Settings.Secure.getUriFor(
345                Settings.Secure.ENABLED_INPUT_METHODS);
346        // Content observer should be registered.
347        final ContentObserver observer = extractContentObserver(enabledInputMethodsContentUri);
348        assertThat(observer).isNotNull();
349
350        /*
351         * When an input method service package is installed, incremental indexing happen.
352         */
353        reset(mIndexManager);
354
355        installInputMethodService(IME_PACKAGE_1);
356
357        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
358        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
359
360        /*
361         * When another input method service package is installed, incremental indexing happens.
362         */
363        reset(mIndexManager);
364
365        installInputMethodService(IME_PACKAGE_2);
366
367        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
368        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
369
370        /*
371         * When an input method service is disabled, rebuild indexing happens.
372         */
373        reset(mIndexManager);
374
375        disableInstalledPackage(IME_PACKAGE_1);
376
377        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
378        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
379
380        /*
381         * When an input method service is enabled, incremental indexing happens.
382         */
383        reset(mIndexManager);
384
385        enableInstalledPackage(IME_PACKAGE_1);
386
387        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
388        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
389
390        /*
391         * When an input method service package is uninstalled, rebuild indexing happens.
392         */
393        reset(mIndexManager);
394
395        uninstallInputMethodService(IME_PACKAGE_1);
396
397        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
398        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
399
400        /*
401         * When an accessibility service package is installed, nothing happens.
402         */
403        reset(mIndexManager);
404
405        installAccessibilityService(A11Y_PACKAGE_1);
406
407        verifyNoIndexing(VirtualKeyboardFragment.class);
408        verifyNoIndexing(AvailableVirtualKeyboardFragment.class);
409
410        /*
411         * When enabled IMEs list is changed, rebuild indexing happens.
412         */
413        reset(mIndexManager);
414
415        observer.onChange(false /* selfChange */, enabledInputMethodsContentUri);
416
417        verifyIncrementalIndexing(VirtualKeyboardFragment.class);
418        verifyIncrementalIndexing(AvailableVirtualKeyboardFragment.class);
419    }
420
421    @Test
422    public void testUserDictionaryChangeMonitor() throws Exception {
423        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
424
425        // Content observer should be registered.
426        final ContentObserver observer = extractContentObserver(UserDictionary.Words.CONTENT_URI);
427        assertThat(observer).isNotNull();
428
429        verifyIncrementalIndexing(LanguageAndInputSettings.class);
430
431        /*
432         * When user dictionary content is changed, rebuild indexing happens.
433         */
434        reset(mIndexManager);
435
436        observer.onChange(false /* selfChange */, UserDictionary.Words.CONTENT_URI);
437
438        verifyIncrementalIndexing(LanguageAndInputSettings.class);
439    }
440
441    /*
442     * Verification helpers.
443     */
444
445    private void verifyNoIndexing(Class<?> indexingClass) {
446        verify(mIndexManager, never()).updateFromClassNameResource(eq(indexingClass.getName()),
447                anyBoolean());
448    }
449
450    private void verifyIncrementalIndexing(Class<?> indexingClass) {
451        verify(mIndexManager, times(1)).updateFromClassNameResource(indexingClass.getName(),
452                true /* includeInSearchResults */);
453        verify(mIndexManager, never()).updateFromClassNameResource(indexingClass.getName(),
454                false /* includeInSearchResults */);
455    }
456
457    /*
458     * Testing helper methods.
459     */
460
461    private InputManager.InputDeviceListener extactInputDeviceListener() {
462        List<InputManager.InputDeviceListener> listeners = ((ShadowInputManager) ShadowExtractor
463                .extract(mInputManager))
464                .getRegisteredInputDeviceListeners();
465        InputManager.InputDeviceListener inputDeviceListener = null;
466        for (InputManager.InputDeviceListener listener : listeners) {
467            if (isUnderTest(listener)) {
468                if (inputDeviceListener != null) {
469                    assertThat(listener).isEqualTo(inputDeviceListener);
470                } else {
471                    inputDeviceListener = listener;
472                }
473            }
474        }
475        return inputDeviceListener;
476    }
477
478    private PackageMonitor extractPackageMonitor() {
479        List<ShadowApplication.Wrapper> receivers = ShadowApplication.getInstance()
480                .getRegisteredReceivers();
481        PackageMonitor packageMonitor = null;
482        for (ShadowApplication.Wrapper wrapper : receivers) {
483            BroadcastReceiver receiver = wrapper.getBroadcastReceiver();
484            if (isUnderTest(receiver) && receiver instanceof PackageMonitor) {
485                if (packageMonitor != null) {
486                    assertThat(receiver).isEqualTo(packageMonitor);
487                } else {
488                    packageMonitor = (PackageMonitor) receiver;
489                }
490            }
491        }
492        return packageMonitor;
493    }
494
495    private ContentObserver extractContentObserver(Uri uri) {
496        ShadowContentResolver contentResolver = (ShadowContentResolver) ShadowExtractor
497                .extract(mActivity.getContentResolver());
498        Collection<ContentObserver> observers = contentResolver.getContentObservers(uri);
499        ContentObserver contentObserver = null;
500        for (ContentObserver observer : observers) {
501            if (isUnderTest(observer)) {
502                if (contentObserver != null) {
503                    assertThat(observer).isEqualTo(contentObserver);
504                } else {
505                    contentObserver = observer;
506                }
507            }
508        }
509        return contentObserver;
510    }
511
512    private void enableInstalledPackage(String packageName) {
513        ((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
514                packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0 /* flags */);
515        extractPackageMonitor().onPackageModified(packageName);
516        Robolectric.flushBackgroundThreadScheduler();
517    }
518
519    private void disableInstalledPackage(String packageName) {
520        ((PackageManager) mRobolectricPackageManager).setApplicationEnabledSetting(
521                packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
522        extractPackageMonitor().onPackageModified(packageName);
523        Robolectric.flushBackgroundThreadScheduler();
524    }
525
526    private void installAccessibilityService(String packageName) throws Exception {
527        final AccessibilityServiceInfo serviceToAdd = buildAccessibilityServiceInfo(packageName);
528
529        final List<AccessibilityServiceInfo> services = new ArrayList<>();
530        services.addAll(mShadowAccessibilityManager.getInstalledAccessibilityServiceList());
531        services.add(serviceToAdd);
532        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(services);
533
534        final Intent intent = DynamicIndexableContentMonitor
535                .getAccessibilityServiceIntent(packageName);
536        mRobolectricPackageManager.addResolveInfoForIntent(intent, serviceToAdd.getResolveInfo());
537        mRobolectricPackageManager.addPackage(packageName);
538
539        extractPackageMonitor()
540                .onPackageAppeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
541        Robolectric.flushBackgroundThreadScheduler();
542    }
543
544    private void uninstallAccessibilityService(String packageName) throws Exception {
545        final AccessibilityServiceInfo serviceToRemove = buildAccessibilityServiceInfo(packageName);
546
547        final List<AccessibilityServiceInfo> services = new ArrayList<>();
548        services.addAll(mShadowAccessibilityManager.getInstalledAccessibilityServiceList());
549        services.remove(serviceToRemove);
550        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(services);
551
552        final Intent intent = DynamicIndexableContentMonitor
553                .getAccessibilityServiceIntent(packageName);
554        mRobolectricPackageManager.removeResolveInfosForIntent(intent, packageName);
555        mRobolectricPackageManager.removePackage(packageName);
556
557        extractPackageMonitor()
558                .onPackageDisappeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
559        Robolectric.flushBackgroundThreadScheduler();
560    }
561
562    private void installInputMethodService(String packageName) throws Exception {
563        final ResolveInfo resolveInfoToAdd = buildResolveInfo(packageName, "imeService");
564        final InputMethodInfo serviceToAdd = buildInputMethodInfo(resolveInfoToAdd);
565
566        final List<InputMethodInfo> services = new ArrayList<>();
567        services.addAll(mShadowInputMethodManager.getInputMethodList());
568        services.add(serviceToAdd);
569        mShadowInputMethodManager.setInputMethodList(services);
570
571        final Intent intent = DynamicIndexableContentMonitor.getIMEServiceIntent(packageName);
572        mRobolectricPackageManager.addResolveInfoForIntent(intent, resolveInfoToAdd);
573        mRobolectricPackageManager.addPackage(packageName);
574
575        extractPackageMonitor()
576                .onPackageAppeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
577        Robolectric.flushBackgroundThreadScheduler();
578    }
579
580    private void uninstallInputMethodService(String packageName) throws Exception {
581        final ResolveInfo resolveInfoToRemove = buildResolveInfo(packageName, "imeService");
582        final InputMethodInfo serviceToRemove = buildInputMethodInfo(resolveInfoToRemove);
583
584        final List<InputMethodInfo> services = new ArrayList<>();
585        services.addAll(mShadowInputMethodManager.getInputMethodList());
586        services.remove(serviceToRemove);
587        mShadowInputMethodManager.setInputMethodList(services);
588
589        final Intent intent = DynamicIndexableContentMonitor.getIMEServiceIntent(packageName);
590        mRobolectricPackageManager.removeResolveInfosForIntent(intent, packageName);
591        mRobolectricPackageManager.removePackage(packageName);
592
593        extractPackageMonitor()
594                .onPackageDisappeared(packageName, PackageMonitor.PACKAGE_PERMANENT_CHANGE);
595        Robolectric.flushBackgroundThreadScheduler();
596    }
597
598    private AccessibilityServiceInfo buildAccessibilityServiceInfo(String packageName)
599            throws IOException, XmlPullParserException {
600        return new AccessibilityServiceInfo(
601                buildResolveInfo(packageName, "A11yService"), mActivity);
602    }
603
604    private static InputMethodInfo buildInputMethodInfo(ResolveInfo resolveInfo) {
605        return new InputMethodInfo(resolveInfo, false /* isAuxIme */, "SettingsActivity",
606                null /* subtypes */,  0 /* defaultResId */, false /* forceDefault */);
607    }
608
609    private static ResolveInfo buildResolveInfo(String packageName, String className) {
610        final ResolveInfo resolveInfo = new ResolveInfo();
611        resolveInfo.serviceInfo = new ServiceInfo();
612        resolveInfo.serviceInfo.packageName = packageName;
613        resolveInfo.serviceInfo.name = className;
614        // To workaround that RobolectricPackageManager.removeResolveInfosForIntent() only works
615        // for activity/broadcast resolver.
616        resolveInfo.activityInfo = new ActivityInfo();
617        resolveInfo.activityInfo.packageName = packageName;
618        resolveInfo.activityInfo.name = className;
619
620        return resolveInfo;
621    }
622
623    private static boolean isUnderTest(Object object) {
624        return object.getClass().getName().startsWith(
625                DynamicIndexableContentMonitor.class.getName());
626    }
627}
628