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.os.Handler; 29 import android.os.IBinder; 30 import android.os.UserHandle; 31 import android.provider.Settings; 32 import android.util.ArrayMap; 33 import android.view.LayoutInflater; 34 35 import org.junit.rules.TestRule; 36 import org.junit.rules.TestWatcher; 37 import org.junit.runner.Description; 38 import org.junit.runners.model.Statement; 39 40 /** 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> 46 * <li>Resources can be mocked out using {@link #getOrCreateTestableResources()}</li> 47 * <li>Settings support {@link TestableSettingsProvider}</li> 48 * <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"> 54 * @Rule 55 * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext()); 56 * </pre> 57 */ 58 public class TestableContext extends ContextWrapper implements TestRule { 59 60 private final TestableContentResolver mTestableContentResolver; 61 private final TestableSettingsProvider mSettingsProvider; 62 63 private ArrayMap<String, Object> mMockSystemServices; 64 private ArrayMap<ComponentName, IBinder> mMockServices; 65 private ArrayMap<ServiceConnection, ComponentName> mActiveServices; 66 67 private PackageManager mMockPackageManager; 68 private LeakCheck.Tracker mReceiver; 69 private LeakCheck.Tracker mService; 70 private LeakCheck.Tracker mComponent; 71 private TestableResources mTestableResources; 72 73 public TestableContext(Context base) { 74 this(base, null); 75 } 76 77 public TestableContext(Context base, LeakCheck check) { 78 super(base); 79 mTestableContentResolver = new TestableContentResolver(base); 80 ContentProviderClient settings = base.getContentResolver() 81 .acquireContentProviderClient(Settings.AUTHORITY); 82 mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings); 83 mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider); 84 mReceiver = check != null ? check.getTracker("receiver") : null; 85 mService = check != null ? check.getTracker("service") : null; 86 mComponent = check != null ? check.getTracker("component") : null; 87 } 88 89 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 101 /** 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 */ 128 @Override 129 public Resources getResources() { 130 return mTestableResources != null ? mTestableResources.getResources() 131 : super.getResources(); 132 } 133 134 /** 135 * @see #getSystemService(String) 136 */ 137 public <T> void addMockSystemService(Class<T> service, T mock) { 138 addMockSystemService(getSystemServiceName(service), mock); 139 } 140 141 /** 142 * @see #getSystemService(String) 143 */ 144 public void addMockSystemService(String name, Object service) { 145 if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>(); 146 mMockSystemServices.put(name, service); 147 } 148 149 /** 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 */ 154 @Override 155 public Object getSystemService(String name) { 156 if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) { 157 return mMockSystemServices.get(name); 158 } 159 if (name.equals(LAYOUT_INFLATER_SERVICE)) { 160 return getBaseContext().getSystemService(LayoutInflater.class).cloneInContext(this); 161 } 162 return super.getSystemService(name); 163 } 164 165 TestableSettingsProvider getSettingsProvider() { 166 return mSettingsProvider; 167 } 168 169 @Override 170 public TestableContentResolver getContentResolver() { 171 return mTestableContentResolver; 172 } 173 174 /** 175 * Will always return itself for a TestableContext to ensure the testable effects extend 176 * to the application context. 177 */ 178 @Override 179 public Context getApplicationContext() { 180 // Return this so its always a TestableContext. 181 return this; 182 } 183 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 211 /** 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 */ 229 @Override 230 public boolean bindService(Intent service, ServiceConnection conn, int flags) { 231 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); 232 if (checkMocks(service.getComponent(), conn)) return true; 233 return super.bindService(service, conn, flags); 234 } 235 236 /** 237 * @see #addMockService(ComponentName, IBinder) 238 */ 239 @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()); 243 if (checkMocks(service.getComponent(), conn)) return true; 244 return super.bindServiceAsUser(service, conn, flags, handler, user); 245 } 246 247 /** 248 * @see #addMockService(ComponentName, IBinder) 249 */ 250 @Override 251 public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, 252 UserHandle user) { 253 if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); 254 if (checkMocks(service.getComponent(), conn)) return true; 255 return super.bindServiceAsUser(service, conn, flags, user); 256 } 257 258 private boolean checkMocks(ComponentName component, ServiceConnection conn) { 259 if (mMockServices != null && component != null && mMockServices.containsKey(component)) { 260 if (mActiveServices == null) mActiveServices = new ArrayMap<>(); 261 mActiveServices.put(conn, component); 262 conn.onServiceConnected(component, mMockServices.get(component)); 263 return true; 264 } 265 return false; 266 } 267 268 /** 269 * @see #addMockService(ComponentName, IBinder) 270 */ 271 @Override 272 public void unbindService(ServiceConnection conn) { 273 if (mService != null) mService.getLeakInfo(conn).clearAllocations(); 274 if (mActiveServices != null && mActiveServices.containsKey(conn)) { 275 conn.onServiceDisconnected(mActiveServices.get(conn)); 276 mActiveServices.remove(conn); 277 return; 278 } 279 super.unbindService(conn); 280 } 281 282 /** 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 */ 289 public boolean isBound(ComponentName component) { 290 return mActiveServices != null && mActiveServices.containsValue(component); 291 } 292 293 @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 } 304 305 @Override 306 public Statement apply(Statement base, Description description) { 307 return new TestWatcher() { 308 @Override 309 protected void succeeded(Description description) { 310 mSettingsProvider.clearValuesAndCheck(TestableContext.this); 311 } 312 313 @Override 314 protected void failed(Throwable e, Description description) { 315 mSettingsProvider.clearValuesAndCheck(TestableContext.this); 316 } 317 }.apply(base, description); 318 } 319 } 320