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