1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package android.testing; 16 17 import android.content.BroadcastReceiver; 18 import android.content.ComponentCallbacks; 19 import android.content.ComponentName; 20 import android.content.ContentProviderClient; 21 import android.content.Context; 22 import android.content.ContextWrapper; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.ServiceConnection; 26 import android.content.pm.PackageManager; 27 import android.content.res.Resources; 28 import android.net.Uri; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.UserHandle; 32 import android.provider.Settings; 33 import android.util.ArrayMap; 34 import android.view.LayoutInflater; 35 36 import androidx.annotation.Nullable; 37 38 import org.junit.rules.TestRule; 39 import org.junit.rules.TestWatcher; 40 import org.junit.runner.Description; 41 import org.junit.runners.model.Statement; 42 43 import java.util.ArrayList; 44 45 /** 46 * A ContextWrapper with utilities specifically designed to make Testing easier. 47 * 48 * <ul> 49 * <li>System services can be mocked out with {@link #addMockSystemService}</li> 50 * <li>Service binding can be mocked out with {@link #addMockService}</li> 51 * <li>Resources can be mocked out using {@link #getOrCreateTestableResources()}</li> 52 * <li>Settings support {@link TestableSettingsProvider}</li> 53 * <li>Has support for {@link LeakCheck} for services and receivers</li> 54 * </ul> 55 * 56 * <p>TestableContext should be defined as a rule on your test so it can clean up after itself. 57 * Like the following:</p> 58 * <pre class="prettyprint"> 59 * @Rule 60 * public final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext()); 61 * </pre> 62 */ 63 public class TestableContext extends ContextWrapper implements TestRule { 64 65 private TestableContentResolver mTestableContentResolver; 66 private TestableSettingsProvider mSettingsProvider; 67 private RuntimeException mSettingsProviderFailure; 68 69 private ArrayList<MockServiceResolver> mMockServiceResolvers; 70 private ArrayMap<String, Object> mMockSystemServices; 71 private ArrayMap<ComponentName, IBinder> mMockServices; 72 private ArrayMap<ServiceConnection, ComponentName> mActiveServices; 73 74 private PackageManager mMockPackageManager; 75 private LeakCheck.Tracker mReceiver; 76 private LeakCheck.Tracker mService; 77 private LeakCheck.Tracker mComponent; 78 private TestableResources mTestableResources; 79 private TestablePermissions mTestablePermissions; 80 TestableContext(Context base)81 public TestableContext(Context base) { 82 this(base, null); 83 } 84 TestableContext(Context base, LeakCheck check)85 public TestableContext(Context base, LeakCheck check) { 86 super(base); 87 88 // Configure TestableSettingsProvider when possible; if we fail to initialize some 89 // underlying infrastructure then remember the error and report it later when a test 90 // attempts to interact with it 91 try { 92 ContentProviderClient settings = base.getContentResolver() 93 .acquireContentProviderClient(Settings.AUTHORITY); 94 mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings); 95 mTestableContentResolver = new TestableContentResolver(base); 96 mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider); 97 mSettingsProvider.clearValuesAndCheck(TestableContext.this); 98 mSettingsProviderFailure = null; 99 } catch (Throwable t) { 100 mTestableContentResolver = null; 101 mSettingsProvider = null; 102 mSettingsProviderFailure = new RuntimeException( 103 "Failed to initialize TestableSettingsProvider", t); 104 } 105 mReceiver = check != null ? check.getTracker("receiver") : null; 106 mService = check != null ? check.getTracker("service") : null; 107 mComponent = check != null ? check.getTracker("component") : null; 108 } 109 setMockPackageManager(PackageManager mock)110 public void setMockPackageManager(PackageManager mock) { 111 mMockPackageManager = mock; 112 } 113 114 @Override getPackageManager()115 public PackageManager getPackageManager() { 116 if (mMockPackageManager != null) { 117 return mMockPackageManager; 118 } 119 return super.getPackageManager(); 120 } 121 122 /** 123 * Makes sure the resources being returned by this TestableContext are a version of 124 * TestableResources. 125 * @see #getResources() 126 */ ensureTestableResources()127 public void ensureTestableResources() { 128 if (mTestableResources == null) { 129 mTestableResources = new TestableResources(super.getResources()); 130 } 131 } 132 133 /** 134 * Get (and create if necessary) {@link TestableResources} for this TestableContext. 135 */ getOrCreateTestableResources()136 public TestableResources getOrCreateTestableResources() { 137 ensureTestableResources(); 138 return mTestableResources; 139 } 140 141 /** 142 * Returns a Resources instance for the test. 143 * 144 * By default this returns the same resources object that would come from the 145 * {@link ContextWrapper}, but if {@link #ensureTestableResources()} or 146 * {@link #getOrCreateTestableResources()} has been called, it will return resources gotten from 147 * {@link TestableResources}. 148 */ 149 @Override getResources()150 public Resources getResources() { 151 return mTestableResources != null ? mTestableResources.getResources() 152 : super.getResources(); 153 } 154 155 /** 156 * @see #getSystemService(String) 157 */ addMockSystemService(Class<T> service, T mock)158 public <T> void addMockSystemService(Class<T> service, T mock) { 159 addMockSystemService(getSystemServiceName(service), mock); 160 } 161 162 /** 163 * @see #getSystemService(String) 164 */ addMockSystemService(String name, Object service)165 public void addMockSystemService(String name, Object service) { 166 if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>(); 167 mMockSystemServices.put(name, service); 168 } 169 170 /** 171 * If a matching mock service has been added through {@link #addMockSystemService} then 172 * that will be returned, otherwise the real service will be acquired from the base 173 * context. 174 */ 175 @Override getSystemService(String name)176 public Object getSystemService(String name) { 177 if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) { 178 return mMockSystemServices.get(name); 179 } 180 if (name.equals(LAYOUT_INFLATER_SERVICE)) { 181 return getBaseContext().getSystemService(LayoutInflater.class).cloneInContext(this); 182 } 183 return super.getSystemService(name); 184 } 185 getSettingsProvider()186 TestableSettingsProvider getSettingsProvider() { 187 if (mSettingsProviderFailure != null) { 188 throw mSettingsProviderFailure; 189 } 190 return mSettingsProvider; 191 } 192 193 @Override getContentResolver()194 public TestableContentResolver getContentResolver() { 195 if (mSettingsProviderFailure != null) { 196 throw mSettingsProviderFailure; 197 } 198 return mTestableContentResolver; 199 } 200 201 /** 202 * Will always return itself for a TestableContext to ensure the testable effects extend 203 * to the application context. 204 */ 205 @Override getApplicationContext()206 public Context getApplicationContext() { 207 // Return this so its always a TestableContext. 208 return this; 209 } 210 211 @Override registerReceiver(BroadcastReceiver receiver, IntentFilter filter)212 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { 213 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable()); 214 return super.registerReceiver(receiver, filter); 215 } 216 217 @Override registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)218 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, 219 String broadcastPermission, Handler scheduler) { 220 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable()); 221 return super.registerReceiver(receiver, filter, broadcastPermission, scheduler); 222 } 223 224 @Override registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler)225 public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, 226 IntentFilter filter, String broadcastPermission, Handler scheduler) { 227 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable()); 228 return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission, 229 scheduler); 230 } 231 232 @Override unregisterReceiver(BroadcastReceiver receiver)233 public void unregisterReceiver(BroadcastReceiver receiver) { 234 if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations(); 235 super.unregisterReceiver(receiver); 236 } 237 238 /** 239 * Adds a mock service to be connected to by a bindService call. 240 * <p> 241 * Normally a TestableContext will pass through all bind requests to the base context 242 * but when addMockService has been called for a ComponentName being bound, then 243 * TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected} 244 * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected} 245 * when the service is unbound. 246 * </p> 247 * 248 * @see #addMockServiceResolver(MockServiceResolver) for custom resolution of service Intents to 249 * ComponentNames 250 */ addMockService(ComponentName component, IBinder service)251 public void addMockService(ComponentName component, IBinder service) { 252 if (mMockServices == null) mMockServices = new ArrayMap<>(); 253 mMockServices.put(component, service); 254 } 255 256 /** 257 * Strategy to resolve a service {@link Intent} to a mock service {@link ComponentName}. 258 */ 259 public interface MockServiceResolver { 260 @Nullable resolve(Intent service)261 ComponentName resolve(Intent service); 262 } 263 264 /** 265 * Registers a strategy to resolve service intents to registered mock services. 266 * <p> 267 * The result of the first {@link MockServiceResolver} to return a non-null 268 * {@link ComponentName} is used to look up a mock service. The mock service must be registered 269 * via {@link #addMockService(ComponentName, IBinder)} separately, using the same component 270 * name. 271 * 272 * If none of the resolvers return a non-null value, or the first returned component name 273 * does not link to a registered mock service, the bind requests are passed to the base context 274 * 275 * The resolvers are queried in order of registration. 276 */ addMockServiceResolver(MockServiceResolver resolver)277 public void addMockServiceResolver(MockServiceResolver resolver) { 278 if (mMockServiceResolvers == null) mMockServiceResolvers = new ArrayList<>(); 279 mMockServiceResolvers.add(resolver); 280 } 281 282 /** 283 * @see #addMockService(ComponentName, IBinder) 284 */ 285 @Override bindService(Intent service, ServiceConnection conn, int flags)286 public boolean bindService(Intent service, ServiceConnection conn, int flags) { 287 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); 288 if (checkMocks(service, conn)) return true; 289 return super.bindService(service, conn, flags); 290 } 291 292 /** 293 * @see #addMockService(ComponentName, IBinder) 294 */ 295 @Override bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user)296 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, 297 Handler handler, UserHandle user) { 298 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); 299 if (checkMocks(service, conn)) return true; 300 return super.bindServiceAsUser(service, conn, flags, handler, user); 301 } 302 303 /** 304 * @see #addMockService(ComponentName, IBinder) 305 */ 306 @Override bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user)307 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, 308 UserHandle user) { 309 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); 310 if (checkMocks(service, conn)) return true; 311 return super.bindServiceAsUser(service, conn, flags, user); 312 } 313 checkMocks(Intent service, ServiceConnection conn)314 private boolean checkMocks(Intent service, ServiceConnection conn) { 315 if (mMockServices == null) return false; 316 317 ComponentName serviceComponent = resolveMockServiceComponent(service); 318 if (serviceComponent == null) return false; 319 320 IBinder serviceImpl = mMockServices.get(serviceComponent); 321 if (serviceImpl == null) return false; 322 323 if (mActiveServices == null) mActiveServices = new ArrayMap<>(); 324 mActiveServices.put(conn, serviceComponent); 325 conn.onServiceConnected(serviceComponent, serviceImpl); 326 return true; 327 } 328 resolveMockServiceComponent(Intent service)329 private ComponentName resolveMockServiceComponent(Intent service) { 330 ComponentName specifiedComponentName = service.getComponent(); 331 if (specifiedComponentName != null) return specifiedComponentName; 332 333 if (mMockServiceResolvers == null) return null; 334 335 for (MockServiceResolver resolver : mMockServiceResolvers) { 336 ComponentName resolvedComponent = resolver.resolve(service); 337 if (resolvedComponent != null) return resolvedComponent; 338 } 339 return null; 340 } 341 342 /** 343 * @see #addMockService(ComponentName, IBinder) 344 */ 345 @Override unbindService(ServiceConnection conn)346 public void unbindService(ServiceConnection conn) { 347 if (mService != null) mService.getLeakInfo(conn).clearAllocations(); 348 if (mActiveServices != null && mActiveServices.containsKey(conn)) { 349 conn.onServiceDisconnected(mActiveServices.get(conn)); 350 mActiveServices.remove(conn); 351 return; 352 } 353 super.unbindService(conn); 354 } 355 356 /** 357 * Check if the TestableContext has a mock binding for a specified component. Will return 358 * true between {@link ServiceConnection#onServiceConnected} and 359 * {@link ServiceConnection#onServiceDisconnected} callbacks for a mock service. 360 * 361 * @see #addMockService(ComponentName, IBinder) 362 */ isBound(ComponentName component)363 public boolean isBound(ComponentName component) { 364 return mActiveServices != null && mActiveServices.containsValue(component); 365 } 366 367 @Override registerComponentCallbacks(ComponentCallbacks callback)368 public void registerComponentCallbacks(ComponentCallbacks callback) { 369 if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable()); 370 getBaseContext().registerComponentCallbacks(callback); 371 } 372 373 @Override unregisterComponentCallbacks(ComponentCallbacks callback)374 public void unregisterComponentCallbacks(ComponentCallbacks callback) { 375 if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations(); 376 getBaseContext().unregisterComponentCallbacks(callback); 377 } 378 getTestablePermissions()379 public TestablePermissions getTestablePermissions() { 380 if (mTestablePermissions == null) { 381 mTestablePermissions = new TestablePermissions(); 382 } 383 return mTestablePermissions; 384 } 385 386 @Override checkCallingOrSelfPermission(String permission)387 public int checkCallingOrSelfPermission(String permission) { 388 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 389 return mTestablePermissions.check(permission); 390 } 391 return super.checkCallingOrSelfPermission(permission); 392 } 393 394 @Override checkCallingPermission(String permission)395 public int checkCallingPermission(String permission) { 396 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 397 return mTestablePermissions.check(permission); 398 } 399 return super.checkCallingPermission(permission); 400 } 401 402 @Override checkPermission(String permission, int pid, int uid)403 public int checkPermission(String permission, int pid, int uid) { 404 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 405 return mTestablePermissions.check(permission); 406 } 407 return super.checkPermission(permission, pid, uid); 408 } 409 410 @Override checkPermission(String permission, int pid, int uid, IBinder callerToken)411 public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { 412 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 413 return mTestablePermissions.check(permission); 414 } 415 return super.checkPermission(permission, pid, uid, callerToken); 416 } 417 418 @Override checkSelfPermission(String permission)419 public int checkSelfPermission(String permission) { 420 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 421 return mTestablePermissions.check(permission); 422 } 423 return super.checkSelfPermission(permission); 424 } 425 426 @Override enforceCallingOrSelfPermission(String permission, String message)427 public void enforceCallingOrSelfPermission(String permission, String message) { 428 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 429 mTestablePermissions.enforce(permission); 430 } else { 431 super.enforceCallingOrSelfPermission(permission, message); 432 } 433 } 434 435 @Override enforceCallingPermission(String permission, String message)436 public void enforceCallingPermission(String permission, String message) { 437 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 438 mTestablePermissions.enforce(permission); 439 } else { 440 super.enforceCallingPermission(permission, message); 441 } 442 } 443 444 @Override enforcePermission(String permission, int pid, int uid, String message)445 public void enforcePermission(String permission, int pid, int uid, String message) { 446 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 447 mTestablePermissions.enforce(permission); 448 } else { 449 super.enforcePermission(permission, pid, uid, message); 450 } 451 } 452 453 @Override checkCallingOrSelfUriPermission(Uri uri, int modeFlags)454 public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { 455 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 456 return mTestablePermissions.check(uri, modeFlags); 457 } 458 return super.checkCallingOrSelfUriPermission(uri, modeFlags); 459 } 460 461 @Override checkCallingUriPermission(Uri uri, int modeFlags)462 public int checkCallingUriPermission(Uri uri, int modeFlags) { 463 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 464 return mTestablePermissions.check(uri, modeFlags); 465 } 466 return super.checkCallingUriPermission(uri, modeFlags); 467 } 468 469 @Override enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)470 public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) { 471 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 472 mTestablePermissions.enforce(uri, modeFlags); 473 } else { 474 super.enforceCallingOrSelfUriPermission(uri, modeFlags, message); 475 } 476 } 477 478 @Override checkUriPermission(Uri uri, int pid, int uid, int modeFlags)479 public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { 480 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 481 return mTestablePermissions.check(uri, modeFlags); 482 } 483 return super.checkUriPermission(uri, pid, uid, modeFlags); 484 } 485 486 @Override checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken)487 public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) { 488 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 489 return mTestablePermissions.check(uri, modeFlags); 490 } 491 return super.checkUriPermission(uri, pid, uid, modeFlags, callerToken); 492 } 493 494 @Override checkUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags)495 public int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid, 496 int uid, int modeFlags) { 497 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 498 return mTestablePermissions.check(uri, modeFlags); 499 } 500 return super.checkUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags); 501 } 502 503 @Override enforceCallingUriPermission(Uri uri, int modeFlags, String message)504 public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) { 505 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 506 mTestablePermissions.enforce(uri, modeFlags); 507 } else { 508 super.enforceCallingUriPermission(uri, modeFlags, message); 509 } 510 } 511 512 @Override enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message)513 public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) { 514 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 515 mTestablePermissions.enforce(uri, modeFlags); 516 } else { 517 super.enforceUriPermission(uri, pid, uid, modeFlags, message); 518 } 519 } 520 521 @Override enforceUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags, String message)522 public void enforceUriPermission(Uri uri, String readPermission, String writePermission, 523 int pid, int uid, int modeFlags, String message) { 524 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 525 mTestablePermissions.enforce(uri, modeFlags); 526 } else { 527 super.enforceUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags, 528 message); 529 } 530 } 531 532 @Override apply(Statement base, Description description)533 public Statement apply(Statement base, Description description) { 534 return new TestWatcher() { 535 @Override 536 protected void succeeded(Description description) { 537 if (mSettingsProvider != null) { 538 mSettingsProvider.clearValuesAndCheck(TestableContext.this); 539 } 540 } 541 542 @Override 543 protected void failed(Throwable e, Description description) { 544 if (mSettingsProvider != null) { 545 mSettingsProvider.clearValuesAndCheck(TestableContext.this); 546 } 547 } 548 }.apply(base, description); 549 } 550 } 551