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