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 final TestableContentResolver mTestableContentResolver; 66 private final TestableSettingsProvider mSettingsProvider; 67 68 private ArrayList<MockServiceResolver> mMockServiceResolvers; 69 private ArrayMap<String, Object> mMockSystemServices; 70 private ArrayMap<ComponentName, IBinder> mMockServices; 71 private ArrayMap<ServiceConnection, ComponentName> mActiveServices; 72 73 private PackageManager mMockPackageManager; 74 private LeakCheck.Tracker mReceiver; 75 private LeakCheck.Tracker mService; 76 private LeakCheck.Tracker mComponent; 77 private TestableResources mTestableResources; 78 private TestablePermissions mTestablePermissions; 79 TestableContext(Context base)80 public TestableContext(Context base) { 81 this(base, null); 82 } 83 TestableContext(Context base, LeakCheck check)84 public TestableContext(Context base, LeakCheck check) { 85 super(base); 86 mTestableContentResolver = new TestableContentResolver(base); 87 ContentProviderClient settings = base.getContentResolver() 88 .acquireContentProviderClient(Settings.AUTHORITY); 89 mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings); 90 mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider); 91 mSettingsProvider.clearValuesAndCheck(TestableContext.this); 92 mReceiver = check != null ? check.getTracker("receiver") : null; 93 mService = check != null ? check.getTracker("service") : null; 94 mComponent = check != null ? check.getTracker("component") : null; 95 } 96 setMockPackageManager(PackageManager mock)97 public void setMockPackageManager(PackageManager mock) { 98 mMockPackageManager = mock; 99 } 100 101 @Override getPackageManager()102 public PackageManager getPackageManager() { 103 if (mMockPackageManager != null) { 104 return mMockPackageManager; 105 } 106 return super.getPackageManager(); 107 } 108 109 /** 110 * Makes sure the resources being returned by this TestableContext are a version of 111 * TestableResources. 112 * @see #getResources() 113 */ ensureTestableResources()114 public void ensureTestableResources() { 115 if (mTestableResources == null) { 116 mTestableResources = new TestableResources(super.getResources()); 117 } 118 } 119 120 /** 121 * Get (and create if necessary) {@link TestableResources} for this TestableContext. 122 */ getOrCreateTestableResources()123 public TestableResources getOrCreateTestableResources() { 124 ensureTestableResources(); 125 return mTestableResources; 126 } 127 128 /** 129 * Returns a Resources instance for the test. 130 * 131 * By default this returns the same resources object that would come from the 132 * {@link ContextWrapper}, but if {@link #ensureTestableResources()} or 133 * {@link #getOrCreateTestableResources()} has been called, it will return resources gotten from 134 * {@link TestableResources}. 135 */ 136 @Override getResources()137 public Resources getResources() { 138 return mTestableResources != null ? mTestableResources.getResources() 139 : super.getResources(); 140 } 141 142 /** 143 * @see #getSystemService(String) 144 */ addMockSystemService(Class<T> service, T mock)145 public <T> void addMockSystemService(Class<T> service, T mock) { 146 addMockSystemService(getSystemServiceName(service), mock); 147 } 148 149 /** 150 * @see #getSystemService(String) 151 */ addMockSystemService(String name, Object service)152 public void addMockSystemService(String name, Object service) { 153 if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>(); 154 mMockSystemServices.put(name, service); 155 } 156 157 /** 158 * If a matching mock service has been added through {@link #addMockSystemService} then 159 * that will be returned, otherwise the real service will be acquired from the base 160 * context. 161 */ 162 @Override getSystemService(String name)163 public Object getSystemService(String name) { 164 if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) { 165 return mMockSystemServices.get(name); 166 } 167 if (name.equals(LAYOUT_INFLATER_SERVICE)) { 168 return getBaseContext().getSystemService(LayoutInflater.class).cloneInContext(this); 169 } 170 return super.getSystemService(name); 171 } 172 getSettingsProvider()173 TestableSettingsProvider getSettingsProvider() { 174 return mSettingsProvider; 175 } 176 177 @Override getContentResolver()178 public TestableContentResolver getContentResolver() { 179 return mTestableContentResolver; 180 } 181 182 /** 183 * Will always return itself for a TestableContext to ensure the testable effects extend 184 * to the application context. 185 */ 186 @Override getApplicationContext()187 public Context getApplicationContext() { 188 // Return this so its always a TestableContext. 189 return this; 190 } 191 192 @Override registerReceiver(BroadcastReceiver receiver, IntentFilter filter)193 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { 194 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable()); 195 return super.registerReceiver(receiver, filter); 196 } 197 198 @Override registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)199 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, 200 String broadcastPermission, Handler scheduler) { 201 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable()); 202 return super.registerReceiver(receiver, filter, broadcastPermission, scheduler); 203 } 204 205 @Override registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler)206 public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, 207 IntentFilter filter, String broadcastPermission, Handler scheduler) { 208 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable()); 209 return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission, 210 scheduler); 211 } 212 213 @Override unregisterReceiver(BroadcastReceiver receiver)214 public void unregisterReceiver(BroadcastReceiver receiver) { 215 if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations(); 216 super.unregisterReceiver(receiver); 217 } 218 219 /** 220 * Adds a mock service to be connected to by a bindService call. 221 * <p> 222 * Normally a TestableContext will pass through all bind requests to the base context 223 * but when addMockService has been called for a ComponentName being bound, then 224 * TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected} 225 * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected} 226 * when the service is unbound. 227 * </p> 228 * 229 * @see #addMockServiceResolver(MockServiceResolver) for custom resolution of service Intents to 230 * ComponentNames 231 */ addMockService(ComponentName component, IBinder service)232 public void addMockService(ComponentName component, IBinder service) { 233 if (mMockServices == null) mMockServices = new ArrayMap<>(); 234 mMockServices.put(component, service); 235 } 236 237 /** 238 * Strategy to resolve a service {@link Intent} to a mock service {@link ComponentName}. 239 */ 240 public interface MockServiceResolver { 241 @Nullable resolve(Intent service)242 ComponentName resolve(Intent service); 243 } 244 245 /** 246 * Registers a strategy to resolve service intents to registered mock services. 247 * <p> 248 * The result of the first {@link MockServiceResolver} to return a non-null 249 * {@link ComponentName} is used to look up a mock service. The mock service must be registered 250 * via {@link #addMockService(ComponentName, IBinder)} separately, using the same component 251 * name. 252 * 253 * If none of the resolvers return a non-null value, or the first returned component name 254 * does not link to a registered mock service, the bind requests are passed to the base context 255 * 256 * The resolvers are queried in order of registration. 257 */ addMockServiceResolver(MockServiceResolver resolver)258 public void addMockServiceResolver(MockServiceResolver resolver) { 259 if (mMockServiceResolvers == null) mMockServiceResolvers = new ArrayList<>(); 260 mMockServiceResolvers.add(resolver); 261 } 262 263 /** 264 * @see #addMockService(ComponentName, IBinder) 265 */ 266 @Override bindService(Intent service, ServiceConnection conn, int flags)267 public boolean bindService(Intent service, ServiceConnection conn, int flags) { 268 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); 269 if (checkMocks(service, conn)) return true; 270 return super.bindService(service, conn, flags); 271 } 272 273 /** 274 * @see #addMockService(ComponentName, IBinder) 275 */ 276 @Override bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user)277 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, 278 Handler handler, UserHandle user) { 279 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); 280 if (checkMocks(service, conn)) return true; 281 return super.bindServiceAsUser(service, conn, flags, handler, user); 282 } 283 284 /** 285 * @see #addMockService(ComponentName, IBinder) 286 */ 287 @Override bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user)288 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, 289 UserHandle user) { 290 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); 291 if (checkMocks(service, conn)) return true; 292 return super.bindServiceAsUser(service, conn, flags, user); 293 } 294 checkMocks(Intent service, ServiceConnection conn)295 private boolean checkMocks(Intent service, ServiceConnection conn) { 296 if (mMockServices == null) return false; 297 298 ComponentName serviceComponent = resolveMockServiceComponent(service); 299 if (serviceComponent == null) return false; 300 301 IBinder serviceImpl = mMockServices.get(serviceComponent); 302 if (serviceImpl == null) return false; 303 304 if (mActiveServices == null) mActiveServices = new ArrayMap<>(); 305 mActiveServices.put(conn, serviceComponent); 306 conn.onServiceConnected(serviceComponent, serviceImpl); 307 return true; 308 } 309 resolveMockServiceComponent(Intent service)310 private ComponentName resolveMockServiceComponent(Intent service) { 311 ComponentName specifiedComponentName = service.getComponent(); 312 if (specifiedComponentName != null) return specifiedComponentName; 313 314 if (mMockServiceResolvers == null) return null; 315 316 for (MockServiceResolver resolver : mMockServiceResolvers) { 317 ComponentName resolvedComponent = resolver.resolve(service); 318 if (resolvedComponent != null) return resolvedComponent; 319 } 320 return null; 321 } 322 323 /** 324 * @see #addMockService(ComponentName, IBinder) 325 */ 326 @Override unbindService(ServiceConnection conn)327 public void unbindService(ServiceConnection conn) { 328 if (mService != null) mService.getLeakInfo(conn).clearAllocations(); 329 if (mActiveServices != null && mActiveServices.containsKey(conn)) { 330 conn.onServiceDisconnected(mActiveServices.get(conn)); 331 mActiveServices.remove(conn); 332 return; 333 } 334 super.unbindService(conn); 335 } 336 337 /** 338 * Check if the TestableContext has a mock binding for a specified component. Will return 339 * true between {@link ServiceConnection#onServiceConnected} and 340 * {@link ServiceConnection#onServiceDisconnected} callbacks for a mock service. 341 * 342 * @see #addMockService(ComponentName, IBinder) 343 */ isBound(ComponentName component)344 public boolean isBound(ComponentName component) { 345 return mActiveServices != null && mActiveServices.containsValue(component); 346 } 347 348 @Override registerComponentCallbacks(ComponentCallbacks callback)349 public void registerComponentCallbacks(ComponentCallbacks callback) { 350 if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable()); 351 getBaseContext().registerComponentCallbacks(callback); 352 } 353 354 @Override unregisterComponentCallbacks(ComponentCallbacks callback)355 public void unregisterComponentCallbacks(ComponentCallbacks callback) { 356 if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations(); 357 getBaseContext().unregisterComponentCallbacks(callback); 358 } 359 getTestablePermissions()360 public TestablePermissions getTestablePermissions() { 361 if (mTestablePermissions == null) { 362 mTestablePermissions = new TestablePermissions(); 363 } 364 return mTestablePermissions; 365 } 366 367 @Override checkCallingOrSelfPermission(String permission)368 public int checkCallingOrSelfPermission(String permission) { 369 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 370 return mTestablePermissions.check(permission); 371 } 372 return super.checkCallingOrSelfPermission(permission); 373 } 374 375 @Override checkCallingPermission(String permission)376 public int checkCallingPermission(String permission) { 377 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 378 return mTestablePermissions.check(permission); 379 } 380 return super.checkCallingPermission(permission); 381 } 382 383 @Override checkPermission(String permission, int pid, int uid)384 public int checkPermission(String permission, int pid, int uid) { 385 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 386 return mTestablePermissions.check(permission); 387 } 388 return super.checkPermission(permission, pid, uid); 389 } 390 391 @Override checkPermission(String permission, int pid, int uid, IBinder callerToken)392 public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { 393 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 394 return mTestablePermissions.check(permission); 395 } 396 return super.checkPermission(permission, pid, uid, callerToken); 397 } 398 399 @Override checkSelfPermission(String permission)400 public int checkSelfPermission(String permission) { 401 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 402 return mTestablePermissions.check(permission); 403 } 404 return super.checkSelfPermission(permission); 405 } 406 407 @Override enforceCallingOrSelfPermission(String permission, String message)408 public void enforceCallingOrSelfPermission(String permission, String message) { 409 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 410 mTestablePermissions.enforce(permission); 411 } else { 412 super.enforceCallingOrSelfPermission(permission, message); 413 } 414 } 415 416 @Override enforceCallingPermission(String permission, String message)417 public void enforceCallingPermission(String permission, String message) { 418 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 419 mTestablePermissions.enforce(permission); 420 } else { 421 super.enforceCallingPermission(permission, message); 422 } 423 } 424 425 @Override enforcePermission(String permission, int pid, int uid, String message)426 public void enforcePermission(String permission, int pid, int uid, String message) { 427 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) { 428 mTestablePermissions.enforce(permission); 429 } else { 430 super.enforcePermission(permission, pid, uid, message); 431 } 432 } 433 434 @Override checkCallingOrSelfUriPermission(Uri uri, int modeFlags)435 public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { 436 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 437 return mTestablePermissions.check(uri, modeFlags); 438 } 439 return super.checkCallingOrSelfUriPermission(uri, modeFlags); 440 } 441 442 @Override checkCallingUriPermission(Uri uri, int modeFlags)443 public int checkCallingUriPermission(Uri uri, int modeFlags) { 444 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 445 return mTestablePermissions.check(uri, modeFlags); 446 } 447 return super.checkCallingUriPermission(uri, modeFlags); 448 } 449 450 @Override enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)451 public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) { 452 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 453 mTestablePermissions.enforce(uri, modeFlags); 454 } else { 455 super.enforceCallingOrSelfUriPermission(uri, modeFlags, message); 456 } 457 } 458 459 @Override checkUriPermission(Uri uri, int pid, int uid, int modeFlags)460 public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { 461 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 462 return mTestablePermissions.check(uri, modeFlags); 463 } 464 return super.checkUriPermission(uri, pid, uid, modeFlags); 465 } 466 467 @Override checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken)468 public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) { 469 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 470 return mTestablePermissions.check(uri, modeFlags); 471 } 472 return super.checkUriPermission(uri, pid, uid, modeFlags, callerToken); 473 } 474 475 @Override checkUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags)476 public int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid, 477 int uid, int modeFlags) { 478 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 479 return mTestablePermissions.check(uri, modeFlags); 480 } 481 return super.checkUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags); 482 } 483 484 @Override enforceCallingUriPermission(Uri uri, int modeFlags, String message)485 public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) { 486 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 487 mTestablePermissions.enforce(uri, modeFlags); 488 } else { 489 super.enforceCallingUriPermission(uri, modeFlags, message); 490 } 491 } 492 493 @Override enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message)494 public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) { 495 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 496 mTestablePermissions.enforce(uri, modeFlags); 497 } else { 498 super.enforceUriPermission(uri, pid, uid, modeFlags, message); 499 } 500 } 501 502 @Override enforceUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags, String message)503 public void enforceUriPermission(Uri uri, String readPermission, String writePermission, 504 int pid, int uid, int modeFlags, String message) { 505 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) { 506 mTestablePermissions.enforce(uri, modeFlags); 507 } else { 508 super.enforceUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags, 509 message); 510 } 511 } 512 513 @Override apply(Statement base, Description description)514 public Statement apply(Statement base, Description description) { 515 return new TestWatcher() { 516 @Override 517 protected void succeeded(Description description) { 518 mSettingsProvider.clearValuesAndCheck(TestableContext.this); 519 } 520 521 @Override 522 protected void failed(Throwable e, Description description) { 523 mSettingsProvider.clearValuesAndCheck(TestableContext.this); 524 } 525 }.apply(base, description); 526 } 527 } 528