Also fixes an infinite recursion when invoking TestableContext.(un)registerComponentCallbacks(). Test: atest WindowInsetsPolicyTest Bug: 126511573 Change-Id: I5c9f40054493a83746bce6124d72412e8eb8a0d1
476 lines
18 KiB
Java
476 lines
18 KiB
Java
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the
|
|
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the specific language governing
|
|
* permissions and limitations under the License.
|
|
*/
|
|
|
|
package android.testing;
|
|
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ComponentCallbacks;
|
|
import android.content.ComponentName;
|
|
import android.content.ContentProviderClient;
|
|
import android.content.Context;
|
|
import android.content.ContextWrapper;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.ServiceConnection;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.Resources;
|
|
import android.net.Uri;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.UserHandle;
|
|
import android.provider.Settings;
|
|
import android.util.ArrayMap;
|
|
import android.view.LayoutInflater;
|
|
|
|
import org.junit.rules.TestRule;
|
|
import org.junit.rules.TestWatcher;
|
|
import org.junit.runner.Description;
|
|
import org.junit.runners.model.Statement;
|
|
|
|
/**
|
|
* A ContextWrapper with utilities specifically designed to make Testing easier.
|
|
*
|
|
* <ul>
|
|
* <li>System services can be mocked out with {@link #addMockSystemService}</li>
|
|
* <li>Service binding can be mocked out with {@link #addMockService}</li>
|
|
* <li>Resources can be mocked out using {@link #getOrCreateTestableResources()}</li>
|
|
* <li>Settings support {@link TestableSettingsProvider}</li>
|
|
* <li>Has support for {@link LeakCheck} for services and receivers</li>
|
|
* </ul>
|
|
*
|
|
* <p>TestableContext should be defined as a rule on your test so it can clean up after itself.
|
|
* Like the following:</p>
|
|
* <pre class="prettyprint">
|
|
* @Rule
|
|
* public final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext());
|
|
* </pre>
|
|
*/
|
|
public class TestableContext extends ContextWrapper implements TestRule {
|
|
|
|
private final TestableContentResolver mTestableContentResolver;
|
|
private final TestableSettingsProvider mSettingsProvider;
|
|
|
|
private ArrayMap<String, Object> mMockSystemServices;
|
|
private ArrayMap<ComponentName, IBinder> mMockServices;
|
|
private ArrayMap<ServiceConnection, ComponentName> mActiveServices;
|
|
|
|
private PackageManager mMockPackageManager;
|
|
private LeakCheck.Tracker mReceiver;
|
|
private LeakCheck.Tracker mService;
|
|
private LeakCheck.Tracker mComponent;
|
|
private TestableResources mTestableResources;
|
|
private TestablePermissions mTestablePermissions;
|
|
|
|
public TestableContext(Context base) {
|
|
this(base, null);
|
|
}
|
|
|
|
public TestableContext(Context base, LeakCheck check) {
|
|
super(base);
|
|
mTestableContentResolver = new TestableContentResolver(base);
|
|
ContentProviderClient settings = base.getContentResolver()
|
|
.acquireContentProviderClient(Settings.AUTHORITY);
|
|
mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
|
|
mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
|
|
mSettingsProvider.clearValuesAndCheck(TestableContext.this);
|
|
mReceiver = check != null ? check.getTracker("receiver") : null;
|
|
mService = check != null ? check.getTracker("service") : null;
|
|
mComponent = check != null ? check.getTracker("component") : null;
|
|
}
|
|
|
|
public void setMockPackageManager(PackageManager mock) {
|
|
mMockPackageManager = mock;
|
|
}
|
|
|
|
@Override
|
|
public PackageManager getPackageManager() {
|
|
if (mMockPackageManager != null) {
|
|
return mMockPackageManager;
|
|
}
|
|
return super.getPackageManager();
|
|
}
|
|
|
|
/**
|
|
* Makes sure the resources being returned by this TestableContext are a version of
|
|
* TestableResources.
|
|
* @see #getResources()
|
|
*/
|
|
public void ensureTestableResources() {
|
|
if (mTestableResources == null) {
|
|
mTestableResources = new TestableResources(super.getResources());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get (and create if necessary) {@link TestableResources} for this TestableContext.
|
|
*/
|
|
public TestableResources getOrCreateTestableResources() {
|
|
ensureTestableResources();
|
|
return mTestableResources;
|
|
}
|
|
|
|
/**
|
|
* Returns a Resources instance for the test.
|
|
*
|
|
* By default this returns the same resources object that would come from the
|
|
* {@link ContextWrapper}, but if {@link #ensureTestableResources()} or
|
|
* {@link #getOrCreateTestableResources()} has been called, it will return resources gotten from
|
|
* {@link TestableResources}.
|
|
*/
|
|
@Override
|
|
public Resources getResources() {
|
|
return mTestableResources != null ? mTestableResources.getResources()
|
|
: super.getResources();
|
|
}
|
|
|
|
/**
|
|
* @see #getSystemService(String)
|
|
*/
|
|
public <T> void addMockSystemService(Class<T> service, T mock) {
|
|
addMockSystemService(getSystemServiceName(service), mock);
|
|
}
|
|
|
|
/**
|
|
* @see #getSystemService(String)
|
|
*/
|
|
public void addMockSystemService(String name, Object service) {
|
|
if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>();
|
|
mMockSystemServices.put(name, service);
|
|
}
|
|
|
|
/**
|
|
* If a matching mock service has been added through {@link #addMockSystemService} then
|
|
* that will be returned, otherwise the real service will be acquired from the base
|
|
* context.
|
|
*/
|
|
@Override
|
|
public Object getSystemService(String name) {
|
|
if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) {
|
|
return mMockSystemServices.get(name);
|
|
}
|
|
if (name.equals(LAYOUT_INFLATER_SERVICE)) {
|
|
return getBaseContext().getSystemService(LayoutInflater.class).cloneInContext(this);
|
|
}
|
|
return super.getSystemService(name);
|
|
}
|
|
|
|
TestableSettingsProvider getSettingsProvider() {
|
|
return mSettingsProvider;
|
|
}
|
|
|
|
@Override
|
|
public TestableContentResolver getContentResolver() {
|
|
return mTestableContentResolver;
|
|
}
|
|
|
|
/**
|
|
* Will always return itself for a TestableContext to ensure the testable effects extend
|
|
* to the application context.
|
|
*/
|
|
@Override
|
|
public Context getApplicationContext() {
|
|
// Return this so its always a TestableContext.
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
|
|
if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
|
|
return super.registerReceiver(receiver, filter);
|
|
}
|
|
|
|
@Override
|
|
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
|
|
String broadcastPermission, Handler scheduler) {
|
|
if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
|
|
return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
|
|
}
|
|
|
|
@Override
|
|
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
|
|
IntentFilter filter, String broadcastPermission, Handler scheduler) {
|
|
if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
|
|
return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
|
|
scheduler);
|
|
}
|
|
|
|
@Override
|
|
public void unregisterReceiver(BroadcastReceiver receiver) {
|
|
if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations();
|
|
super.unregisterReceiver(receiver);
|
|
}
|
|
|
|
/**
|
|
* Adds a mock service to be connected to by a bindService call.
|
|
* <p>
|
|
* Normally a TestableContext will pass through all bind requests to the base context
|
|
* but when addMockService has been called for a ComponentName being bound, then
|
|
* TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected}
|
|
* with the specified service, and will call {@link ServiceConnection#onServiceDisconnected}
|
|
* when the service is unbound.
|
|
* </p>
|
|
*/
|
|
public void addMockService(ComponentName component, IBinder service) {
|
|
if (mMockServices == null) mMockServices = new ArrayMap<>();
|
|
mMockServices.put(component, service);
|
|
}
|
|
|
|
/**
|
|
* @see #addMockService(ComponentName, IBinder)
|
|
*/
|
|
@Override
|
|
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
|
|
if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
|
|
if (checkMocks(service.getComponent(), conn)) return true;
|
|
return super.bindService(service, conn, flags);
|
|
}
|
|
|
|
/**
|
|
* @see #addMockService(ComponentName, IBinder)
|
|
*/
|
|
@Override
|
|
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
|
|
Handler handler, UserHandle user) {
|
|
if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
|
|
if (checkMocks(service.getComponent(), conn)) return true;
|
|
return super.bindServiceAsUser(service, conn, flags, handler, user);
|
|
}
|
|
|
|
/**
|
|
* @see #addMockService(ComponentName, IBinder)
|
|
*/
|
|
@Override
|
|
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
|
|
UserHandle user) {
|
|
if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
|
|
if (checkMocks(service.getComponent(), conn)) return true;
|
|
return super.bindServiceAsUser(service, conn, flags, user);
|
|
}
|
|
|
|
private boolean checkMocks(ComponentName component, ServiceConnection conn) {
|
|
if (mMockServices != null && component != null && mMockServices.containsKey(component)) {
|
|
if (mActiveServices == null) mActiveServices = new ArrayMap<>();
|
|
mActiveServices.put(conn, component);
|
|
conn.onServiceConnected(component, mMockServices.get(component));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @see #addMockService(ComponentName, IBinder)
|
|
*/
|
|
@Override
|
|
public void unbindService(ServiceConnection conn) {
|
|
if (mService != null) mService.getLeakInfo(conn).clearAllocations();
|
|
if (mActiveServices != null && mActiveServices.containsKey(conn)) {
|
|
conn.onServiceDisconnected(mActiveServices.get(conn));
|
|
mActiveServices.remove(conn);
|
|
return;
|
|
}
|
|
super.unbindService(conn);
|
|
}
|
|
|
|
/**
|
|
* Check if the TestableContext has a mock binding for a specified component. Will return
|
|
* true between {@link ServiceConnection#onServiceConnected} and
|
|
* {@link ServiceConnection#onServiceDisconnected} callbacks for a mock service.
|
|
*
|
|
* @see #addMockService(ComponentName, IBinder)
|
|
*/
|
|
public boolean isBound(ComponentName component) {
|
|
return mActiveServices != null && mActiveServices.containsValue(component);
|
|
}
|
|
|
|
@Override
|
|
public void registerComponentCallbacks(ComponentCallbacks callback) {
|
|
if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
|
|
getBaseContext().registerComponentCallbacks(callback);
|
|
}
|
|
|
|
@Override
|
|
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
|
|
if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
|
|
getBaseContext().unregisterComponentCallbacks(callback);
|
|
}
|
|
|
|
public TestablePermissions getTestablePermissions() {
|
|
if (mTestablePermissions == null) {
|
|
mTestablePermissions = new TestablePermissions();
|
|
}
|
|
return mTestablePermissions;
|
|
}
|
|
|
|
@Override
|
|
public int checkCallingOrSelfPermission(String permission) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
|
|
return mTestablePermissions.check(permission);
|
|
}
|
|
return super.checkCallingOrSelfPermission(permission);
|
|
}
|
|
|
|
@Override
|
|
public int checkCallingPermission(String permission) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
|
|
return mTestablePermissions.check(permission);
|
|
}
|
|
return super.checkCallingPermission(permission);
|
|
}
|
|
|
|
@Override
|
|
public int checkPermission(String permission, int pid, int uid) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
|
|
return mTestablePermissions.check(permission);
|
|
}
|
|
return super.checkPermission(permission, pid, uid);
|
|
}
|
|
|
|
@Override
|
|
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
|
|
return mTestablePermissions.check(permission);
|
|
}
|
|
return super.checkPermission(permission, pid, uid, callerToken);
|
|
}
|
|
|
|
@Override
|
|
public int checkSelfPermission(String permission) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
|
|
return mTestablePermissions.check(permission);
|
|
}
|
|
return super.checkSelfPermission(permission);
|
|
}
|
|
|
|
@Override
|
|
public void enforceCallingOrSelfPermission(String permission, String message) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
|
|
mTestablePermissions.enforce(permission);
|
|
} else {
|
|
super.enforceCallingOrSelfPermission(permission, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void enforceCallingPermission(String permission, String message) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
|
|
mTestablePermissions.enforce(permission);
|
|
} else {
|
|
super.enforceCallingPermission(permission, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void enforcePermission(String permission, int pid, int uid, String message) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(permission)) {
|
|
mTestablePermissions.enforce(permission);
|
|
} else {
|
|
super.enforcePermission(permission, pid, uid, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
return mTestablePermissions.check(uri, modeFlags);
|
|
}
|
|
return super.checkCallingOrSelfUriPermission(uri, modeFlags);
|
|
}
|
|
|
|
@Override
|
|
public int checkCallingUriPermission(Uri uri, int modeFlags) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
return mTestablePermissions.check(uri, modeFlags);
|
|
}
|
|
return super.checkCallingUriPermission(uri, modeFlags);
|
|
}
|
|
|
|
@Override
|
|
public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
mTestablePermissions.enforce(uri, modeFlags);
|
|
} else {
|
|
super.enforceCallingOrSelfUriPermission(uri, modeFlags, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
return mTestablePermissions.check(uri, modeFlags);
|
|
}
|
|
return super.checkUriPermission(uri, pid, uid, modeFlags);
|
|
}
|
|
|
|
@Override
|
|
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
return mTestablePermissions.check(uri, modeFlags);
|
|
}
|
|
return super.checkUriPermission(uri, pid, uid, modeFlags, callerToken);
|
|
}
|
|
|
|
@Override
|
|
public int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid,
|
|
int uid, int modeFlags) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
return mTestablePermissions.check(uri, modeFlags);
|
|
}
|
|
return super.checkUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags);
|
|
}
|
|
|
|
@Override
|
|
public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
mTestablePermissions.enforce(uri, modeFlags);
|
|
} else {
|
|
super.enforceCallingUriPermission(uri, modeFlags, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
mTestablePermissions.enforce(uri, modeFlags);
|
|
} else {
|
|
super.enforceUriPermission(uri, pid, uid, modeFlags, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void enforceUriPermission(Uri uri, String readPermission, String writePermission,
|
|
int pid, int uid, int modeFlags, String message) {
|
|
if (mTestablePermissions != null && mTestablePermissions.wantsCall(uri)) {
|
|
mTestablePermissions.enforce(uri, modeFlags);
|
|
} else {
|
|
super.enforceUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags,
|
|
message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Statement apply(Statement base, Description description) {
|
|
return new TestWatcher() {
|
|
@Override
|
|
protected void succeeded(Description description) {
|
|
mSettingsProvider.clearValuesAndCheck(TestableContext.this);
|
|
}
|
|
|
|
@Override
|
|
protected void failed(Throwable e, Description description) {
|
|
mSettingsProvider.clearValuesAndCheck(TestableContext.this);
|
|
}
|
|
}.apply(base, description);
|
|
}
|
|
}
|