blob: ffe721993a37862aa8459a25ac8773260acda984 [file] [log] [blame]
Jason Monke9789282016-11-09 08:59:56 -05001/*
Jason Monk340b0e52017-03-08 14:57:56 -05002 * Copyright (C) 2017 The Android Open Source Project
Jason Monke9789282016-11-09 08:59:56 -05003 *
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
Jason Monk340b0e52017-03-08 14:57:56 -050015package android.testing;
Jason Monke9789282016-11-09 08:59:56 -050016
Jason Monk9abca5e2016-11-11 16:18:14 -050017import android.content.BroadcastReceiver;
18import android.content.ComponentCallbacks;
Jason Monk3cfedd72016-12-09 09:31:37 -050019import android.content.ComponentName;
Jason Monke9789282016-11-09 08:59:56 -050020import android.content.ContentProviderClient;
Jason Monke9789282016-11-09 08:59:56 -050021import android.content.Context;
22import android.content.ContextWrapper;
Jason Monk9abca5e2016-11-11 16:18:14 -050023import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.ServiceConnection;
Jason Monk26bc8992017-01-04 14:17:47 -050026import android.content.pm.PackageManager;
Jason Monk3cfedd72016-12-09 09:31:37 -050027import android.content.res.Resources;
Jason Monkd01cef92017-12-08 09:48:25 -050028import android.net.Uri;
Jason Monk9abca5e2016-11-11 16:18:14 -050029import android.os.Handler;
Jason Monk3cfedd72016-12-09 09:31:37 -050030import android.os.IBinder;
Jason Monk9abca5e2016-11-11 16:18:14 -050031import android.os.UserHandle;
Jason Monke9789282016-11-09 08:59:56 -050032import android.provider.Settings;
Jason Monk3cfedd72016-12-09 09:31:37 -050033import android.util.ArrayMap;
Jason Monkaa573e92017-01-27 17:00:29 -050034import android.view.LayoutInflater;
Jason Monke9789282016-11-09 08:59:56 -050035
Jason Monk340b0e52017-03-08 14:57:56 -050036import org.junit.rules.TestRule;
37import org.junit.rules.TestWatcher;
38import org.junit.runner.Description;
39import org.junit.runners.model.Statement;
Jason Monk9abca5e2016-11-11 16:18:14 -050040
Jason Monk340b0e52017-03-08 14:57:56 -050041/**
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>
Jason Monk0c408002017-05-03 15:43:52 -040047 * <li>Resources can be mocked out using {@link #getOrCreateTestableResources()}</li>
Jason Monkf06a3172017-04-25 16:30:53 -040048 * <li>Settings support {@link TestableSettingsProvider}</li>
Jason Monk340b0e52017-03-08 14:57:56 -050049 * <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">
Jason Monk0c408002017-05-03 15:43:52 -040055 * &#064;Rule
Jason Monk340b0e52017-03-08 14:57:56 -050056 * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext());
Jason Monk340b0e52017-03-08 14:57:56 -050057 * </pre>
58 */
59public class TestableContext extends ContextWrapper implements TestRule {
Jason Monke9789282016-11-09 08:59:56 -050060
Jason Monk340b0e52017-03-08 14:57:56 -050061 private final TestableContentResolver mTestableContentResolver;
Jason Monkf06a3172017-04-25 16:30:53 -040062 private final TestableSettingsProvider mSettingsProvider;
Jason Monke9789282016-11-09 08:59:56 -050063
Jason Monk3cfedd72016-12-09 09:31:37 -050064 private ArrayMap<String, Object> mMockSystemServices;
65 private ArrayMap<ComponentName, IBinder> mMockServices;
66 private ArrayMap<ServiceConnection, ComponentName> mActiveServices;
67
Jason Monk26bc8992017-01-04 14:17:47 -050068 private PackageManager mMockPackageManager;
Jason Monk340b0e52017-03-08 14:57:56 -050069 private LeakCheck.Tracker mReceiver;
70 private LeakCheck.Tracker mService;
71 private LeakCheck.Tracker mComponent;
Jason Monk77f1b052017-05-02 14:22:21 -040072 private TestableResources mTestableResources;
Jason Monkd01cef92017-12-08 09:48:25 -050073 private TestablePermissions mTestablePermissions;
Jason Monk9abca5e2016-11-11 16:18:14 -050074
Jason Monk340b0e52017-03-08 14:57:56 -050075 public TestableContext(Context base) {
76 this(base, null);
77 }
78
79 public TestableContext(Context base, LeakCheck check) {
Jason Monke9789282016-11-09 08:59:56 -050080 super(base);
Jason Monk340b0e52017-03-08 14:57:56 -050081 mTestableContentResolver = new TestableContentResolver(base);
Jason Monke9789282016-11-09 08:59:56 -050082 ContentProviderClient settings = base.getContentResolver()
83 .acquireContentProviderClient(Settings.AUTHORITY);
Jason Monkf06a3172017-04-25 16:30:53 -040084 mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
85 mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
Jason Monk340b0e52017-03-08 14:57:56 -050086 mReceiver = check != null ? check.getTracker("receiver") : null;
87 mService = check != null ? check.getTracker("service") : null;
88 mComponent = check != null ? check.getTracker("component") : null;
Jason Monke9789282016-11-09 08:59:56 -050089 }
90
Jason Monk26bc8992017-01-04 14:17:47 -050091 public void setMockPackageManager(PackageManager mock) {
92 mMockPackageManager = mock;
93 }
94
95 @Override
96 public PackageManager getPackageManager() {
97 if (mMockPackageManager != null) {
98 return mMockPackageManager;
99 }
100 return super.getPackageManager();
101 }
102
Jason Monk77f1b052017-05-02 14:22:21 -0400103 /**
104 * Makes sure the resources being returned by this TestableContext are a version of
105 * TestableResources.
106 * @see #getResources()
107 */
108 public void ensureTestableResources() {
109 if (mTestableResources == null) {
110 mTestableResources = new TestableResources(super.getResources());
111 }
112 }
113
114 /**
115 * Get (and create if necessary) {@link TestableResources} for this TestableContext.
116 */
117 public TestableResources getOrCreateTestableResources() {
118 ensureTestableResources();
119 return mTestableResources;
120 }
121
122 /**
123 * Returns a Resources instance for the test.
124 *
125 * By default this returns the same resources object that would come from the
126 * {@link ContextWrapper}, but if {@link #ensureTestableResources()} or
127 * {@link #getOrCreateTestableResources()} has been called, it will return resources gotten from
128 * {@link TestableResources}.
129 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500130 @Override
131 public Resources getResources() {
Jason Monk77f1b052017-05-02 14:22:21 -0400132 return mTestableResources != null ? mTestableResources.getResources()
133 : super.getResources();
Jason Monk3cfedd72016-12-09 09:31:37 -0500134 }
135
Jason Monk0c408002017-05-03 15:43:52 -0400136 /**
137 * @see #getSystemService(String)
138 */
Adrian Roos91250682017-02-06 14:48:15 -0800139 public <T> void addMockSystemService(Class<T> service, T mock) {
140 addMockSystemService(getSystemServiceName(service), mock);
141 }
142
Jason Monk0c408002017-05-03 15:43:52 -0400143 /**
144 * @see #getSystemService(String)
145 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500146 public void addMockSystemService(String name, Object service) {
Jason Monk340b0e52017-03-08 14:57:56 -0500147 if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>();
Jason Monk3cfedd72016-12-09 09:31:37 -0500148 mMockSystemServices.put(name, service);
149 }
150
Jason Monk0c408002017-05-03 15:43:52 -0400151 /**
152 * If a matching mock service has been added through {@link #addMockSystemService} then
153 * that will be returned, otherwise the real service will be acquired from the base
154 * context.
155 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500156 @Override
157 public Object getSystemService(String name) {
158 if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) {
159 return mMockSystemServices.get(name);
160 }
Jason Monkaa573e92017-01-27 17:00:29 -0500161 if (name.equals(LAYOUT_INFLATER_SERVICE)) {
162 return getBaseContext().getSystemService(LayoutInflater.class).cloneInContext(this);
163 }
Jason Monk3cfedd72016-12-09 09:31:37 -0500164 return super.getSystemService(name);
165 }
166
Jason Monkf06a3172017-04-25 16:30:53 -0400167 TestableSettingsProvider getSettingsProvider() {
Jason Monke9789282016-11-09 08:59:56 -0500168 return mSettingsProvider;
169 }
170
171 @Override
Jason Monk340b0e52017-03-08 14:57:56 -0500172 public TestableContentResolver getContentResolver() {
173 return mTestableContentResolver;
Jason Monke9789282016-11-09 08:59:56 -0500174 }
175
Jason Monk0c408002017-05-03 15:43:52 -0400176 /**
177 * Will always return itself for a TestableContext to ensure the testable effects extend
178 * to the application context.
179 */
Jason Monke9789282016-11-09 08:59:56 -0500180 @Override
181 public Context getApplicationContext() {
182 // Return this so its always a TestableContext.
183 return this;
184 }
Jason Monk9abca5e2016-11-11 16:18:14 -0500185
186 @Override
187 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
188 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
189 return super.registerReceiver(receiver, filter);
190 }
191
192 @Override
193 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
194 String broadcastPermission, Handler scheduler) {
195 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
196 return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
197 }
198
199 @Override
200 public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
201 IntentFilter filter, String broadcastPermission, Handler scheduler) {
202 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
203 return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
204 scheduler);
205 }
206
207 @Override
208 public void unregisterReceiver(BroadcastReceiver receiver) {
209 if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations();
210 super.unregisterReceiver(receiver);
211 }
212
Jason Monk0c408002017-05-03 15:43:52 -0400213 /**
214 * Adds a mock service to be connected to by a bindService call.
215 * <p>
216 * Normally a TestableContext will pass through all bind requests to the base context
217 * but when addMockService has been called for a ComponentName being bound, then
218 * TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected}
219 * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected}
220 * when the service is unbound.
221 * </p>
222 */
223 public void addMockService(ComponentName component, IBinder service) {
224 if (mMockServices == null) mMockServices = new ArrayMap<>();
225 mMockServices.put(component, service);
226 }
227
228 /**
229 * @see #addMockService(ComponentName, IBinder)
230 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500231 @Override
232 public boolean bindService(Intent service, ServiceConnection conn, int flags) {
233 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
Jason Monk3cfedd72016-12-09 09:31:37 -0500234 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500235 return super.bindService(service, conn, flags);
236 }
237
Jason Monk0c408002017-05-03 15:43:52 -0400238 /**
239 * @see #addMockService(ComponentName, IBinder)
240 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500241 @Override
242 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
243 Handler handler, UserHandle user) {
244 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
Jason Monk3cfedd72016-12-09 09:31:37 -0500245 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500246 return super.bindServiceAsUser(service, conn, flags, handler, user);
247 }
248
Jason Monk0c408002017-05-03 15:43:52 -0400249 /**
250 * @see #addMockService(ComponentName, IBinder)
251 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500252 @Override
253 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
254 UserHandle user) {
255 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
Jason Monk3cfedd72016-12-09 09:31:37 -0500256 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500257 return super.bindServiceAsUser(service, conn, flags, user);
258 }
259
Jason Monk3cfedd72016-12-09 09:31:37 -0500260 private boolean checkMocks(ComponentName component, ServiceConnection conn) {
261 if (mMockServices != null && component != null && mMockServices.containsKey(component)) {
Jason Monk340b0e52017-03-08 14:57:56 -0500262 if (mActiveServices == null) mActiveServices = new ArrayMap<>();
Jason Monk3cfedd72016-12-09 09:31:37 -0500263 mActiveServices.put(conn, component);
264 conn.onServiceConnected(component, mMockServices.get(component));
265 return true;
266 }
267 return false;
268 }
269
Jason Monk0c408002017-05-03 15:43:52 -0400270 /**
271 * @see #addMockService(ComponentName, IBinder)
272 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500273 @Override
274 public void unbindService(ServiceConnection conn) {
275 if (mService != null) mService.getLeakInfo(conn).clearAllocations();
Jason Monk3cfedd72016-12-09 09:31:37 -0500276 if (mActiveServices != null && mActiveServices.containsKey(conn)) {
277 conn.onServiceDisconnected(mActiveServices.get(conn));
278 mActiveServices.remove(conn);
279 return;
280 }
Jason Monk9abca5e2016-11-11 16:18:14 -0500281 super.unbindService(conn);
282 }
283
Jason Monk0c408002017-05-03 15:43:52 -0400284 /**
285 * Check if the TestableContext has a mock binding for a specified component. Will return
286 * true between {@link ServiceConnection#onServiceConnected} and
287 * {@link ServiceConnection#onServiceDisconnected} callbacks for a mock service.
288 *
289 * @see #addMockService(ComponentName, IBinder)
290 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500291 public boolean isBound(ComponentName component) {
292 return mActiveServices != null && mActiveServices.containsValue(component);
293 }
294
Jason Monk9abca5e2016-11-11 16:18:14 -0500295 @Override
296 public void registerComponentCallbacks(ComponentCallbacks callback) {
297 if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
298 super.registerComponentCallbacks(callback);
299 }
300
301 @Override
302 public void unregisterComponentCallbacks(ComponentCallbacks callback) {
303 if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
304 super.unregisterComponentCallbacks(callback);
305 }
Jason Monk49fa0162017-01-11 09:21:56 -0500306
Jason Monkd01cef92017-12-08 09:48:25 -0500307 public TestablePermissions getTestablePermissions() {
308 if (mTestablePermissions == null) {
309 mTestablePermissions = new TestablePermissions();
310 }
311 return mTestablePermissions;
312 }
313
314 @Override
315 public int checkCallingOrSelfPermission(String permission) {
316 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
317 return mTestablePermissions.check(permission);
318 }
319 return super.checkCallingOrSelfPermission(permission);
320 }
321
322 @Override
323 public int checkCallingPermission(String permission) {
324 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
325 return mTestablePermissions.check(permission);
326 }
327 return super.checkCallingPermission(permission);
328 }
329
330 @Override
331 public int checkPermission(String permission, int pid, int uid) {
332 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
333 return mTestablePermissions.check(permission);
334 }
335 return super.checkPermission(permission, pid, uid);
336 }
337
338 @Override
339 public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
340 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
341 return mTestablePermissions.check(permission);
342 }
343 return super.checkPermission(permission, pid, uid, callerToken);
344 }
345
346 @Override
347 public int checkSelfPermission(String permission) {
348 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
349 return mTestablePermissions.check(permission);
350 }
351 return super.checkSelfPermission(permission);
352 }
353
354 @Override
355 public void enforceCallingOrSelfPermission(String permission, String message) {
356 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
357 mTestablePermissions.enforce(permission);
358 } else {
359 super.enforceCallingOrSelfPermission(permission, message);
360 }
361 }
362
363 @Override
364 public void enforceCallingPermission(String permission, String message) {
365 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
366 mTestablePermissions.enforce(permission);
367 } else {
368 super.enforceCallingPermission(permission, message);
369 }
370 }
371
372 @Override
373 public void enforcePermission(String permission, int pid, int uid, String message) {
374 if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
375 mTestablePermissions.enforce(permission);
376 } else {
377 super.enforcePermission(permission, pid, uid, message);
378 }
379 }
380
381 @Override
382 public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
383 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
384 return mTestablePermissions.check(uri, modeFlags);
385 }
386 return super.checkCallingOrSelfUriPermission(uri, modeFlags);
387 }
388
389 @Override
390 public int checkCallingUriPermission(Uri uri, int modeFlags) {
391 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
392 return mTestablePermissions.check(uri, modeFlags);
393 }
394 return super.checkCallingUriPermission(uri, modeFlags);
395 }
396
397 @Override
398 public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) {
399 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
400 mTestablePermissions.enforce(uri, modeFlags);
401 } else {
402 super.enforceCallingOrSelfUriPermission(uri, modeFlags, message);
403 }
404 }
405
406 @Override
407 public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
408 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
409 return mTestablePermissions.check(uri, modeFlags);
410 }
411 return super.checkUriPermission(uri, pid, uid, modeFlags);
412 }
413
414 @Override
415 public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
416 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
417 return mTestablePermissions.check(uri, modeFlags);
418 }
419 return super.checkUriPermission(uri, pid, uid, modeFlags, callerToken);
420 }
421
422 @Override
423 public int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid,
424 int uid, int modeFlags) {
425 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
426 return mTestablePermissions.check(uri, modeFlags);
427 }
428 return super.checkUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags);
429 }
430
431 @Override
432 public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) {
433 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
434 mTestablePermissions.enforce(uri, modeFlags);
435 } else {
436 super.enforceCallingUriPermission(uri, modeFlags, message);
437 }
438 }
439
440 @Override
441 public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) {
442 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
443 mTestablePermissions.enforce(uri, modeFlags);
444 } else {
445 super.enforceUriPermission(uri, pid, uid, modeFlags, message);
446 }
447 }
448
449 @Override
450 public void enforceUriPermission(Uri uri, String readPermission, String writePermission,
451 int pid, int uid, int modeFlags, String message) {
452 if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
453 mTestablePermissions.enforce(uri, modeFlags);
454 } else {
455 super.enforceUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags,
456 message);
457 }
458 }
459
Jason Monk340b0e52017-03-08 14:57:56 -0500460 @Override
461 public Statement apply(Statement base, Description description) {
462 return new TestWatcher() {
463 @Override
464 protected void succeeded(Description description) {
Jason Monkf06a3172017-04-25 16:30:53 -0400465 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
Jason Monk340b0e52017-03-08 14:57:56 -0500466 }
Jason Monk49fa0162017-01-11 09:21:56 -0500467
Jason Monk340b0e52017-03-08 14:57:56 -0500468 @Override
469 protected void failed(Throwable e, Description description) {
Jason Monkf06a3172017-04-25 16:30:53 -0400470 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
Jason Monk340b0e52017-03-08 14:57:56 -0500471 }
472 }.apply(base, description);
Jason Monk49fa0162017-01-11 09:21:56 -0500473 }
Jason Monke9789282016-11-09 08:59:56 -0500474}