Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2cddbcaa authored by Jason Monk's avatar Jason Monk Committed by Android (Google) Code Review
Browse files

Merge "Guard against incorrect context use." into oc-dev

parents 2e0e7f3c f06a3170
Loading
Loading
Loading
Loading
+2 −3
Original line number Original line Diff line number Diff line
@@ -47,9 +47,8 @@ public class DozeConfigurationTest extends SysuiTestCase {
            return;
            return;
        }
        }


        mContext.getSettingsProvider().acquireOverridesBuilder()
        Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.DOZE_ALWAYS_ON,
                .addSetting("secure", Settings.Secure.DOZE_ALWAYS_ON, null)
                null);
                .build();


        assertFalse(mDozeConfig.alwaysOnEnabled(UserHandle.USER_CURRENT));
        assertFalse(mDozeConfig.alwaysOnEnabled(UserHandle.USER_CURRENT));
    }
    }
+4 −8
Original line number Original line Diff line number Diff line
@@ -18,7 +18,6 @@ import android.test.suitebuilder.annotation.SmallTest;


import com.android.settingslib.Utils;
import com.android.settingslib.Utils;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import android.testing.TestableSettings.SettingOverrider;


import org.junit.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runner.RunWith;
@@ -92,11 +91,10 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
                        attr);
                        attr);


        // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in
        // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in
        // TestableSettings.
        // TestableSettingsProvider.
        SettingOverrider settingsOverrider =
        Settings.Global.putString(mContext.getContentResolver(),
                mContext.getSettingsProvider().acquireOverridesBuilder()
                Settings.Global.NETWORK_SCORING_UI_ENABLED,
                        .addSetting("global", Settings.Global.NETWORK_SCORING_UI_ENABLED, "1")
                "1");
                        .build();
        super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated
        super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated
        setupNetworkScoreManager();
        setupNetworkScoreManager();


