Merge "android.provider.Setting test utilities"

This commit is contained in:
Jason Monk
2016-11-11 16:28:31 +00:00
committed by Android (Google) Code Review
11 changed files with 700 additions and 41 deletions

View File

@@ -32,8 +32,8 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import com.android.systemui.statusbar.policy.BatteryController;
public class BatteryMeterDrawable extends Drawable implements

View File

@@ -16,26 +16,24 @@
package com.android.keyguard;
import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import android.os.Handler;
import android.os.Looper;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import static junit.framework.Assert.*;
import static org.mockito.Mockito.mock;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KeyguardMessageAreaTest extends SysuiTestCase {
private Context mContext = InstrumentationRegistry.getTargetContext();
private Handler mHandler = new Handler(Looper.getMainLooper());
private KeyguardMessageArea mMessageArea;

View File

@@ -17,7 +17,7 @@
package com.android.systemui;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyString;
@@ -28,27 +28,24 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BatteryMeterDrawableTest {
public class BatteryMeterDrawableTest extends SysuiTestCase {
private Context mContext;
private Resources mResources;
private BatteryMeterDrawable mBatteryMeter;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mResources = mContext.getResources();
mBatteryMeter = new BatteryMeterDrawable(mContext, 0);
}

View File

@@ -20,6 +20,10 @@ import android.support.test.InstrumentationRegistry;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import com.android.systemui.utils.TestableContext;
import org.junit.After;
import org.junit.Before;
/**
@@ -28,11 +32,16 @@ import org.junit.Before;
public class SysuiTestCase {
private Handler mHandler;
protected Context mContext;
protected TestableContext mContext;
@Before
public void SysuiSetup() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mContext = new TestableContext(InstrumentationRegistry.getTargetContext());
}
@After
public void cleanup() throws Exception {
mContext.getSettingsProvider().clearOverrides(this);
}
protected Context getContext() {

View File

@@ -17,9 +17,11 @@
package com.android.systemui.power;
import static android.test.MoreAsserts.assertNotEqual;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
@@ -29,9 +31,11 @@ import static org.mockito.Mockito.verify;
import android.app.Notification;
import android.app.NotificationManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,7 +43,7 @@ import org.mockito.ArgumentCaptor;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PowerNotificationWarningsTest {
public class PowerNotificationWarningsTest extends SysuiTestCase {
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
private PowerNotificationWarnings mPowerNotificationWarnings;
@@ -47,7 +51,7 @@ public class PowerNotificationWarningsTest {
public void setUp() throws Exception {
// Test Instance.
mPowerNotificationWarnings = new PowerNotificationWarnings(
InstrumentationRegistry.getTargetContext(), mMockNotificationManager, null);
mContext, mMockNotificationManager, null);
}
@Test

View File

@@ -18,6 +18,7 @@ package com.android.systemui.qs;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -26,11 +27,12 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,13 +40,13 @@ import org.mockito.ArgumentCaptor;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileLayoutTest {
private Context mContext = InstrumentationRegistry.getTargetContext();
private final TileLayout mTileLayout = new TileLayout(mContext);
public class TileLayoutTest extends SysuiTestCase {
private TileLayout mTileLayout;
private int mLayoutSizeForOneTile;
@Before
public void setUp() throws Exception {
mTileLayout = new TileLayout(mContext);
// Layout needs to leave space for the tile margins. Three times the margin size is
// sufficient for any number of columns.
mLayoutSizeForOneTile =

View File

@@ -16,7 +16,7 @@
package com.android.systemui.qs.external;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
@@ -25,12 +25,9 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;
@@ -38,19 +35,16 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
import android.util.Log;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -61,7 +55,7 @@ import org.mockito.stubbing.Answer;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileLifecycleManagerTest {
public class TileLifecycleManagerTest extends SysuiTestCase {
private static final int TEST_FAIL_TIMEOUT = 5000;
private final Context mMockContext = Mockito.mock(Context.class);
@@ -78,8 +72,7 @@ public class TileLifecycleManagerTest {
@Before
public void setUp() throws Exception {
setPackageEnabled(true);
mTileServiceComponentName = new ComponentName(
InstrumentationRegistry.getTargetContext(), "FakeTileService.class");
mTileServiceComponentName = new ComponentName(mContext, "FakeTileService.class");
// Stub.asInterface will just return itself.
when(mMockTileService.queryLocalInterface(anyString())).thenReturn(mMockTileService);

View File

@@ -0,0 +1,131 @@
/*
* Copyright (C) 2016 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 com.android.systemui.utils;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.database.ContentObserver;
import android.net.Uri;
import android.util.ArraySet;
import com.google.android.collect.Maps;
import java.util.Map;
/**
* Alternative to a MockContentResolver that falls back to real providers.
*/
public class FakeContentResolver extends ContentResolver {
private final Map<String, ContentProvider> mProviders = Maps.newHashMap();
private final ContentResolver mParent;
private final ArraySet<ContentProvider> mInUse = new ArraySet<>();
private boolean mFallbackToExisting;
public FakeContentResolver(Context context) {
super(context);
mParent = context.getContentResolver();
mFallbackToExisting = true;
}
/**
* Sets whether existing providers should be returned when a mock does not exist.
* The default is true.
*/
public void setFallbackToExisting(boolean fallbackToExisting) {
mFallbackToExisting = fallbackToExisting;
}
/**
* Adds access to a provider based on its authority
*
* @param name The authority name associated with the provider.
* @param provider An instance of {@link android.content.ContentProvider} or one of its
* subclasses, or null.
*/
public void addProvider(String name, ContentProvider provider) {
mProviders.put(name, provider);
}
@Override
protected IContentProvider acquireProvider(Context context, String name) {
final ContentProvider provider = mProviders.get(name);
if (provider != null) {
return provider.getIContentProvider();
} else {
return mFallbackToExisting ? mParent.acquireProvider(name) : null;
}
}
@Override
protected IContentProvider acquireExistingProvider(Context context, String name) {
final ContentProvider provider = mProviders.get(name);
if (provider != null) {
return provider.getIContentProvider();
} else {
return mFallbackToExisting ? mParent.acquireExistingProvider(
new Uri.Builder().authority(name).build()) : null;
}
}
@Override
public boolean releaseProvider(IContentProvider provider) {
if (!mFallbackToExisting) return true;
if (mInUse.contains(provider)) {
mInUse.remove(provider);
return true;
}
return mParent.releaseProvider(provider);
}
@Override
protected IContentProvider acquireUnstableProvider(Context c, String name) {
final ContentProvider provider = mProviders.get(name);
if (provider != null) {
return provider.getIContentProvider();
} else {
return mFallbackToExisting ? mParent.acquireUnstableProvider(name) : null;
}
}
@Override
public boolean releaseUnstableProvider(IContentProvider icp) {
if (!mFallbackToExisting) return true;
if (mInUse.contains(icp)) {
mInUse.remove(icp);
return true;
}
return mParent.releaseUnstableProvider(icp);
}
@Override
public void unstableProviderDied(IContentProvider icp) {
if (!mFallbackToExisting) return;
if (mInUse.contains(icp)) {
return;
}
mParent.unstableProviderDied(icp);
}
@Override
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
if (!mFallbackToExisting) return;
if (!mProviders.containsKey(uri.getAuthority())) {
super.notifyChange(uri, observer, syncToNetwork);
}
}
}

View File

@@ -0,0 +1,298 @@
/*
* Copyright (C) 2016 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 com.android.systemui.utils;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.test.mock.MockContentProvider;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider.Builder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Allows calls to android.provider.Settings to be tested easier. A SettingOverride
* can be acquired and a set of specific settings can be set to a value (and not changed
* in the system when set), so that they can be tested without breaking the test device.
* <p>
* To use, in the before method acquire the override add all settings that will affect if
* your test passes or not.
*
* <pre class="prettyprint">
* {@literal
* mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder()
* .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0")
* .build();
* }
* </pre>
*
* Then in the after free up the settings.
*
* <pre class="prettyprint">
* {@literal
* mSettingOverride.release();
* }
* </pre>
*/
public class FakeSettingsProvider extends MockContentProvider {
private static final String TAG = "FakeSettingsProvider";
private static final boolean DEBUG = false;
// Number of times to try to acquire a setting if in use.
private static final int MAX_TRIES = 10;
// Time to wait for each setting. WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time
// for a setting.
private static final long WAIT_TIMEOUT = 1000;
private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>();
private final Map<SysuiTestCase, List<SettingOverrider>> mOwners = new ArrayMap<>();
private static FakeSettingsProvider sInstance;
private final ContentProviderClient mSettings;
private final ContentResolver mResolver;
private FakeSettingsProvider(ContentProviderClient settings, ContentResolver resolver) {
mSettings = settings;
mResolver = resolver;
}
public Builder acquireOverridesBuilder(SysuiTestCase test) {
return new Builder(this, test);
}
public void clearOverrides(SysuiTestCase test) {
List<SettingOverrider> overrides = mOwners.remove(test);
if (overrides != null) {
overrides.forEach(override -> override.ensureReleased());
}
}
public Bundle call(String method, String arg, Bundle extras) {
// Methods are "GET_system", "GET_global", "PUT_secure", etc.
final String[] commands = method.split("_", 2);
final String op = commands[0];
final String table = commands[1];
synchronized (mOverrideMap) {
SettingOverrider overrider = mOverrideMap.get(key(table, arg));
if (overrider == null) {
// Fall through to real settings.
try {
if (DEBUG) Log.d(TAG, "Falling through to real settings " + method);
// TODO: Add our own version of caching to handle this.
Bundle call = mSettings.call(method, arg, extras);
call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
return call;
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
String value;
Bundle out = new Bundle();
switch (op) {
case "GET":
value = overrider.get(table, arg);
if (value != null) {
out.putString(Settings.NameValueTable.VALUE, value);
}
break;
case "PUT":
value = extras.getString(Settings.NameValueTable.VALUE, null);
if (value != null) {
overrider.put(table, arg, value);
} else {
overrider.remove(table, arg);
}
break;
default:
throw new UnsupportedOperationException("Unknown command " + method);
}
return out;
}
}
private void acquireSettings(SettingOverrider overridder, Set<String> keys,
SysuiTestCase owner) throws AcquireTimeoutException {
synchronized (mOwners) {
List<SettingOverrider> list = mOwners.get(owner);
if (list == null) {
list = new ArrayList<>();
mOwners.put(owner, list);
}
list.add(overridder);
}
synchronized (mOverrideMap) {
for (int i = 0; i < MAX_TRIES; i++) {
if (checkKeys(keys, false)) break;
try {
if (DEBUG) Log.d(TAG, "Waiting for contention to finish");
mOverrideMap.wait(WAIT_TIMEOUT);
} catch (InterruptedException e) {
}
}
checkKeys(keys, true);
for (String key : keys) {
if (DEBUG) Log.d(TAG, "Acquiring " + key);
mOverrideMap.put(key, overridder);
}
}
}
private void releaseSettings(Set<String> keys) {
synchronized (mOverrideMap) {
for (String key : keys) {
if (DEBUG) Log.d(TAG, "Releasing " + key);
mOverrideMap.remove(key);
}
if (DEBUG) Log.d(TAG, "Notifying");
mOverrideMap.notify();
}
}
@VisibleForTesting
public Object getLock() {
return mOverrideMap;
}
private boolean checkKeys(Set<String> keys, boolean shouldThrow)
throws AcquireTimeoutException {
for (String key : keys) {
if (mOverrideMap.containsKey(key)) {
if (shouldThrow) {
throw new AcquireTimeoutException("Could not acquire " + key);
}
return false;
}
}
return true;
}
public static class SettingOverrider {
private final Set<String> mValidKeys;
private final Map<String, String> mValueMap = new ArrayMap<>();
private final FakeSettingsProvider mProvider;
private boolean mReleased;
private SettingOverrider(Set<String> keys, FakeSettingsProvider provider) {
mValidKeys = new ArraySet<>(keys);
mProvider = provider;
}
private void ensureReleased() {
if (!mReleased) {
release();
}
}
public void release() {
mProvider.releaseSettings(mValidKeys);
mReleased = true;
}
private void putDirect(String key, String value) {
mValueMap.put(key, value);
}
public void put(String table, String key, String value) {
if (!mValidKeys.contains(key(table, key))) {
throw new IllegalArgumentException("Key " + table + " " + key
+ " not acquired for this overrider");
}
mValueMap.put(key(table, key), value);
}
public void remove(String table, String key) {
if (!mValidKeys.contains(key(table, key))) {
throw new IllegalArgumentException("Key " + table + " " + key
+ " not acquired for this overrider");
}
mValueMap.remove(key(table, key));
}
public String get(String table, String key) {
if (!mValidKeys.contains(key(table, key))) {
throw new IllegalArgumentException("Key " + table + " " + key
+ " not acquired for this overrider");
}
Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key)));
return mValueMap.get(key(table, key));
}
public static class Builder {
private final FakeSettingsProvider mProvider;
private final SysuiTestCase mOwner;
private Set<String> mKeys = new ArraySet<>();
private Map<String, String> mValues = new ArrayMap<>();
private Builder(FakeSettingsProvider provider, SysuiTestCase test) {
mProvider = provider;
mOwner = test;
}
public Builder addSetting(String table, String key) {
mKeys.add(key(table, key));
return this;
}
public Builder addSetting(String table, String key, String value) {
addSetting(table, key);
mValues.put(key(table, key), value);
return this;
}
public SettingOverrider build() throws AcquireTimeoutException {
SettingOverrider overrider = new SettingOverrider(mKeys, mProvider);
mProvider.acquireSettings(overrider, mKeys, mOwner);
mValues.forEach((key, value) -> overrider.putDirect(key, value));
return overrider;
}
}
}
public static class AcquireTimeoutException extends Exception {
public AcquireTimeoutException(String str) {
super(str);
}
}
private static String key(String table, String key) {
return table + "_" + key;
}
/**
* Since the settings provider is cached inside android.provider.Settings, this must
* be gotten statically to ensure there is only one instance referenced.
* @param settings
*/
public static FakeSettingsProvider getFakeSettingsProvider(ContentProviderClient settings,
ContentResolver resolver) {
if (sInstance == null) {
sInstance = new FakeSettingsProvider(settings, resolver);
}
return sInstance;
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2016 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 com.android.systemui.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.ContentResolver;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.utils.FakeSettingsProvider.AcquireTimeoutException;
import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class FakeSettingsProviderTest extends SysuiTestCase {
public static final String NONEXISTENT_SETTING = "nonexistent_setting";
private static final String TAG = "FakeSettingsProviderTest";
private SettingOverrider mOverrider;
private ContentResolver mContentResolver;
@Before
public void setup() throws AcquireTimeoutException {
mOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this)
.addSetting("secure", NONEXISTENT_SETTING)
.addSetting("global", NONEXISTENT_SETTING, "initial value")
.addSetting("global", Global.DEVICE_PROVISIONED)
.build();
mContentResolver = mContext.getContentResolver();
}
@After
public void teardown() {
if (mOverrider != null) {
mOverrider.release();
}
}
@Test
public void testInitialValueSecure() {
String value = Secure.getString(mContentResolver, NONEXISTENT_SETTING);
assertNull(value);
}
@Test
public void testInitialValueGlobal() {
String value = Global.getString(mContentResolver, NONEXISTENT_SETTING);
assertEquals("initial value", value);
}
@Test
public void testSeparateTables() {
Secure.putString(mContentResolver, NONEXISTENT_SETTING, "something");
Global.putString(mContentResolver, NONEXISTENT_SETTING, "else");
assertEquals("something", Secure.getString(mContentResolver, NONEXISTENT_SETTING));
assertEquals("something", mOverrider.get("secure", NONEXISTENT_SETTING));
assertEquals("else", Global.getString(mContentResolver, NONEXISTENT_SETTING));
assertEquals("else", mOverrider.get("global", NONEXISTENT_SETTING));
}
@Test
public void testPassThrough() {
// Grab the value of a setting that is not overridden.
assertTrue(Secure.getInt(mContentResolver, Secure.USER_SETUP_COMPLETE, 0) != 0);
}
@Test
public void testOverrideExisting() {
// Grab the value of a setting that is overridden and will be different than the actual
// value.
assertNull(Global.getString(mContentResolver, Global.DEVICE_PROVISIONED));
}
@Test
public void testRelease() {
// Verify different value.
assertNull(Global.getString(mContentResolver, Global.DEVICE_PROVISIONED));
mOverrider.release();
mOverrider = null;
// Verify actual value after release.
assertEquals("1", Global.getString(mContentResolver, Global.DEVICE_PROVISIONED));
}
@Test
public void testAutoRelease() throws Exception {
super.cleanup();
mContext.getSettingsProvider().acquireOverridesBuilder(this)
.addSetting("global", Global.DEVICE_PROVISIONED)
.build();
}
@Test
public void testContention() throws AcquireTimeoutException, InterruptedException {
SettingOverrider[] overriders = new SettingOverrider[2];
Object lock = new Object();
String secure = "secure";
String key = "something shared";
String[] result = new String[1];
overriders[0] = mContext.getSettingsProvider().acquireOverridesBuilder(this)
.addSetting(secure, key, "Some craziness")
.build();
synchronized (lock) {
HandlerThread t = runOnHandler(() -> {
try {
// Grab the lock that will be used for the settings ownership to ensure
// we have some contention going on.
synchronized (mContext.getSettingsProvider().getLock()) {
synchronized (lock) {
// Let the other thread know to release the settings, but it won't
// be able to until this thread waits in the build() method.
lock.notify();
}
overriders[1] = mContext.getSettingsProvider()
.acquireOverridesBuilder(FakeSettingsProviderTest.this)
.addSetting(secure, key, "default value")
.build();
// Ensure that the default is the one we set, and not left over from
// the other setting override.
result[0] = Settings.Secure.getString(mContentResolver, key);
synchronized (lock) {
// Let the main thread know we are done.
lock.notify();
}
}
} catch (AcquireTimeoutException e) {
Log.e(TAG, "Couldn't acquire setting", e);
}
});
// Wait for the thread to hold the acquire lock, then release the settings.
lock.wait();
overriders[0].release();
// Wait for the thread to be done getting the value.
lock.wait();
// Quit and cleanup.
t.quitSafely();
assertNotNull(overriders[1]);
overriders[1].release();
}
// Verify the value was the expected one from the thread's SettingOverride.
assertEquals("default value", result[0]);
}
private HandlerThread runOnHandler(Runnable r) {
HandlerThread t = new HandlerThread("Test Thread");
t.start();
new Handler(t.getLooper()).post(r);
return t;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2016 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 com.android.systemui.utils;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.provider.Settings;
public class TestableContext extends ContextWrapper {
private final FakeContentResolver mFakeContentResolver;
private final FakeSettingsProvider mSettingsProvider;
public TestableContext(Context base) {
super(base);
mFakeContentResolver = new FakeContentResolver(base);
ContentProviderClient settings = base.getContentResolver()
.acquireContentProviderClient(Settings.AUTHORITY);
mSettingsProvider = FakeSettingsProvider.getFakeSettingsProvider(settings,
mFakeContentResolver);
mFakeContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
}
public FakeSettingsProvider getSettingsProvider() {
return mSettingsProvider;
}
@Override
public FakeContentResolver getContentResolver() {
return mFakeContentResolver;
}
@Override
public Context getApplicationContext() {
// Return this so its always a TestableContext.
return this;
}
}