/*
 * Copyright (C) 2021 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.net

import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE
import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_IGNORE
import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT
import android.net.ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
import android.net.ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_MOBILE
import android.net.ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_WIFI
import android.net.ConnectivitySettingsManager.DNS_RESOLVER_MAX_SAMPLES
import android.net.ConnectivitySettingsManager.DNS_RESOLVER_MIN_SAMPLES
import android.net.ConnectivitySettingsManager.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS
import android.net.ConnectivitySettingsManager.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT
import android.net.ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON
import android.net.ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT
import android.net.ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS
import android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE
import android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF
import android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC
import android.net.ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED
import android.net.ConnectivitySettingsManager.getCaptivePortalMode
import android.net.ConnectivitySettingsManager.getConnectivityKeepPendingIntentDuration
import android.net.ConnectivitySettingsManager.getDnsResolverSampleRanges
import android.net.ConnectivitySettingsManager.getDnsResolverSampleValidityDuration
import android.net.ConnectivitySettingsManager.getDnsResolverSuccessThresholdPercent
import android.net.ConnectivitySettingsManager.getMobileDataActivityTimeout
import android.net.ConnectivitySettingsManager.getMobileDataAlwaysOn
import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationMaximumDailyCount
import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationRateDuration
import android.net.ConnectivitySettingsManager.getPrivateDnsDefaultMode
import android.net.ConnectivitySettingsManager.getWifiAlwaysRequested
import android.net.ConnectivitySettingsManager.getWifiDataActivityTimeout
import android.net.ConnectivitySettingsManager.setCaptivePortalMode
import android.net.ConnectivitySettingsManager.setConnectivityKeepPendingIntentDuration
import android.net.ConnectivitySettingsManager.setDnsResolverSampleRanges
import android.net.ConnectivitySettingsManager.setDnsResolverSampleValidityDuration
import android.net.ConnectivitySettingsManager.setDnsResolverSuccessThresholdPercent
import android.net.ConnectivitySettingsManager.setMobileDataActivityTimeout
import android.net.ConnectivitySettingsManager.setMobileDataAlwaysOn
import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationMaximumDailyCount
import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationRateDuration
import android.net.ConnectivitySettingsManager.setPrivateDnsDefaultMode
import android.net.ConnectivitySettingsManager.setWifiAlwaysRequested
import android.net.ConnectivitySettingsManager.setWifiDataActivityTimeout
import android.os.Build
import android.platform.test.annotations.AppModeFull
import android.provider.Settings
import android.util.Range
import androidx.test.InstrumentationRegistry
import androidx.test.filters.SmallTest
import com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import junit.framework.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import java.time.Duration
import java.util.Objects
import kotlin.test.assertFailsWith

/**
 * Tests for [ConnectivitySettingsManager].
 *
 * Build, install and run with:
 * atest android.net.ConnectivitySettingsManagerTest
 */
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.R)
@SmallTest
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
class ConnectivitySettingsManagerTest {
    private val instrumentation = InstrumentationRegistry.getInstrumentation()
    private val context = instrumentation.context
    private val resolver = context.contentResolver

    private val defaultDuration = Duration.ofSeconds(0L)
    private val testTime1 = 5L
    private val testTime2 = 10L
    private val settingsTypeGlobal = "global"
    private val settingsTypeSecure = "secure"

    /*** Reset setting value or delete setting if the setting was not existed before testing. */
    private fun resetSettings(names: Array<String>, type: String, values: Array<String?>) {
        for (i in names.indices) {
            if (Objects.equals(values[i], null)) {
                instrumentation.uiAutomation.executeShellCommand(
                        "settings delete $type ${names[i]}")
            } else {
                if (settingsTypeSecure.equals(type)) {
                    Settings.Secure.putString(resolver, names[i], values[i])
                } else {
                    Settings.Global.putString(resolver, names[i], values[i])
                }
            }
        }
    }

