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 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