Home | History | Annotate | Download | only in testing
      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  * &#064;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