summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java131
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java298
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java175
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java52
11 files changed, 698 insertions, 39 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index e1d6a94fcf07..57e324548a34 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
index fccb2a240660..f3be945d5002 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
@@ -16,26 +16,24 @@
package com.android.keyguard;
-import com.android.systemui.SysuiTestCase;
+import static junit.framework.Assert.assertEquals;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import static org.mockito.Mockito.mock;
-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;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KeyguardMessageAreaTest extends SysuiTestCase {
- private Context mContext = InstrumentationRegistry.getTargetContext();
private Handler mHandler = new Handler(Looper.getMainLooper());
private KeyguardMessageArea mMessageArea;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
index 5cb5e68fe6f8..cb0f7a388d01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
@@ -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);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index d943eb6a1a60..5dac8e54aa21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -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() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 39b64129f64d..5c87fb01e23c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 8eecfcf00d46..5401c30def54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -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 =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index d7ff04f539ab..782a4890ba55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -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);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java
new file mode 100644
index 000000000000..34f2e019761d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java
@@ -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);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java
new file mode 100644
index 000000000000..f40fe4cb2450
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java
@@ -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;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java
new file mode 100644
index 000000000000..63bb5e7f0e4a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java
@@ -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;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
new file mode 100644
index 000000000000..517982361424
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
@@ -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;
+ }
+}