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>Settings support {@link TestableSettingsProvider}</li>
     47  * <li>Has support for {@link LeakCheck} for services and receivers</li>
     48  * </ul>
     49  *
     50  * <p>TestableContext should be defined as a rule on your test so it can clean up after itself.
     51  * Like the following:</p>
     52  * <pre class="prettyprint">
     53  * {@literal
     54  * @Rule
     55  * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext());
     56  * }
     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 
     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     @Override
    102     public Resources getResources() {
    103         return super.getResources();
    104     }
    105 
    106     public <T> void addMockSystemService(Class<T> service, T mock) {
    107         addMockSystemService(getSystemServiceName(service), mock);
    108     }
    109 
    110     public void addMockSystemService(String name, Object service) {
    111         if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>();
    112         mMockSystemServices.put(name, service);
    113     }
    114 
    115     public void addMockService(ComponentName component, IBinder service) {
    116         if (mMockServices == null) mMockServices = new ArrayMap<>();
    117         mMockServices.put(component, service);
    118     }
    119 
    120     @Override
    121     public Object getSystemService(String name) {
    122         if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) {
    123             return mMockSystemServices.get(name);
    124         }
    125         if (name.equals(LAYOUT_INFLATER_SERVICE)) {
    126             return getBaseContext().getSystemService(LayoutInflater.class).cloneInContext(this);
    127         }
    128         return super.getSystemService(name);
    129     }
    130 
    131     TestableSettingsProvider getSettingsProvider() {
    132         return mSettingsProvider;
    133     }
    134 
    135     @Override
    136     public TestableContentResolver getContentResolver() {
    137         return mTestableContentResolver;
    138     }
    139 
    140     @Override
    141     public Context getApplicationContext() {
    142         // Return this so its always a TestableContext.
    143         return this;
    144     }
    145 
    146     @Override
    147     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    148         if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
    149         return super.registerReceiver(receiver, filter);
    150     }
    151 
    152     @Override
    153     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
    154             String broadcastPermission, Handler scheduler) {
    155         if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
    156         return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
    157     }
    158 
    159     @Override
    160     public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
    161             IntentFilter filter, String broadcastPermission, Handler scheduler) {
    162         if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
    163         return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
    164                 scheduler);
    165     }
    166 
    167     @Override
    168     public void unregisterReceiver(BroadcastReceiver receiver) {
    169         if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations();
    170         super.unregisterReceiver(receiver);
    171     }
    172 
    173     @Override
    174     public boolean bindService(Intent service, ServiceConnection conn, int flags) {
    175         if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
    176         if (checkMocks(service.getComponent(), conn)) return true;
    177         return super.bindService(service, conn, flags);
    178     }
    179 
    180     @Override
    181     public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
    182             Handler handler, UserHandle user) {
    183         if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
    184         if (checkMocks(service.getComponent(), conn)) return true;
    185         return super.bindServiceAsUser(service, conn, flags, handler, user);
    186     }
    187 
    188     @Override
    189     public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
    190             UserHandle user) {
    191         if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
    192         if (checkMocks(service.getComponent(), conn)) return true;
    193         return super.bindServiceAsUser(service, conn, flags, user);
    194     }
    195 
    196     private boolean checkMocks(ComponentName component, ServiceConnection conn) {
    197         if (mMockServices != null && component != null && mMockServices.containsKey(component)) {
    198             if (mActiveServices == null) mActiveServices = new ArrayMap<>();
    199             mActiveServices.put(conn, component);
    200             conn.onServiceConnected(component, mMockServices.get(component));
    201             return true;
    202         }
    203         return false;
    204     }
    205 
    206     @Override
    207     public void unbindService(ServiceConnection conn) {
    208         if (mService != null) mService.getLeakInfo(conn).clearAllocations();
    209         if (mActiveServices != null && mActiveServices.containsKey(conn)) {
    210             conn.onServiceDisconnected(mActiveServices.get(conn));
    211             mActiveServices.remove(conn);
    212             return;
    213         }
    214         super.unbindService(conn);
    215     }
    216 
    217     public boolean isBound(ComponentName component) {
    218         return mActiveServices != null && mActiveServices.containsValue(component);
    219     }
    220 
    221     @Override
    222     public void registerComponentCallbacks(ComponentCallbacks callback) {
    223         if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
    224         super.registerComponentCallbacks(callback);
    225     }
    226 
    227     @Override
    228     public void unregisterComponentCallbacks(ComponentCallbacks callback) {
    229         if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
    230         super.unregisterComponentCallbacks(callback);
    231     }
    232 
    233     @Override
    234     public Statement apply(Statement base, Description description) {
    235         return new TestWatcher() {
    236             @Override
    237             protected void succeeded(Description description) {
    238                 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
    239             }
    240 
    241             @Override
    242             protected void failed(Throwable e, Description description) {
    243                 mSettingsProvider.clearValuesAndCheck(TestableContext.this);
    244             }
    245         }.apply(base, description);
    246     }
    247 }
    248