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  * &#064;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