@@ -131,8 +129,6 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
        assertEquals("SD Badge is set",
        assertEquals("SD Badge is set",
                Utils.getWifiBadgeResource(NetworkBadging.BADGING_SD),
                Utils.getWifiBadgeResource(NetworkBadging.BADGING_SD),
                iconState.iconOverlay);
                iconState.iconOverlay);

        settingsOverrider.release();
    }
    }


    private void setupNetworkScoreManager() {
    private void setupNetworkScoreManager() {
+7 −8
Original line number Original line Diff line number Diff line
@@ -43,7 +43,7 @@ import org.junit.runners.model.Statement;
 * <ul>
 * <ul>
 * <li>System services can be mocked out with {@link #addMockSystemService}</li>
 * <li>System services can be mocked out with {@link #addMockSystemService}</li>
 * <li>Service binding can be mocked out with {@link #addMockService}</li>
 * <li>Service binding can be mocked out with {@link #addMockService}</li>
 * <li>Settings support {@link TestableSettings}</li>
 * <li>Settings support {@link TestableSettingsProvider}</li>
 * <li>Has support for {@link LeakCheck} for services and receivers</li>
 * <li>Has support for {@link LeakCheck} for services and receivers</li>
 * </ul>
 * </ul>
 *
 *
@@ -59,7 +59,7 @@ import org.junit.runners.model.Statement;
public class TestableContext extends ContextWrapper implements TestRule {
public class TestableContext extends ContextWrapper implements TestRule {


    private final TestableContentResolver mTestableContentResolver;
    private final TestableContentResolver mTestableContentResolver;
    private final TestableSettings mSettingsProvider;
    private final TestableSettingsProvider mSettingsProvider;


    private ArrayMap<String, Object> mMockSystemServices;
    private ArrayMap<String, Object> mMockSystemServices;
    private ArrayMap<ComponentName, IBinder> mMockServices;
    private ArrayMap<ComponentName, IBinder> mMockServices;
@@ -79,9 +79,8 @@ public class TestableContext extends ContextWrapper implements TestRule {
        mTestableContentResolver = new TestableContentResolver(base);
        mTestableContentResolver = new TestableContentResolver(base);
        ContentProviderClient settings = base.getContentResolver()
        ContentProviderClient settings = base.getContentResolver()
                .acquireContentProviderClient(Settings.AUTHORITY);
                .acquireContentProviderClient(Settings.AUTHORITY);
        mSettingsProvider = TestableSettings.getFakeSettingsProvider(settings,
        mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
                mTestableContentResolver);
        mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
        mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider.getProvider());
        mReceiver = check != null ? check.getTracker("receiver") : null;
        mReceiver = check != null ? check.getTracker("receiver") : null;
        mService = check != null ? check.getTracker("service") : null;
        mService = check != null ? check.getTracker("service") : null;
        mComponent = check != null ? check.getTracker("component") : null;
        mComponent = check != null ? check.getTracker("component") : null;
@@ -129,7 +128,7 @@ public class TestableContext extends ContextWrapper implements TestRule {
        return super.getSystemService(name);
        return super.getSystemService(name);
    }
    }


    public TestableSettings getSettingsProvider() {
    TestableSettingsProvider getSettingsProvider() {
        return mSettingsProvider;
        return mSettingsProvider;
    }
    }


@@ -236,12 +235,12 @@ public class TestableContext extends ContextWrapper implements TestRule {
        return new TestWatcher() {
        return new TestWatcher() {
            @Override
            @Override
            protected void succeeded(Description description) {
            protected void succeeded(Description description) {
                mSettingsProvider.clearOverrides();
                mSettingsProvider.clearValuesAndCheck(TestableContext.this);
            }
            }


            @Override
            @Override
            protected void failed(Throwable e, Description description) {
            protected void failed(Throwable e, Description description) {
                mSettingsProvider.clearOverrides();
                mSettingsProvider.clearValuesAndCheck(TestableContext.this);
            }
            }
        }.apply(base, description);
        }.apply(base, description);
    }
    }
+0 −318
Original line number Original line Diff line number Diff line
/*
 * 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.ContentProvider;
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.testing.TestableSettings.SettingOverrider.Builder;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;

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 TestableSettings {

    private static final String TAG = "TestableSettings";
    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 static TestableSettingsProvider sInstance;

    private final TestableSettingsProvider mProvider;

    private TestableSettings(TestableSettingsProvider provider) {
        mProvider = provider;
    }

    public Builder acquireOverridesBuilder() {
        return new Builder(this);
    }

    public void clearOverrides() {
        List<SettingOverrider> overrides = mProvider.mOwners.remove(this);
        if (overrides != null) {
            overrides.forEach(override -> override.ensureReleased());
        }
    }

    private void acquireSettings(SettingOverrider overridder, Set<String> keys)
            throws AcquireTimeoutException {
        mProvider.acquireSettings(overridder, keys, this);
    }

    ContentProvider getProvider() {
        return mProvider;
    }

    @VisibleForTesting
    Object getLock() {
        return mProvider.mOverrideMap;
    }

    public static class SettingOverrider {
        private final Set<String> mValidKeys;
        private final Map<String, String> mValueMap = new ArrayMap<>();
        private final TestableSettings mSettings;
        private boolean mReleased;
        public Throwable mObtain;

        private SettingOverrider(Set<String> keys, TestableSettings provider) {
            mValidKeys = new ArraySet<>(keys);
            mSettings = provider;
        }

        private void ensureReleased() {
            if (!mReleased) {
                release();
            }
        }

        public void release() {
            mSettings.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 TestableSettings mProvider;
            private Set<String> mKeys = new ArraySet<>();
            private Map<String, String> mValues = new ArrayMap<>();

            private Builder(TestableSettings provider) {
                mProvider = provider;
            }

            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);
                mValues.forEach((key, value) -> overrider.putDirect(key, value));
                return overrider;
            }
        }
    }

    private static class TestableSettingsProvider extends MockContentProvider {

        private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>();
        private final Map<Object, List<SettingOverrider>> mOwners = new ArrayMap<>();

        private final ContentProviderClient mSettings;
        private final ContentResolver mResolver;

        public TestableSettingsProvider(ContentProviderClient settings, ContentResolver resolver) {
            mSettings = settings;
            mResolver = resolver;
        }

        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();
            }
        }

        private boolean checkKeysLocked(Set<String> keys, boolean shouldThrow)
                throws AcquireTimeoutException {
            for (String key : keys) {
                if (mOverrideMap.containsKey(key)) {
                    if (shouldThrow) {
                        if (DEBUG) Log.e(TAG, "Lock obtained at",
                                mOverrideMap.get(key).mObtain);
                        throw new AcquireTimeoutException("Could not acquire " + key,
                                mOverrideMap.get(key).mObtain);
                    }
                    return false;
                }
            }
            return true;
        }

        private void acquireSettings(SettingOverrider overridder, Set<String> keys,
                Object 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 (checkKeysLocked(keys, false)) break;
                    try {
                        if (DEBUG) Log.d(TAG, "Waiting for contention to finish");
                        mOverrideMap.wait(WAIT_TIMEOUT);
                    } catch (InterruptedException e) {
                    }
                }
                overridder.mObtain = new Throwable();
                checkKeysLocked(keys, true);
                for (String key : keys) {
                    if (DEBUG) Log.d(TAG, "Acquiring " + key);
                    mOverrideMap.put(key, overridder);
                }
            }
        }

        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;
            }
        }
    }

    public static class AcquireTimeoutException extends Exception {
        public AcquireTimeoutException(String str, Throwable cause) {
            super(str, cause);
        }
    }

    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.
     */
    public static TestableSettings getFakeSettingsProvider(ContentProviderClient settings,
            ContentResolver resolver) {
        if (sInstance == null) {
            sInstance = new TestableSettingsProvider(settings, resolver);
        }
        return new TestableSettings(sInstance);
    }
}
+120 −0
Original line number Original line Diff line number Diff line
/*
 * 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.ContentProviderClient;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.Settings;
import android.test.mock.MockContentProvider;
import android.util.Log;

import java.util.HashMap;

import static org.junit.Assert.*;

/**
 * Allows calls to android.provider.Settings to be tested easier.
 *
 * This provides a simple copy-on-write implementation of settings that gets cleared
 * at the end of each test.
 */
public class TestableSettingsProvider extends MockContentProvider {

    private static final String TAG = "TestableSettingsProvider";
    private static final boolean DEBUG = false;
    private static final String MY_UNIQUE_KEY = "Key_" + TestableSettingsProvider.class.getName();
    private static TestableSettingsProvider sInstance;

    private final ContentProviderClient mSettings;

    private final HashMap<String, String> mValues = new HashMap<>();

    private TestableSettingsProvider(ContentProviderClient settings) {
        mSettings = settings;
    }

    void clearValuesAndCheck(Context context) {
        mValues.put(key("global", MY_UNIQUE_KEY), MY_UNIQUE_KEY);
        mValues.put(key("secure", MY_UNIQUE_KEY), MY_UNIQUE_KEY);
        mValues.put(key("system", MY_UNIQUE_KEY), MY_UNIQUE_KEY);

        // Verify that if any test is using TestableContext, they all have the correct settings
        // provider.
        assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
                Settings.Global.getString(context.getContentResolver(), MY_UNIQUE_KEY));
        assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
                Settings.Secure.getString(context.getContentResolver(), MY_UNIQUE_KEY));
        assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
                Settings.System.getString(context.getContentResolver(), MY_UNIQUE_KEY));

        mValues.clear();
    }

    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];

            String k = key(table, arg);
            String value;
            Bundle out = new Bundle();
            switch (op) {
                case "GET":
                    if (mValues.containsKey(k)) {
                        value = mValues.get(k);
                        if (value != null) {
                            out.putString(Settings.NameValueTable.VALUE, value);
                        }
                    } else {
                        // 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);
                        }
                    }
                    break;
                case "PUT":
                    value = extras.getString(Settings.NameValueTable.VALUE, null);
                    mValues.put(k, value);
                    break;
                default:
                    throw new UnsupportedOperationException("Unknown command " + method);
            }
            return out;
    }

    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.
     */
    static TestableSettingsProvider getFakeSettingsProvider(ContentProviderClient settings) {
        if (sInstance == null) {
            sInstance = new TestableSettingsProvider(settings);
        }
        return sInstance;
    }
}
Loading