/*
 * Copyright (C) 2015 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.providers.settings;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.fail;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;

/**
 * Tests for the SettingContentProvider.
 *
 * Before you run this test you must add a secondary user.
 */
public class SettingsProviderTest extends BaseSettingsProviderTest {
    private static final String LOG_TAG = "SettingsProviderTest";

    private static final long WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec

    private static final String[] NAME_VALUE_COLUMNS = new String[]{
            Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE
    };

    private final Object mLock = new Object();

    @Test
    public void testSetAndGetGlobalViaFrontEndApiForSystemUser() throws Exception {
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
    }

    @Test
    public void testSetAndGetGlobalViaFrontEndApiForNonSystemUser() throws Exception {
        final int secondaryUserId = getSecondaryUserId();
        if (secondaryUserId == UserHandle.USER_SYSTEM) {
            Log.w(LOG_TAG, "No secondary user. Skipping "
                    + "testSetAndGetGlobalViaFrontEndApiForNonSystemUser");
            return;
        }
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, secondaryUserId);
    }

    @Test
    public void testSetAndGetSecureViaFrontEndApiForSystemUser() throws Exception {
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, UserHandle.USER_SYSTEM);
    }

    @Test
    public void testSetAndGetSecureViaFrontEndApiForNonSystemUser() throws Exception {
        final int secondaryUserId = getSecondaryUserId();
        if (secondaryUserId == UserHandle.USER_SYSTEM) {
            Log.w(LOG_TAG, "No secondary user. Skipping "
                    + "testSetAndGetSecureViaFrontEndApiForNonSystemUser");
            return;
        }
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, secondaryUserId);
    }

    @Test
    public void testSetAndGetSystemViaFrontEndApiForSystemUser() throws Exception {
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, UserHandle.USER_SYSTEM);
    }

    @Test
    public void testSetAndGetSystemViaFrontEndApiForNonSystemUser() throws Exception {
        final int secondaryUserId = getSecondaryUserId();
        if (secondaryUserId == UserHandle.USER_SYSTEM) {
            Log.w(LOG_TAG, "No secondary user. Skipping "
                    + "testSetAndGetSystemViaFrontEndApiForNonSystemUser");
            return;
        }
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, secondaryUserId);
    }

    @Test
    public void testSetAndGetGlobalViaProviderApi() throws Exception {
        performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testSetAndGetSecureViaProviderApi() throws Exception {
        performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_SECURE);
    }

    @Test
    public void testSetAndGetSystemViaProviderApi() throws Exception {
        performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_SYSTEM);
    }

    @Test
    public void testSelectAllGlobalViaProviderApi() throws Exception {
        setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_GLOBAL,
                FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
        try {
            queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_GLOBAL,
                    FAKE_SETTING_NAME);
        } finally {
            deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
        }
    }

    @Test
    public void testSelectAllSecureViaProviderApi() throws Exception {
        setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_SECURE,
                FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
        try {
            queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_SECURE,
                    FAKE_SETTING_NAME);
        } finally {
            deleteStringViaProviderApi(SETTING_TYPE_SECURE, FAKE_SETTING_NAME);
        }
    }

    @Test
    public void testSelectAllSystemViaProviderApi() throws Exception {
        setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_SYSTEM,
                FAKE_SETTING_NAME, FAKE_SETTING_VALUE, true);
        try {
            queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(SETTING_TYPE_SYSTEM,
                    FAKE_SETTING_NAME);
        } finally {
            deleteStringViaProviderApi(SETTING_TYPE_SYSTEM, FAKE_SETTING_NAME);
        }
    }

    @Test
    public void testQueryUpdateDeleteGlobalViaProviderApi() throws Exception {
        doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testQueryUpdateDeleteSecureViaProviderApi() throws Exception {
        doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_SECURE);
    }

    @Test
    public void testQueryUpdateDeleteSystemViaProviderApi() throws Exception {
        doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_SYSTEM);
    }

    @Test
    public void testBulkInsertGlobalViaProviderApi() throws Exception {
        toTestBulkInsertViaProviderApiForType(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testBulkInsertSystemViaProviderApi() throws Exception {
        toTestBulkInsertViaProviderApiForType(SETTING_TYPE_SYSTEM);
    }

    @Test
    public void testBulkInsertSecureViaProviderApi() throws Exception {
        toTestBulkInsertViaProviderApiForType(SETTING_TYPE_SECURE);
    }

    @Test
    public void testAppCannotRunsSystemOutOfMemoryWritingSystemSettings() throws Exception {
        int insertedCount = 0;
        try {
            for (; insertedCount < 1200; insertedCount++) {
                Log.w(LOG_TAG, "Adding app specific setting: " + insertedCount);
                insertStringViaProviderApi(SETTING_TYPE_SYSTEM,
                        String.valueOf(insertedCount), FAKE_SETTING_VALUE, false);
            }
            fail("Adding app specific settings must be bound.");
        } catch (Exception e) {
            // expected
        } finally {
            for (; insertedCount >= 0; insertedCount--) {
                Log.w(LOG_TAG, "Removing app specific setting: " + insertedCount);
                deleteStringViaProviderApi(SETTING_TYPE_SYSTEM,
                        String.valueOf(insertedCount));
            }
        }
    }

    @Test
    public void testQueryStringInBracketsGlobalViaProviderApiForType() throws Exception {
        doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testQueryStringInBracketsSecureViaProviderApiForType() throws Exception {
        doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_SECURE);
    }

    @Test
    public void testQueryStringInBracketsSystemViaProviderApiForType() throws Exception {
        doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_SYSTEM);
    }

    @Test
    public void testQueryStringWithAppendedNameToUriViaProviderApi() throws Exception {
        // Make sure we have a clean slate.
        deleteStringViaProviderApi(SETTING_TYPE_SYSTEM, FAKE_SETTING_NAME);

        try {
            // Insert the setting.
            final Uri uri = insertStringViaProviderApi(SETTING_TYPE_SYSTEM, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE, false);
            Uri expectUri = Uri.withAppendedPath(getBaseUriForType(SETTING_TYPE_SYSTEM),
                    FAKE_SETTING_NAME);
            assertEquals("Did not get expected Uri.", expectUri, uri);

            // Make sure the first setting is there.
            String firstValue = queryStringViaProviderApi(SETTING_TYPE_SYSTEM, FAKE_SETTING_NAME,
                    false, true);
            assertEquals("Setting must be present", FAKE_SETTING_VALUE, firstValue);
        } finally {
            // Clean up.
            deleteStringViaProviderApi(SETTING_TYPE_SYSTEM, FAKE_SETTING_NAME);
        }
    }

    @Test
    public void testResetModePackageDefaultsGlobal() throws Exception {
        testResetModePackageDefaultsCommon(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testResetModePackageDefaultsSecure() throws Exception {
        testResetModePackageDefaultsCommon(SETTING_TYPE_SECURE);
    }

    private void testResetModePackageDefaultsCommon(int type) throws Exception {
        // Make sure we have a clean slate.
        setSettingViaShell(type, FAKE_SETTING_NAME, null, true);
        try {
            // Set a value but don't make it the default
            setSettingViaShell(type, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE, false);

            // Reset the changes made by the "shell/root" package
            resetToDefaultsViaShell(type, "shell");
            resetToDefaultsViaShell(type, "root");

            // Make sure the old APIs don't set defaults
            assertNull(getSetting(type, FAKE_SETTING_NAME));

            // Set a value and make it the default
            setSettingViaShell(type, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE, true);
            // Change the setting from the default
            setSettingViaShell(type, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE_2, false);

            // Reset the changes made by this package
            resetToDefaultsViaShell(type, "shell");
            resetToDefaultsViaShell(type, "root");

            // Make sure the old APIs don't set defaults
            assertEquals(FAKE_SETTING_VALUE, getSetting(type, FAKE_SETTING_NAME));
        } finally {
            // Make sure we have a clean slate.
            setSettingViaShell(type, FAKE_SETTING_NAME, null, true);
        }
    }

    @Test
    public void testResetModePackageDefaultsWithTokensGlobal() throws Exception {
        testResetModePackageDefaultsWithTokensCommon(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testResetModePackageDefaultsWithTokensSecure() throws Exception {
        testResetModePackageDefaultsWithTokensCommon(SETTING_TYPE_SECURE);
    }

    private void testResetModePackageDefaultsWithTokensCommon(int type) throws Exception {
        // Make sure we have a clean slate.
        setSettingViaShell(type, FAKE_SETTING_NAME, null, true);
        setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
        try {
            // Set a default value
            setSettingViaShell(type, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE, true);
            // Change the default and associate a token
            setSettingViaShell(type, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE_2, "TOKEN1", false);

            // Set a default value
            setSettingViaShell(type, FAKE_SETTING_NAME_1,
                    FAKE_SETTING_VALUE, "TOKEN2", true);
            // Change the default and associate a token
            setSettingViaShell(type, FAKE_SETTING_NAME_1,
                    FAKE_SETTING_VALUE_2, "TOKEN2", false);

            // Reset settings associated with TOKEN1
            resetToDefaultsViaShell(type, "shell", "TOKEN1");
            resetToDefaultsViaShell(type, "root", "TOKEN1");

            // Make sure TOKEN1 settings are reset
            assertEquals(FAKE_SETTING_VALUE, getSetting(type,
                    FAKE_SETTING_NAME));

            // Make sure TOKEN2 settings are not reset
            assertEquals(FAKE_SETTING_VALUE_2, getSetting(type,
                    FAKE_SETTING_NAME_1));

            // Reset settings associated with TOKEN2
            resetToDefaultsViaShell(type, "shell", "TOKEN2");
            resetToDefaultsViaShell(type, "root", "TOKEN2");

            // Make sure TOKEN2 settings are reset
            assertEquals(FAKE_SETTING_VALUE, getSetting(type,
                    FAKE_SETTING_NAME_1));
        } finally {
            // Make sure we have a clean slate.
            setSettingViaShell(type, FAKE_SETTING_NAME, null, true);
            setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
        }
    }

    @Test
    public void testResetModeUntrustedDefaultsGlobal() throws Exception {
        testResetModeUntrustedDefaultsCommon(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testResetModeUntrustedDefaultsSecure() throws Exception {
        testResetModeUntrustedDefaultsCommon(SETTING_TYPE_SECURE);
    }

    private void testResetModeUntrustedDefaultsCommon(int type) throws Exception {
        // Make sure we have a clean slate.
        putSetting(type, FAKE_SETTING_NAME, null);
        setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
        try {
            // Set a default setting as a trusted component
            putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE);
            // Change the setting as a trusted component
            putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE_2);

            // Set a default setting as an untrusted component
            setSettingViaShell(type, FAKE_SETTING_NAME_1,
                    FAKE_SETTING_VALUE, true);
            // Change the setting as an untrusted component
            setSettingViaShell(type, FAKE_SETTING_NAME_1,
                    FAKE_SETTING_VALUE_2, false);

            // Reset the untrusted changes to defaults
            resetSettingsViaShell(type,
                    Settings.RESET_MODE_UNTRUSTED_DEFAULTS);

            // Check whether only the untrusted changes set to defaults
            assertEquals(FAKE_SETTING_VALUE_2, getSetting(type, FAKE_SETTING_NAME));
            assertEquals(FAKE_SETTING_VALUE, getSetting(type, FAKE_SETTING_NAME_1));
        } finally {
            // Make sure we have a clean slate.
            putSetting(type, FAKE_SETTING_NAME, null);
            setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
        }
    }

    @Test
    public void testResetModeUntrustedClearGlobal() throws Exception {
        testResetModeUntrustedClearCommon(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testResetModeUntrustedClearSecure() throws Exception {
        testResetModeUntrustedClearCommon(SETTING_TYPE_SECURE);
    }

    private void testResetModeUntrustedClearCommon(int type) throws Exception {
        // Make sure we have a clean slate.
        putSetting(type, FAKE_SETTING_NAME, null);
        setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
        try {
            // Set a default setting as a trusted component
            putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE);
            // Change the setting as a trusted component
            putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE_2);

            // Set a default setting as an untrusted component
            setSettingViaShell(type, FAKE_SETTING_NAME_1,
                    FAKE_SETTING_VALUE, true);
            // Change the setting as an untrusted component
            setSettingViaShell(type, FAKE_SETTING_NAME_1,
                    FAKE_SETTING_VALUE_2, false);

            // Clear the untrusted changes
            resetSettingsViaShell(type,
                    Settings.RESET_MODE_UNTRUSTED_CHANGES);

            // Check whether only the untrusted changes set to defaults
            assertEquals(FAKE_SETTING_VALUE_2, getSetting(type, FAKE_SETTING_NAME));
            assertNull(getSetting(type, FAKE_SETTING_NAME_1));
        } finally {
            // Make sure we have a clean slate.
            putSetting(type, FAKE_SETTING_NAME, null);
            setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
        }
    }

    @Test
    public void testResetModeTrustedDefaultsGlobal() throws Exception {
        testResetModeTrustedDefaultsCommon(SETTING_TYPE_GLOBAL);
    }

    @Test
    public void testResetModeTrustedDefaultsSecure() throws Exception {
        testResetModeTrustedDefaultsCommon(SETTING_TYPE_SECURE);
    }

    private void testResetModeTrustedDefaultsCommon(int type) throws Exception {
        // Make sure we have a clean slate.
        putSetting(type, FAKE_SETTING_NAME, null);
        setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
        try {
            // Set a default setting as a trusted component
            putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE);
            // Change the setting as a trusted component
            setSettingViaShell(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE_2, false);

            // Set a default setting as an untrusted component
            setSettingViaShell(type, FAKE_SETTING_NAME_1,
                    FAKE_SETTING_VALUE, true);
            // Change the setting as an untrusted component
            setSettingViaShell(type, FAKE_SETTING_NAME_1,
                    FAKE_SETTING_VALUE_2, false);

            // Reset to trusted defaults
            resetSettingsViaShell(type,
                    Settings.RESET_MODE_TRUSTED_DEFAULTS);

            // Check whether snapped to trusted defaults
            assertEquals(FAKE_SETTING_VALUE, getSetting(type, FAKE_SETTING_NAME));
            assertNull(getSetting(type, FAKE_SETTING_NAME_1));
        } finally {
            // Make sure we have a clean slate.
            putSetting(type, FAKE_SETTING_NAME, null);
            setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
        }
    }

    private void doTestQueryStringInBracketsViaProviderApiForType(int type) {
        // Make sure we have a clean slate.
        deleteStringViaProviderApi(type, FAKE_SETTING_NAME);

        try {
            // Insert the setting.
            final Uri uri = insertStringViaProviderApi(type, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE, false);
            Uri expectUri = Uri.withAppendedPath(getBaseUriForType(type), FAKE_SETTING_NAME);
            assertEquals("Did not get expected Uri.", expectUri, uri);

            // Make sure the first setting is there.
            String firstValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME, true, false);
            assertEquals("Setting must be present", FAKE_SETTING_VALUE, firstValue);
        } finally {
            // Clean up.
            deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
        }
    }

    private void toTestBulkInsertViaProviderApiForType(int type) {
        // Make sure we have a clean slate.
        deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
        deleteStringViaProviderApi(type, FAKE_SETTING_NAME_1);
        deleteStringViaProviderApi(type, FAKE_SETTING_NAME_2);

        try {
            Uri uri = getBaseUriForType(type);
            ContentValues[] allValues = new ContentValues[3];

            // Insert the first setting.
            ContentValues firstValues = new ContentValues();
            firstValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME);
            firstValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE);
            allValues[0] = firstValues;

            // Insert the second setting.
            ContentValues secondValues = new ContentValues();
            secondValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME_1);
            secondValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE_1);
            allValues[1] = secondValues;

            // Insert the third setting. (null)
            ContentValues thirdValues = new ContentValues();
            thirdValues.put(Settings.NameValueTable.NAME, FAKE_SETTING_NAME_2);
            thirdValues.put(Settings.NameValueTable.VALUE, FAKE_SETTING_VALUE_2);
            allValues[2] = thirdValues;

            // Verify insertion count.
            final int insertCount = getContext().getContentResolver().bulkInsert(uri, allValues);
            assertSame("Couldn't insert both values", 3, insertCount);

            // Make sure the first setting is there.
            String firstValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
            assertEquals("First setting must be present", FAKE_SETTING_VALUE, firstValue);

            // Make sure the second setting is there.
            String secondValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME_1);
            assertEquals("Second setting must be present", FAKE_SETTING_VALUE_1, secondValue);

            // Make sure the third setting is there.
            String thirdValue = queryStringViaProviderApi(type, FAKE_SETTING_NAME_2);
            assertEquals("Third setting must be present", FAKE_SETTING_VALUE_2, thirdValue);
        } finally {
            // Clean up.
            deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
            deleteStringViaProviderApi(type, FAKE_SETTING_NAME_1);
            deleteStringViaProviderApi(type, FAKE_SETTING_NAME_2);
        }
    }

    private void doTestQueryUpdateDeleteGlobalViaProviderApiForType(int type) throws Exception {
        // Make sure it is not there.
        deleteStringViaProviderApi(type, FAKE_SETTING_NAME);

        // Now selection should return nothing.
        String value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
        assertNull("Setting should not be present.", value);

        // Insert the setting.
        Uri uri = insertStringViaProviderApi(type,
                FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
        Uri expectUri = Uri.withAppendedPath(getBaseUriForType(type), FAKE_SETTING_NAME);
        assertEquals("Did not get expected Uri.", expectUri, uri);

        // Now selection should return the setting.
        value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
        assertEquals("Setting should be present.", FAKE_SETTING_VALUE, value);

        // Update the setting.
        final int changeCount = updateStringViaProviderApiSetting(type,
                FAKE_SETTING_NAME, FAKE_SETTING_VALUE_1);
        assertEquals("Did not get expected change count.", 1, changeCount);

        // Now selection should return the new setting.
        value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
        assertEquals("Setting should be present.", FAKE_SETTING_VALUE_1, value);

        // Delete the setting.
        final int deletedCount = deleteStringViaProviderApi(type,
                FAKE_SETTING_NAME);
        assertEquals("Did not get expected deleted count", 1, deletedCount);

        // Now selection should return nothing.
        value = queryStringViaProviderApi(type, FAKE_SETTING_NAME);
        assertNull("Setting should not be present.", value);
    }

    private void performSetAndGetSettingTestViaFrontEndApi(int type, int userId)
            throws Exception {
        try {
            // Change the setting and assert a successful change.
            setSettingViaFrontEndApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE, userId);
        } finally {
            // Remove the setting.
            setStringViaFrontEndApiSetting(type, FAKE_SETTING_NAME, null, userId);
        }
    }

    private void performSetAndGetSettingTestViaProviderApi(int type)
            throws Exception {
        try {
            // Change the setting and assert a successful change.
            setSettingViaProviderApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME,
                    FAKE_SETTING_VALUE, true);
        } finally {
            // Remove the setting.
            setSettingViaProviderApiAndAssertSuccessfulChange(type, FAKE_SETTING_NAME, null,
                    true);
        }
    }

    private void setSettingViaFrontEndApiAndAssertSuccessfulChange(final int type,
            final String name, final String value, final int userId) throws Exception {
        setSettingAndAssertSuccessfulChange(() -> {
            setStringViaFrontEndApiSetting(type, name, value, userId);
        }, type, name, value, userId);
    }

    private void setSettingViaProviderApiAndAssertSuccessfulChange(final int type,
            final String name, final String value, final boolean withTableRowUri)
            throws Exception {
        setSettingAndAssertSuccessfulChange(() -> {
            insertStringViaProviderApi(type, name, value, withTableRowUri);
        }, type, name, value, UserHandle.USER_SYSTEM);
    }

    private void setSettingAndAssertSuccessfulChange(Runnable setCommand, final int type,
            final String name, final String value, final int userId) throws Exception {
        ContentResolver contentResolver = getContext().getContentResolver();

        final Uri settingUri = getBaseUriForType(type);

        final AtomicBoolean success = new AtomicBoolean();

        ContentObserver contentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
            public void onChange(boolean selfChange, Uri changeUri, int changeId) {
                Log.i(LOG_TAG, "onChange(" + selfChange + ", " + changeUri + ", " + changeId + ")");
                assertEquals("Wrong change Uri", changeUri, settingUri);
                assertEquals("Wrong user id", userId, changeId);
                String changeValue = getStringViaFrontEndApiSetting(type, name, userId);
                assertEquals("Wrong setting value", value, changeValue);

                success.set(true);

                synchronized (mLock) {
                    mLock.notifyAll();
                }
            }
        };

        contentResolver.registerContentObserver(settingUri, false, contentObserver, userId);

        try {
            setCommand.run();

            final long startTimeMillis = SystemClock.uptimeMillis();
            synchronized (mLock) {
                if (success.get()) {
                    return;
                }
                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                if (elapsedTimeMillis > WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS) {
                    fail("Could not change setting for "
                            + WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS + " ms");
                }
                final long remainingTimeMillis = WAIT_FOR_SETTING_URI_CHANGE_TIMEOUT_MILLIS
                        - elapsedTimeMillis;
                try {
                    mLock.wait(remainingTimeMillis);
                } catch (InterruptedException ie) {
                    /* ignore */
                }
            }
        } finally {
            contentResolver.unregisterContentObserver(contentObserver);
        }
    }

    private void queryAllSettingsViaProviderApiSettingAndAssertSettingPresent(int type,
            String name) {
        Uri uri = getBaseUriForType(type);

        Cursor cursor = getContext().getContentResolver().query(uri, NAME_VALUE_COLUMNS,
                null, null, null);

        if (cursor == null || !cursor.moveToFirst()) {
            fail("Nothing selected");
        }

        try {
            final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME);

            while (cursor.moveToNext()) {
                String currentName = cursor.getString(nameColumnIdx);
                if (name.equals(currentName)) {
                    return;
                }
            }

            fail("Not found setting: " + name);
        } finally {
            cursor.close();
        }
    }

    @Test
    public void testUpdateLocationProvidersAllowedLocked_enableProviders() throws Exception {
        setSettingViaFrontEndApiAndAssertSuccessfulChange(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_MODE,
                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
                UserHandle.USER_SYSTEM);

        // Enable one provider
        updateStringViaProviderApiSetting(
                SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+gps");

        assertEquals(
                "Wrong location providers",
                "gps",
                queryStringViaProviderApi(
                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));

        // Enable a list of providers, including the one that is already enabled
        updateStringViaProviderApiSetting(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                "+gps,+network,+network");

        assertEquals(
                "Wrong location providers",
                "gps,network",
                queryStringViaProviderApi(
                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
    }

    @Test
    public void testUpdateLocationProvidersAllowedLocked_disableProviders() throws Exception {
        setSettingViaFrontEndApiAndAssertSuccessfulChange(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_MODE,
                String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY),
                UserHandle.USER_SYSTEM);

        // Disable providers that were enabled
        updateStringViaProviderApiSetting(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                "-gps,-network");

        assertEquals(
                "Wrong location providers",
                "",
                queryStringViaProviderApi(
                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));

        // Disable a provider that was not enabled
        updateStringViaProviderApiSetting(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                "-test");

        assertEquals(
                "Wrong location providers",
                "",
                queryStringViaProviderApi(
                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
    }

    @Test
    public void testUpdateLocationProvidersAllowedLocked_enableAndDisable() throws Exception {
        setSettingViaFrontEndApiAndAssertSuccessfulChange(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_MODE,
                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
                UserHandle.USER_SYSTEM);

        updateStringViaProviderApiSetting(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                "+gps,+network,+test");
        updateStringViaProviderApiSetting(
                SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test");

        assertEquals(
                "Wrong location providers",
                "gps,network",
                queryStringViaProviderApi(
                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
    }

    @Test
    public void testUpdateLocationProvidersAllowedLocked_invalidInput() throws Exception {
        setSettingViaFrontEndApiAndAssertSuccessfulChange(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_MODE,
                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
                UserHandle.USER_SYSTEM);

        // update providers with a invalid string
        updateStringViaProviderApiSetting(
                SETTING_TYPE_SECURE,
                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                "+gps, invalid-string");

        // Verifies providers list does not change
        assertEquals(
                "Wrong location providers",
                "",
                queryStringViaProviderApi(
                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
    }
}
