/*
 * 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 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;

/**
 * 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();

    public void testSetAndGetGlobalViaFrontEndApiForOwnerUser() throws Exception {
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, UserHandle.USER_OWNER);
    }

    public void testSetAndGetGlobalViaFrontEndApiForNonOwnerUser() throws Exception {
        if (mSecondaryUserId == UserHandle.USER_OWNER) {
            Log.w(LOG_TAG, "No secondary user. Skipping "
                    + "testSetAndGetGlobalViaFrontEndApiForNonOwnerUser");
            return;
        }
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, mSecondaryUserId);
    }

    public void testSetAndGetSecureViaFrontEndApiForOwnerUser() throws Exception {
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, UserHandle.USER_OWNER);
    }

    public void testSetAndGetSecureViaFrontEndApiForNonOwnerUser() throws Exception {
        if (mSecondaryUserId == UserHandle.USER_OWNER) {
            Log.w(LOG_TAG, "No secondary user. Skipping "
                    + "testSetAndGetSecureViaFrontEndApiForNonOwnerUser");
            return;
        }
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, mSecondaryUserId);
    }

    public void testSetAndGetSystemViaFrontEndApiForOwnerUser() throws Exception {
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, UserHandle.USER_OWNER);
    }

    public void testSetAndGetSystemViaFrontEndApiForNonOwnerUser() throws Exception {
        if (mSecondaryUserId == UserHandle.USER_OWNER) {
            Log.w(LOG_TAG, "No secondary user. Skipping "
                    + "testSetAndGetSystemViaFrontEndApiForNonOwnerUser");
            return;
        }
        performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, mSecondaryUserId);
    }

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

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

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

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

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

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

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

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

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

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

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

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

    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) {
            for (; insertedCount >= 0; insertedCount--) {
                Log.w(LOG_TAG, "Removing app specific setting: " + insertedCount);
                deleteStringViaProviderApi(SETTING_TYPE_SYSTEM,
                        String.valueOf(insertedCount));
            }
        }
    }

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

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

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

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

    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(new Runnable() {
            @Override
            public void run() {
                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(new Runnable() {
            @Override
            public void run() {
                insertStringViaProviderApi(type, name, value, withTableRowUri);
            }
        }, type, name, value, UserHandle.USER_OWNER);
    }

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