blob: e2668bc4281fa4fc7cab4ecf7bfb0c6f839963de [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
Kweku Adams00e3a372018-09-28 16:57:09 -070056 * public 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 Monk1fc931a2017-12-14 13:22:58 -050086 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
Jason Monk340b0e52017-03-08 14:57:56 -050087 mReceiver = check != null ? check.getTracker("receiver") : null;
88 mService = check != null ? check.getTracker("service") : null;
89 mComponent = check != null ? check.getTracker("component") : null;
Jason Monke9789282016-11-09 08:59:56 -050090 }
91
Jason Monk26bc8992017-01-04 14:17:47 -050092 public void setMockPackageManager(PackageManager mock) {
93 mMockPackageManager = mock;
94 }
95
96 @Override
97 public PackageManager getPackageManager() {
98 if (mMockPackageManager != null) {
99 return mMockPackageManager;
100 }
101 return super.getPackageManager();
102 }
103
Jason Monk77f1b052017-05-02 14:22:21 -0400104 /**
105 * Makes sure the resources being returned by this TestableContext are a version of
106 * TestableResources.
107 * @see #getResources()
108 */
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 */
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 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500131 @Override
132 public Resources getResources() {
Jason Monk77f1b052017-05-02 14:22:21 -0400133 return mTestableResources != null ? mTestableResources.getResources()
134 : super.getResources();
Jason Monk3cfedd72016-12-09 09:31:37 -0500135 }
136
Jason Monk0c408002017-05-03 15:43:52 -0400137 /**
138 * @see #getSystemService(String)
139 */
Adrian Roos91250682017-02-06 14:48:15 -0800140 public <T> void addMockSystemService(Class<T> service, T mock) {
141 addMockSystemService(getSystemServiceName(service), mock);
142 }
143
Jason Monk0c408002017-05-03 15:43:52 -0400144 /**
145 * @see #getSystemService(String)
146 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500147 public void addMockSystemService(String name, Object service) {
Jason Monk340b0e52017-03-08 14:57:56 -0500148 if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>();
Jason Monk3cfedd72016-12-09 09:31:37 -0500149 mMockSystemServices.put(name, service);
150 }
151
Jason Monk0c408002017-05-03 15:43:52 -0400152 /**
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 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500157 @Override
158 public Object getSystemService(String name) {
159 if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) {
160 return mMockSystemServices.get(name);
161 }
Jason Monkaa573e92017-01-27 17:00:29 -0500162 if (name.equals(LAYOUT_INFLATER_SERVICE)) {
163 return getBaseContext().getSystemService(LayoutInflater.class).cloneInContext(this);
164 }
Jason Monk3cfedd72016-12-09 09:31:37 -0500165 return super.getSystemService(name);
166 }
167
Jason Monkf06a3172017-04-25 16:30:53 -0400168 TestableSettingsProvider getSettingsProvider() {
Jason Monke9789282016-11-09 08:59:56 -0500169 return mSettingsProvider;
170 }
171
172 @Override
Jason Monk340b0e52017-03-08 14:57:56 -0500173 public TestableContentResolver getContentResolver() {
174 return mTestableContentResolver;
Jason Monke9789282016-11-09 08:59:56 -0500175 }
176
Jason Monk0c408002017-05-03 15:43:52 -0400177 /**
178 * Will always return itself for a TestableContext to ensure the testable effects extend
179 * to the application context.
180 */
Jason Monke9789282016-11-09 08:59:56 -0500181 @Override
182 public Context getApplicationContext() {
183 // Return this so its always a TestableContext.
184 return this;
185 }
Jason Monk9abca5e2016-11-11 16:18:14 -0500186
187 @Override
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
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
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
209 public void unregisterReceiver(BroadcastReceiver receiver) {
210 if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations();
211 super.unregisterReceiver(receiver);
212 }
213
Jason Monk0c408002017-05-03 15:43:52 -0400214 /**
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 */
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 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500232 @Override
233 public boolean bindService(Intent service, ServiceConnection conn, int flags) {
234 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
Jason Monk3cfedd72016-12-09 09:31:37 -0500235 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500236 return super.bindService(service, conn, flags);
237 }
238
Jason Monk0c408002017-05-03 15:43:52 -0400239 /**
240 * @see #addMockService(ComponentName, IBinder)
241 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500242 @Override
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());
Jason Monk3cfedd72016-12-09 09:31:37 -0500246 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500247 return super.bindServiceAsUser(service, conn, flags, handler, user);
248 }
249
Jason Monk0c408002017-05-03 15:43:52 -0400250 /**
251 * @see #addMockService(ComponentName, IBinder)
252 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500253 @Override
254 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
255 UserHandle user) {
256 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
Jason Monk3cfedd72016-12-09 09:31:37 -0500257 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500258 return super.bindServiceAsUser(service, conn, flags, user);
259 }
260
Jason Monk3cfedd72016-12-09 09:31:37 -0500261 private boolean checkMocks(ComponentName component, ServiceConnection conn) {
262 if (mMockServices != null && component != null && mMockServices.containsKey(component)) {
Jason Monk340b0e52017-03-08 14:57:56 -0500263 if (mActiveServices == null) mActiveServices = new ArrayMap<>();
Jason Monk3cfedd72016-12-09 09:31:37 -0500264 mActiveServices.put(conn, component);
265 conn.onServiceConnected(component, mMockServices.get(component));
266 return true;
267 }
268 return false;
269 }
270
Jason Monk0c408002017-05-03 15:43:52 -0400271 /**
272 * @see #addMockService(ComponentName, IBinder)
273 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500274 @Override
275 public void unbindService(ServiceConnection conn) {
276 if (mService != null) mService.getLeakInfo(conn).clearAllocations();
Jason Monk3cfedd72016-12-09 09:31:37 -0500277 if (mActiveServices != null && mActiveServices.containsKey(conn)) {
278 conn.onServiceDisconnected(mActiveServices.get(conn));
279 mActiveServices.remove(conn);
280 return;
281 }
Jason Monk9abca5e2016-11-11 16:18:14 -0500282 super.unbindService(conn);
283 }
284
Jason Monk0c408002017-05-03 15:43:52 -0400285 /**
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 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500292 public boolean isBound(ComponentName component) {
293 return mActiveServices != null && mActiveServices.containsValue(component);
294 }
295
Jason Monk9abca5e2016-11-11 16:18:14 -0500296 @Override
297 public void registerComponentCallbacks(ComponentCallbacks callback) {
298 if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
Adrian Roos11dfd272019-03-25 19:21:26 +0100299 getBaseContext().registerComponentCallbacks(callback);
Jason Monk9abca5e2016-11-11 16:18:14 -0500300 }
301
302 @Override
303 public void unregisterComponentCallbacks(ComponentCallbacks callback) {
304 if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
Adrian Roos11dfd272019-03-25 19:21:26 +0100305 getBaseContext().unregisterComponentCallbacks(callback);
Jason Monk9abca5e2016-11-11 16:18:14 -0500306 }
Jason Monk49fa0162017-01-11 09:21:56 -0500307
Jason Monkd01cef92017-12-08 09:48:25 -0500308 public TestablePermissions getTestablePermissions() {
309 if (mTestablePermissions == null) {
310 mTestablePermissions = new TestablePermissions();
311 }
312 return mTestablePermissions;
313 }
314
315 @Override
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Jason Monk340b0e52017-03-08 14:57:56 -0500461 @Override
462 public Statement apply(Statement base, Description description) {
463 return new TestWatcher() {
464 @Override
465 protected void succeeded(Description description) {
Jason Monkf06a3172017-04-25 16:30:53 -0400466 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
Jason Monk340b0e52017-03-08 14:57:56 -0500467 }
Jason Monk49fa0162017-01-11 09:21:56 -0500468
Jason Monk340b0e52017-03-08 14:57:56 -0500469 @Override
470 protected void failed(Throwable e, Description description) {
Jason Monkf06a3172017-04-25 16:30:53 -0400471 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
Jason Monk340b0e52017-03-08 14:57:56 -0500472 }
473 }.apply(base, description);
Jason Monk49fa0162017-01-11 09:21:56 -0500474 }
Jason Monke9789282016-11-09 08:59:56 -0500475}