    fun <T> testIntSetting(
        names: Array<String>,
        type: String,
        value1: T,
        value2: T,
        getter: () -> T,
        setter: (value: T) -> Unit,
        testIntValues: IntArray
    ) {
        val originals: Array<String?> = Array(names.size) { i ->
            if (settingsTypeSecure.equals(type)) {
                Settings.Secure.getString(resolver, names[i])
            } else {
                Settings.Global.getString(resolver, names[i])
            }
        }

        try {
            for (i in names.indices) {
                if (settingsTypeSecure.equals(type)) {
                    Settings.Secure.putString(resolver, names[i], testIntValues[i].toString())
                } else {
                    Settings.Global.putString(resolver, names[i], testIntValues[i].toString())
                }
            }
            assertEquals(value1, getter())

            setter(value2)
            assertEquals(value2, getter())
        } finally {
            resetSettings(names, type, originals)
        }
    }

    @Test
    fun testMobileDataActivityTimeout() {
        testIntSetting(names = arrayOf(DATA_ACTIVITY_TIMEOUT_MOBILE), type = settingsTypeGlobal,
                value1 = Duration.ofSeconds(testTime1), value2 = Duration.ofSeconds(testTime2),
                getter = { getMobileDataActivityTimeout(context, defaultDuration) },
                setter = { setMobileDataActivityTimeout(context, it) },
                testIntValues = intArrayOf(testTime1.toInt()))
    }

    @Test
    fun testWifiDataActivityTimeout() {
        testIntSetting(names = arrayOf(DATA_ACTIVITY_TIMEOUT_WIFI), type = settingsTypeGlobal,
                value1 = Duration.ofSeconds(testTime1), value2 = Duration.ofSeconds(testTime2),
                getter = { getWifiDataActivityTimeout(context, defaultDuration) },
                setter = { setWifiDataActivityTimeout(context, it) },
                testIntValues = intArrayOf(testTime1.toInt()))
    }

