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