blob: 498d517c104bb6fd779e31cc534f8e7b2b1fc9c8 [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 Monk9abca5e2016-11-11 16:18:14 -050028import android.os.Handler;
Jason Monk3cfedd72016-12-09 09:31:37 -050029import android.os.IBinder;
Jason Monk9abca5e2016-11-11 16:18:14 -050030import android.os.UserHandle;
Jason Monke9789282016-11-09 08:59:56 -050031import android.provider.Settings;
Jason Monk3cfedd72016-12-09 09:31:37 -050032import android.util.ArrayMap;
Jason Monkaa573e92017-01-27 17:00:29 -050033import android.view.LayoutInflater;
Jason Monke9789282016-11-09 08:59:56 -050034
Jason Monk340b0e52017-03-08 14:57:56 -050035import org.junit.rules.TestRule;
36import org.junit.rules.TestWatcher;
37import org.junit.runner.Description;
38import org.junit.runners.model.Statement;
Jason Monk9abca5e2016-11-11 16:18:14 -050039
Jason Monk340b0e52017-03-08 14:57:56 -050040/**
41 * A ContextWrapper with utilities specifically designed to make Testing easier.
42 *
43 * <ul>
44 * <li>System services can be mocked out with {@link #addMockSystemService}</li>
45 * <li>Service binding can be mocked out with {@link #addMockService}</li>
Jason Monk0c408002017-05-03 15:43:52 -040046 * <li>Resources can be mocked out using {@link #getOrCreateTestableResources()}</li>
Jason Monkf06a3172017-04-25 16:30:53 -040047 * <li>Settings support {@link TestableSettingsProvider}</li>
Jason Monk340b0e52017-03-08 14:57:56 -050048 * <li>Has support for {@link LeakCheck} for services and receivers</li>
49 * </ul>
50 *
51 * <p>TestableContext should be defined as a rule on your test so it can clean up after itself.
52 * Like the following:</p>
53 * <pre class="prettyprint">
Jason Monk0c408002017-05-03 15:43:52 -040054 * &#064;Rule
Jason Monk340b0e52017-03-08 14:57:56 -050055 * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext());
Jason Monk340b0e52017-03-08 14:57:56 -050056 * </pre>
57 */
58public class TestableContext extends ContextWrapper implements TestRule {
Jason Monke9789282016-11-09 08:59:56 -050059
Jason Monk340b0e52017-03-08 14:57:56 -050060 private final TestableContentResolver mTestableContentResolver;
Jason Monkf06a3172017-04-25 16:30:53 -040061 private final TestableSettingsProvider mSettingsProvider;
Jason Monke9789282016-11-09 08:59:56 -050062
Jason Monk3cfedd72016-12-09 09:31:37 -050063 private ArrayMap<String, Object> mMockSystemServices;
64 private ArrayMap<ComponentName, IBinder> mMockServices;
65 private ArrayMap<ServiceConnection, ComponentName> mActiveServices;
66
Jason Monk26bc8992017-01-04 14:17:47 -050067 private PackageManager mMockPackageManager;
Jason Monk340b0e52017-03-08 14:57:56 -050068 private LeakCheck.Tracker mReceiver;
69 private LeakCheck.Tracker mService;
70 private LeakCheck.Tracker mComponent;
Jason Monk77f1b052017-05-02 14:22:21 -040071 private TestableResources mTestableResources;
Jason Monk9abca5e2016-11-11 16:18:14 -050072
Jason Monk340b0e52017-03-08 14:57:56 -050073 public TestableContext(Context base) {
74 this(base, null);
75 }
76
77 public TestableContext(Context base, LeakCheck check) {
Jason Monke9789282016-11-09 08:59:56 -050078 super(base);
Jason Monk340b0e52017-03-08 14:57:56 -050079 mTestableContentResolver = new TestableContentResolver(base);
Jason Monke9789282016-11-09 08:59:56 -050080 ContentProviderClient settings = base.getContentResolver()
81 .acquireContentProviderClient(Settings.AUTHORITY);
Jason Monkf06a3172017-04-25 16:30:53 -040082 mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
83 mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
Jason Monk340b0e52017-03-08 14:57:56 -050084 mReceiver = check != null ? check.getTracker("receiver") : null;
85 mService = check != null ? check.getTracker("service") : null;
86 mComponent = check != null ? check.getTracker("component") : null;
Jason Monke9789282016-11-09 08:59:56 -050087 }
88
Jason Monk26bc8992017-01-04 14:17:47 -050089 public void setMockPackageManager(PackageManager mock) {
90 mMockPackageManager = mock;
91 }
92
93 @Override
94 public PackageManager getPackageManager() {
95 if (mMockPackageManager != null) {
96 return mMockPackageManager;
97 }
98 return super.getPackageManager();
99 }
100
Jason Monk77f1b052017-05-02 14:22:21 -0400101 /**
102 * Makes sure the resources being returned by this TestableContext are a version of
103 * TestableResources.
104 * @see #getResources()
105 */
106 public void ensureTestableResources() {
107 if (mTestableResources == null) {
108 mTestableResources = new TestableResources(super.getResources());
109 }
110 }
111
112 /**
113 * Get (and create if necessary) {@link TestableResources} for this TestableContext.
114 */
115 public TestableResources getOrCreateTestableResources() {
116 ensureTestableResources();
117 return mTestableResources;
118 }
119
120 /**
121 * Returns a Resources instance for the test.
122 *
123 * By default this returns the same resources object that would come from the
124 * {@link ContextWrapper}, but if {@link #ensureTestableResources()} or
125 * {@link #getOrCreateTestableResources()} has been called, it will return resources gotten from
126 * {@link TestableResources}.
127 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500128 @Override
129 public Resources getResources() {
Jason Monk77f1b052017-05-02 14:22:21 -0400130 return mTestableResources != null ? mTestableResources.getResources()
131 : super.getResources();
Jason Monk3cfedd72016-12-09 09:31:37 -0500132 }
133
Jason Monk0c408002017-05-03 15:43:52 -0400134 /**
135 * @see #getSystemService(String)
136 */
Adrian Roos91250682017-02-06 14:48:15 -0800137 public <T> void addMockSystemService(Class<T> service, T mock) {
138 addMockSystemService(getSystemServiceName(service), mock);
139 }
140
Jason Monk0c408002017-05-03 15:43:52 -0400141 /**
142 * @see #getSystemService(String)
143 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500144 public void addMockSystemService(String name, Object service) {
Jason Monk340b0e52017-03-08 14:57:56 -0500145 if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>();
Jason Monk3cfedd72016-12-09 09:31:37 -0500146 mMockSystemServices.put(name, service);
147 }
148
Jason Monk0c408002017-05-03 15:43:52 -0400149 /**
150 * If a matching mock service has been added through {@link #addMockSystemService} then
151 * that will be returned, otherwise the real service will be acquired from the base
152 * context.
153 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500154 @Override
155 public Object getSystemService(String name) {
156 if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) {
157 return mMockSystemServices.get(name);
158 }
Jason Monkaa573e92017-01-27 17:00:29 -0500159 if (name.equals(LAYOUT_INFLATER_SERVICE)) {
160 return getBaseContext().getSystemService(LayoutInflater.class).cloneInContext(this);
161 }
Jason Monk3cfedd72016-12-09 09:31:37 -0500162 return super.getSystemService(name);
163 }
164
Jason Monkf06a3172017-04-25 16:30:53 -0400165 TestableSettingsProvider getSettingsProvider() {
Jason Monke9789282016-11-09 08:59:56 -0500166 return mSettingsProvider;
167 }
168
169 @Override
Jason Monk340b0e52017-03-08 14:57:56 -0500170 public TestableContentResolver getContentResolver() {
171 return mTestableContentResolver;
Jason Monke9789282016-11-09 08:59:56 -0500172 }
173
Jason Monk0c408002017-05-03 15:43:52 -0400174 /**
175 * Will always return itself for a TestableContext to ensure the testable effects extend
176 * to the application context.
177 */
Jason Monke9789282016-11-09 08:59:56 -0500178 @Override
179 public Context getApplicationContext() {
180 // Return this so its always a TestableContext.
181 return this;
182 }
Jason Monk9abca5e2016-11-11 16:18:14 -0500183
184 @Override
185 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
186 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
187 return super.registerReceiver(receiver, filter);
188 }
189
190 @Override
191 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
192 String broadcastPermission, Handler scheduler) {
193 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
194 return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
195 }
196
197 @Override
198 public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
199 IntentFilter filter, String broadcastPermission, Handler scheduler) {
200 if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
201 return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
202 scheduler);
203 }
204
205 @Override
206 public void unregisterReceiver(BroadcastReceiver receiver) {
207 if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations();
208 super.unregisterReceiver(receiver);
209 }
210
Jason Monk0c408002017-05-03 15:43:52 -0400211 /**
212 * Adds a mock service to be connected to by a bindService call.
213 * <p>
214 * Normally a TestableContext will pass through all bind requests to the base context
215 * but when addMockService has been called for a ComponentName being bound, then
216 * TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected}
217 * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected}
218 * when the service is unbound.
219 * </p>
220 */
221 public void addMockService(ComponentName component, IBinder service) {
222 if (mMockServices == null) mMockServices = new ArrayMap<>();
223 mMockServices.put(component, service);
224 }
225
226 /**
227 * @see #addMockService(ComponentName, IBinder)
228 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500229 @Override
230 public boolean bindService(Intent service, ServiceConnection conn, int flags) {
231 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
Jason Monk3cfedd72016-12-09 09:31:37 -0500232 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500233 return super.bindService(service, conn, flags);
234 }
235
Jason Monk0c408002017-05-03 15:43:52 -0400236 /**
237 * @see #addMockService(ComponentName, IBinder)
238 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500239 @Override
240 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
241 Handler handler, UserHandle user) {
242 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
Jason Monk3cfedd72016-12-09 09:31:37 -0500243 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500244 return super.bindServiceAsUser(service, conn, flags, handler, user);
245 }
246
Jason Monk0c408002017-05-03 15:43:52 -0400247 /**
248 * @see #addMockService(ComponentName, IBinder)
249 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500250 @Override
251 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
252 UserHandle user) {
253 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
Jason Monk3cfedd72016-12-09 09:31:37 -0500254 if (checkMocks(service.getComponent(), conn)) return true;
Jason Monk9abca5e2016-11-11 16:18:14 -0500255 return super.bindServiceAsUser(service, conn, flags, user);
256 }
257
Jason Monk3cfedd72016-12-09 09:31:37 -0500258 private boolean checkMocks(ComponentName component, ServiceConnection conn) {
259 if (mMockServices != null && component != null && mMockServices.containsKey(component)) {
Jason Monk340b0e52017-03-08 14:57:56 -0500260 if (mActiveServices == null) mActiveServices = new ArrayMap<>();
Jason Monk3cfedd72016-12-09 09:31:37 -0500261 mActiveServices.put(conn, component);
262 conn.onServiceConnected(component, mMockServices.get(component));
263 return true;
264 }
265 return false;
266 }
267
Jason Monk0c408002017-05-03 15:43:52 -0400268 /**
269 * @see #addMockService(ComponentName, IBinder)
270 */
Jason Monk9abca5e2016-11-11 16:18:14 -0500271 @Override
272 public void unbindService(ServiceConnection conn) {
273 if (mService != null) mService.getLeakInfo(conn).clearAllocations();
Jason Monk3cfedd72016-12-09 09:31:37 -0500274 if (mActiveServices != null && mActiveServices.containsKey(conn)) {
275 conn.onServiceDisconnected(mActiveServices.get(conn));
276 mActiveServices.remove(conn);
277 return;
278 }
Jason Monk9abca5e2016-11-11 16:18:14 -0500279 super.unbindService(conn);
280 }
281
Jason Monk0c408002017-05-03 15:43:52 -0400282 /**
283 * Check if the TestableContext has a mock binding for a specified component. Will return
284 * true between {@link ServiceConnection#onServiceConnected} and
285 * {@link ServiceConnection#onServiceDisconnected} callbacks for a mock service.
286 *
287 * @see #addMockService(ComponentName, IBinder)
288 */
Jason Monk3cfedd72016-12-09 09:31:37 -0500289 public boolean isBound(ComponentName component) {
290 return mActiveServices != null && mActiveServices.containsValue(component);
291 }
292
Jason Monk9abca5e2016-11-11 16:18:14 -0500293 @Override
294 public void registerComponentCallbacks(ComponentCallbacks callback) {
295 if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
296 super.registerComponentCallbacks(callback);
297 }
298
299 @Override
300 public void unregisterComponentCallbacks(ComponentCallbacks callback) {
301 if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
302 super.unregisterComponentCallbacks(callback);
303 }
Jason Monk49fa0162017-01-11 09:21:56 -0500304
Jason Monk340b0e52017-03-08 14:57:56 -0500305 @Override
306 public Statement apply(Statement base, Description description) {
307 return new TestWatcher() {
308 @Override
309 protected void succeeded(Description description) {
Jason Monkf06a3172017-04-25 16:30:53 -0400310 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
Jason Monk340b0e52017-03-08 14:57:56 -0500311 }
Jason Monk49fa0162017-01-11 09:21:56 -0500312
Jason Monk340b0e52017-03-08 14:57:56 -0500313 @Override
314 protected void failed(Throwable e, Description description) {
Jason Monkf06a3172017-04-25 16:30:53 -0400315 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
Jason Monk340b0e52017-03-08 14:57:56 -0500316 }
317 }.apply(base, description);
Jason Monk49fa0162017-01-11 09:21:56 -0500318 }
Jason Monke9789282016-11-09 08:59:56 -0500319}