    @Test
    fun testDnsResolverSampleValidityDuration() {
        testIntSetting(names = arrayOf(DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS),
                type = settingsTypeGlobal, value1 = Duration.ofSeconds(testTime1),
                value2 = Duration.ofSeconds(testTime2),
                getter = { getDnsResolverSampleValidityDuration(context, defaultDuration) },
                setter = { setDnsResolverSampleValidityDuration(context, it) },
                testIntValues = intArrayOf(testTime1.toInt()))

        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setDnsResolverSampleValidityDuration(context, Duration.ofSeconds(-1L)) }
    }

    @Test
    fun testDnsResolverSuccessThresholdPercent() {
        testIntSetting(names = arrayOf(DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT),
                type = settingsTypeGlobal, value1 = 5, value2 = 10,
                getter = { getDnsResolverSuccessThresholdPercent(context, 0 /* def */) },
                setter = { setDnsResolverSuccessThresholdPercent(context, it) },
                testIntValues = intArrayOf(5))

        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setDnsResolverSuccessThresholdPercent(context, -1) }
        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setDnsResolverSuccessThresholdPercent(context, 120) }
    }

    @Test
    fun testDnsResolverSampleRanges() {
        testIntSetting(names = arrayOf(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_MAX_SAMPLES),
                type = settingsTypeGlobal, value1 = Range(1, 63), value2 = Range(2, 62),
                getter = { getDnsResolverSampleRanges(context) },
                setter = { setDnsResolverSampleRanges(context, it) },
                testIntValues = intArrayOf(1, 63))

        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setDnsResolverSampleRanges(context, Range(-1, 62)) }
        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setDnsResolverSampleRanges(context, Range(2, 65)) }
    }

    @Test
    fun testNetworkSwitchNotificationMaximumDailyCount() {
        testIntSetting(names = arrayOf(NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT),
                type = settingsTypeGlobal, value1 = 5, value2 = 15,
                getter = { getNetworkSwitchNotificationMaximumDailyCount(context, 0 /* def */) },
                setter = { setNetworkSwitchNotificationMaximumDailyCount(context, it) },
                testIntValues = intArrayOf(5))

        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setNetworkSwitchNotificationMaximumDailyCount(context, -1) }
    }

    @Test
    fun testNetworkSwitchNotificationRateDuration() {
        testIntSetting(names = arrayOf(NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS),
                type = settingsTypeGlobal, value1 = Duration.ofMillis(testTime1),
                value2 = Duration.ofMillis(testTime2),
                getter = { getNetworkSwitchNotificationRateDuration(context, defaultDuration) },
                setter = { setNetworkSwitchNotificationRateDuration(context, it) },
                testIntValues = intArrayOf(testTime1.toInt()))

        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setNetworkSwitchNotificationRateDuration(context, Duration.ofMillis(-1L)) }
    }

    @Test
    fun testCaptivePortalMode() {
        testIntSetting(names = arrayOf(CAPTIVE_PORTAL_MODE), type = settingsTypeGlobal,
                value1 = CAPTIVE_PORTAL_MODE_AVOID, value2 = CAPTIVE_PORTAL_MODE_PROMPT,
                getter = { getCaptivePortalMode(context, CAPTIVE_PORTAL_MODE_IGNORE) },
                setter = { setCaptivePortalMode(context, it) },
                testIntValues = intArrayOf(CAPTIVE_PORTAL_MODE_AVOID))

        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setCaptivePortalMode(context, 5 /* mode */) }
    }

    @Test
    fun testPrivateDnsDefaultMode() {
        val original = Settings.Global.getString(resolver, PRIVATE_DNS_DEFAULT_MODE)

        try {
            val mode = getPrivateDnsModeAsString(PRIVATE_DNS_MODE_OPPORTUNISTIC)
            Settings.Global.putString(resolver, PRIVATE_DNS_DEFAULT_MODE, mode)
            assertEquals(mode, getPrivateDnsDefaultMode(context))

            setPrivateDnsDefaultMode(context, PRIVATE_DNS_MODE_OFF)
            assertEquals(getPrivateDnsModeAsString(PRIVATE_DNS_MODE_OFF),
                    getPrivateDnsDefaultMode(context))
        } finally {
            resetSettings(names = arrayOf(PRIVATE_DNS_DEFAULT_MODE), type = settingsTypeGlobal,
                    values = arrayOf(original))
        }

        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setPrivateDnsDefaultMode(context, -1) }
    }

    @Test
    fun testConnectivityKeepPendingIntentDuration() {
        testIntSetting(names = arrayOf(CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS),
                type = settingsTypeSecure, value1 = Duration.ofMillis(testTime1),
                value2 = Duration.ofMillis(testTime2),
                getter = { getConnectivityKeepPendingIntentDuration(context, defaultDuration) },
                setter = { setConnectivityKeepPendingIntentDuration(context, it) },
                testIntValues = intArrayOf(testTime1.toInt()))

        assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
            setConnectivityKeepPendingIntentDuration(context, Duration.ofMillis(-1L)) }
    }

    @Test
    fun testMobileDataAlwaysOn() {
        testIntSetting(names = arrayOf(MOBILE_DATA_ALWAYS_ON), type = settingsTypeGlobal,
                value1 = false, value2 = true,
                getter = { getMobileDataAlwaysOn(context, true /* def */) },
                setter = { setMobileDataAlwaysOn(context, it) },
                testIntValues = intArrayOf(0))
    }

    @Test
    fun testWifiAlwaysRequested() {
        testIntSetting(names = arrayOf(WIFI_ALWAYS_REQUESTED), type = settingsTypeGlobal,
                value1 = false, value2 = true,
                getter = { getWifiAlwaysRequested(context, true /* def */) },
                setter = { setWifiAlwaysRequested(context, it) },
                testIntValues = intArrayOf(0))
    }
}