feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -0,0 +1,128 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
gradle.ext {
httpVersion = '2.3.4'
}
android {
namespace 'com.newsdk.ams.httpdns.demo'
compileSdk 34
defaultConfig {
applicationId "com.newsdk.ams.httpdns.demo"
minSdkVersion 26
targetSdkVersion 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildConfigField "String", "HTTPDNS_VERSION", "\"${gradle.httpVersion}\""
buildConfigField "String", "ACCOUNT_ID", "\"replace-with-your-accountId\""
buildConfigField "String", "SECRET_KEY", "\"replace-with-your-secret\""
buildConfigField "String", "AES_SECRET_KEY", "\"replace-with-your-aes-secret\""
buildConfigField "String", "SERVICE_URL", "\"\""
}
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
forTest {
// Keep this flavor to avoid BuildVariants errors when switching httpdns-sdk to end2end tests.
initWith release
debuggable true
}
}
variantFilter { variant ->
def names = variant.flavors*.name
def type = variant.buildType.name
// To check for a certain build type, use variant.buildType.name == "<buildType>"
if ((names.contains("normal") && type.contains("forTest"))
|| (names.contains("intl") && type.contains("forTest"))
|| (names.contains("end2end") && type.contains("release"))
|| (names.contains("end2end") && type.contains("debug"))
) {
// Gradle ignores any variants that satisfy the conditions above.
setIgnore(true)
}
}
testOptions {
unitTests {
all {
jvmArgs '-noverify'
systemProperty 'robolectric.logging.enable', true
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding = true
dataBinding = true
}
flavorDimensions += "version"
productFlavors {
normal {
}
intl {
}
end2end {
// Keep this flavor to avoid BuildVariants errors when switching httpdns-sdk to end2end tests.
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
implementation project(path: ':httpdns-sdk')
implementation('com.newsdk:fastjson:1.1.73.android@jar')
// implementation('com.emas.hybrid:emas-hybrid-android:1.1.0.4-public-SNAPSHOT') {
// exclude group: 'com.android.support', module: 'appcompat-v7'
// exclude group: 'com.taobao.android', module: 'thin_so_release'
// }
implementation 'com.newsdk.ams:new-android-tool:1.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

View File

@@ -0,0 +1,19 @@
## Project-wide Gradle settings.
#
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Sat Jun 11 21:37:51 CST 2016
org.gradle.jvmargs=-Xmx1536m
android.enableD8=true
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,22 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep class com.newsdk.sdk.android.httpdns.**{*;}

View File

@@ -0,0 +1,25 @@
package com.newsdk.ams.emas.demo
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.newsdk.ams.emas.demo", appContext.packageName)
}
}

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
<application
android:name="com.newsdk.ams.emas.demo.HttpDnsApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:extractNativeLibs="true"
android:theme="@style/Theme.NewHttpDnsDemo"
android:usesCleartextTraffic="true">
<activity
android:name="com.newsdk.ams.emas.demo.ui.practice.HttpDnsWebviewGetActivity"
android:exported="false"
android:theme="@style/Theme.NewHttpDnsDemo.NoActionBar">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity
android:name="com.newsdk.ams.emas.demo.ui.info.list.ListActivity"
android:exported="false"
android:theme="@style/Theme.NewHttpDnsDemo.NoActionBar">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity
android:name="com.newsdk.ams.emas.demo.MainActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity android:name="com.newsdk.ams.emas.demo.ui.info.SdnsGlobalSettingActivity"
android:exported="false"
android:theme="@style/Theme.NewHttpDnsDemo.NoActionBar" >
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,93 @@
package com.newsdk.ams.emas.demo
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
object BatchResolveCacheHolder {
var batchResolveV4List: MutableList<String> = mutableListOf()
var batchResolveV6List: MutableList<String> = mutableListOf()
var batchResolveBothList: MutableList<String> = mutableListOf()
var batchResolveAutoList: MutableList<String> = mutableListOf()
fun convertBatchResolveCacheData(cacheData: String?) {
if (cacheData == null) {
batchResolveBothList.add("www.baidu.com")
batchResolveBothList.add("m.baidu.com")
batchResolveBothList.add("demo.cloudxdr.com")
batchResolveBothList.add("www.taobao.com")
batchResolveBothList.add("www.163.com")
batchResolveBothList.add("www.sohu.com")
batchResolveBothList.add("www.sina.com.cn")
batchResolveBothList.add("www.douyin.com")
batchResolveBothList.add("www.qq.com")
batchResolveBothList.add("www.chinaamc.com")
batchResolveBothList.add("m.chinaamc.com")
return
}
try {
val jsonObject = JSONObject(cacheData)
val v4Array = jsonObject.optJSONArray("v4")
val v6Array = jsonObject.optJSONArray("v6")
val bothArray = jsonObject.optJSONArray("both")
val autoArray = jsonObject.optJSONArray("auto")
if (v4Array != null) {
var length = v4Array.length()
--length
while (length >= 0) {
batchResolveV4List.add(0, v4Array.getString(length))
--length
}
}
if (v6Array != null) {
var length = v6Array.length()
--length
while (length >= 0) {
batchResolveV6List.add(0, v6Array.getString(length))
--length
}
}
if (bothArray != null) {
var length = bothArray.length()
--length
while (length >= 0) {
batchResolveBothList.add(0, bothArray.getString(length))
--length
}
}
if (autoArray != null) {
var length = autoArray.length()
--length
while (length >= 0) {
batchResolveAutoList.add(0, autoArray.getString(length))
--length
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
fun convertBatchResolveString(): String {
val jsonObject = JSONObject()
val v4Array = JSONArray()
val v6Array = JSONArray()
val bothArray = JSONArray()
val autoArray = JSONArray()
for (host in batchResolveV4List) {
v4Array.put(host)
}
jsonObject.put("v4", v4Array)
for (host in batchResolveV6List) {
v6Array.put(host)
}
jsonObject.put("v6", v6Array)
for (host in batchResolveBothList) {
bothArray.put(host)
}
jsonObject.put("both", bothArray)
for (host in batchResolveAutoList) {
autoArray.put(host)
}
jsonObject.put("auto", autoArray)
return jsonObject.toString()
}
}

View File

@@ -0,0 +1,17 @@
package com.newsdk.ams.emas.demo
import android.app.Application
/**
* @author allen.wy
* @date 2023/5/24
*/
class HttpDnsApplication : Application() {
override fun onCreate() {
super.onCreate()
}
}

View File

@@ -0,0 +1,38 @@
package com.newsdk.ams.emas.demo
import android.content.Context
import android.text.TextUtils
import com.newsdk.ams.emas.demo.constant.KEY_ENABLE_AUTH_MODE
import com.newsdk.ams.emas.demo.constant.KEY_SECRET_KEY_SET_BY_CONFIG
import com.newsdk.sdk.android.httpdns.HttpDns
import com.newsdk.sdk.android.httpdns.HttpDnsService
import com.newsdk.ams.httpdns.demo.BuildConfig
/**
* @author allen.wy
* @date 2023/6/6
*/
object HttpDnsServiceHolder {
fun getHttpDnsService(context: Context) : HttpDnsService? {
val dnsService = if (!TextUtils.isEmpty(BuildConfig.ACCOUNT_ID)) {
val secretKeySetByConfig = getAccountPreference(context).getBoolean(KEY_SECRET_KEY_SET_BY_CONFIG, true)
if (secretKeySetByConfig) {
HttpDns.getService(BuildConfig.ACCOUNT_ID)
} else {
val authMode = getAccountPreference(context).getBoolean(KEY_ENABLE_AUTH_MODE, true)
if (authMode && !TextUtils.isEmpty(BuildConfig.SECRET_KEY)) HttpDns.getService(
context,
BuildConfig.ACCOUNT_ID, BuildConfig.SECRET_KEY
) else HttpDns.getService(
context,
BuildConfig.ACCOUNT_ID
)
}
} else null
return dnsService
}
}

View File

@@ -0,0 +1,52 @@
package com.newsdk.ams.emas.demo
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import com.newsdk.ams.httpdns.demo.R
import com.newsdk.ams.httpdns.demo.databinding.ActivityMainBinding
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
object HttpDns {
var inited = false
}
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_basic,
R.id.navigation_resolve,
R.id.navigation_best_practice,
R.id.navigation_information
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setOnItemSelectedListener {
if (HttpDns.inited) {
navController.navigate(it.itemId)
true
} else {
Toast.makeText(this, R.string.init_tip, Toast.LENGTH_SHORT).show()
false
}
}
}
}

View File

@@ -0,0 +1,93 @@
package com.newsdk.ams.emas.demo
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
object PreResolveCacheHolder {
var preResolveV4List: MutableList<String> = mutableListOf()
var preResolveV6List: MutableList<String> = mutableListOf()
var preResolveBothList: MutableList<String> = mutableListOf()
var preResolveAutoList: MutableList<String> = mutableListOf()
fun convertPreResolveCacheData(cacheData: String?) {
if (cacheData == null) {
return
}
try {
val jsonObject = JSONObject(cacheData)
val v4Array = jsonObject.optJSONArray("v4")
val v6Array = jsonObject.optJSONArray("v6")
val bothArray = jsonObject.optJSONArray("both")
val autoArray = jsonObject.optJSONArray("auto")
if (v4Array != null) {
var length = v4Array.length()
--length
while (length >= 0) {
preResolveV4List.add(0, v4Array.getString(length))
--length
}
}
if (v6Array != null) {
var length = v6Array.length()
--length
while (length >= 0) {
preResolveV6List.add(0, v6Array.getString(length))
--length
}
}
if (bothArray != null) {
var length = bothArray.length()
--length
while (length >= 0) {
preResolveBothList.add(0, bothArray.getString(length))
--length
}
}
if (autoArray != null) {
var length = autoArray.length()
--length
while (length >= 0) {
preResolveAutoList.add(0, autoArray.getString(length))
--length
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
fun convertPreResolveString(): String {
val jsonObject = JSONObject()
val v4Array = JSONArray()
val v6Array = JSONArray()
val bothArray = JSONArray()
val autoArray = JSONArray()
for (host in preResolveV4List) {
v4Array.put(host)
}
jsonObject.put("v4", v4Array)
for (host in preResolveV6List) {
v6Array.put(host)
}
jsonObject.put("v6", v6Array)
for (host in preResolveBothList) {
bothArray.put(host)
}
jsonObject.put("both", bothArray)
for (host in preResolveAutoList) {
autoArray.put(host)
}
jsonObject.put("auto", autoArray)
return jsonObject.toString()
}
}

View File

@@ -0,0 +1,48 @@
package com.newsdk.ams.emas.demo
import android.util.Log
import androidx.annotation.MainThread
import androidx.annotation.Nullable
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
/**
* @author allen.wy
* @date 2023/5/18
*/
class SingleLiveData<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w("SingleLiveData", "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(@Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
}

View File

@@ -0,0 +1,51 @@
package com.newsdk.ams.emas.demo
import com.newsdk.sdk.android.httpdns.CacheTtlChanger
import org.json.JSONException
import org.json.JSONObject
/**
* @author allen.wy
* @date 2023/6/6
*/
object TtlCacheHolder {
var ttlCache = mutableMapOf<String, Int>()
val cacheTtlChanger = CacheTtlChanger { host, _, ttl ->
if (ttlCache[host] != null) ttlCache[host]!! else ttl
}
fun convertTtlCacheData(cacheData: String?) {
if (cacheData == null) {
return
}
try {
val jsonObject = JSONObject(cacheData)
val it = jsonObject.keys()
while (it.hasNext()) {
val host = it.next()
ttlCache[host] = jsonObject.getInt(host)
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
fun MutableMap<String, Int>?.toJsonString(): String? {
if (this == null) {
return null
}
val jsonObject = JSONObject()
for (host in this.keys) {
try {
jsonObject.put(host, this[host])
} catch (e: JSONException) {
e.printStackTrace()
}
}
return jsonObject.toString()
}
}

View File

@@ -0,0 +1,141 @@
package com.newsdk.ams.emas.demo
import android.content.Context
import android.content.SharedPreferences
import com.newsdk.sdk.android.httpdns.ranking.IPRankingBean
import com.newsdk.ams.httpdns.demo.BuildConfig
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
import java.io.IOException
/**
* @author allen.wy
* @date 2023/6/5
*/
fun String?.toHostList(): MutableList<String>? {
if (this == null) {
return null
}
try {
val array = JSONArray(this)
val list = mutableListOf<String>()
for (i in 0 until array.length()) {
list.add(array.getString(i))
}
return list
} catch (e: JSONException) {
e.printStackTrace()
}
return null
}
fun String?.toTagList(): MutableList<String>? {
if (this == null) {
return null
}
try {
val array = JSONArray(this)
val list = mutableListOf<String>()
for (i in 0 until array.length()) {
list.add(array.getString(i))
}
return list
} catch (e: JSONException) {
e.printStackTrace()
}
return null
}
fun String?.toIPRankingList(): MutableList<IPRankingBean>? {
if (this == null) {
return null
}
try {
val jsonObject = JSONObject(this)
val list = mutableListOf<IPRankingBean>()
val it = jsonObject.keys()
while (it.hasNext()) {
val host = it.next()
list.add(
IPRankingBean(
host,
jsonObject.getInt(host)
)
)
}
return list
} catch (e: JSONException) {
e.printStackTrace()
}
return null
}
fun String?.toTtlCacheMap(): MutableMap<String, Int>? {
if (this == null) {
return null
}
try {
val jsonObject = JSONObject(this)
val map = mutableMapOf<String, Int>()
val it = jsonObject.keys()
while (it.hasNext()) {
val host = it.next()
val ttl = jsonObject.getInt(host)
map[host] = ttl
}
return map
} catch (e: JSONException) {
e.printStackTrace()
}
return null
}
fun String?.toBlackList(): MutableList<String>? {
if (this == null) {
return null
}
try {
val array = JSONArray(this)
val list = mutableListOf<String>()
for (i in 0 until array.length()) {
list.add(array.getString(i))
}
return list
} catch (e: JSONException) {
e.printStackTrace()
}
return null
}
fun getAccountPreference(context: Context): SharedPreferences {
return context.getSharedPreferences(
"New_httpdns_${BuildConfig.ACCOUNT_ID}",
Context.MODE_PRIVATE
)
}
fun convertPreResolveList(preResolveHostList: List<String>?): String? {
if (preResolveHostList == null) {
return null
}
val array = JSONArray()
for (host in preResolveHostList) {
array.put(host)
}
return array.toString()
}
@Throws(IOException::class)
fun readStringFrom(streamReader: BufferedReader): StringBuilder {
val sb = StringBuilder()
var line: String?
while (streamReader.readLine().also { line = it } != null) {
sb.append(line)
}
return sb
}

View File

@@ -0,0 +1,55 @@
package com.newsdk.ams.emas.demo.constant
/**
* @author allen.wy
* @date 2023/5/24
*/
const val KEY_ENABLE_AUTH_MODE = "enable_auth_mode"
const val KEY_SECRET_KEY_SET_BY_CONFIG = "secret_key_set_by_config"
const val KEY_ENABLE_ENCRYPT_MODE = "enable_encrypt_mode"
const val KEY_ENABLE_EXPIRED_IP = "enable_expired_ip"
const val KEY_ENABLE_CACHE_IP = "enable_cache_ip"
const val KEY_CACHE_EXPIRE_TIME = "cache_expire_time"
const val KEY_ENABLE_HTTPS = "enable_https"
const val KEY_ENABLE_DEGRADE = "enable_degrade"
const val KEY_ENABLE_AUTO_REFRESH = "enable_auto_refresh"
const val KEY_ENABLE_LOG = "enable_log";
const val KEY_REGION = "region";
const val KEY_TIMEOUT = "timeout"
const val KEY_IP_RANKING_ITEMS = "ip_ranking_items"
const val KEY_TTL_CHANGER = "ttl_changer"
const val KEY_TAGS = "tags"
const val KEY_HOST_WITH_FIXED_IP = "host_with_fixed_ip"
const val KEY_HOST_BLACK_LIST = "host_black_list"
const val KEY_ASYNC_RESOLVE = "async_resolve"
const val KEY_SDNS_RESOLVE = "sdns_resolve"
const val KEY_RESOLVE_IP_TYPE = "resolve_ip_type"
const val KEY_RESOLVE_METHOD = "resolve_method"
const val KEY_PRE_RESOLVE_HOST_LIST = "pre_resolve_host_list"
const val KEY_SDNS_GLOBAL_PARAMS = "sdns_global_params"
const val KEY_BATCH_RESOLVE_HOST_LIST = "batch_resolve_host_list"

View File

@@ -0,0 +1,139 @@
package com.newsdk.ams.emas.demo.net
import android.content.Context
import android.util.Log
import com.newsdk.ams.emas.demo.HttpDnsServiceHolder
import com.newsdk.ams.emas.demo.readStringFrom
import com.newsdk.ams.emas.demo.ui.resolve.Response
import com.newsdk.sdk.android.httpdns.HTTPDNSResult
import com.newsdk.sdk.android.httpdns.HttpDnsCallback
import com.newsdk.sdk.android.httpdns.NetType
import com.newsdk.sdk.android.httpdns.RequestIpType
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.CountDownLatch
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
/**
* @author allen.wy
* @date 2023/5/26
*/
class HttpURLConnectionRequest(private val context: Context, private val requestIpType: RequestIpType, private val resolveMethod: String,
private val isSdns: Boolean, private val sdnsParams: Map<String, String>?, private val cacheKey: String): IRequest {
override fun get(url: String): Response {
val conn: HttpURLConnection = getConnection(url)
val inputStream: InputStream?
val streamReader: BufferedReader?
return if (conn.responseCode != HttpURLConnection.HTTP_OK) {
inputStream = conn.errorStream
var errStr: String? = null
if (inputStream != null) {
streamReader = BufferedReader(InputStreamReader(inputStream, "UTF-8"))
errStr = readStringFrom(streamReader).toString()
}
throw Exception("Status Code : " + conn.responseCode + " Msg : " + errStr)
} else {
inputStream = conn.inputStream
streamReader = BufferedReader(InputStreamReader(inputStream, "UTF-8"))
val responseStr: String = readStringFrom(streamReader).toString()
Response(conn.responseCode, responseStr)
}
}
private fun getConnection(url: String): HttpURLConnection {
val host = URL(url).host
val dnsService = HttpDnsServiceHolder.getHttpDnsService(context)
var ipURL: String? = null
dnsService?.let {
//鏇挎崲涓烘渶鏂扮殑api
Log.d("HttpURLConnection", "start lookup $host via $resolveMethod")
var httpDnsResult = HTTPDNSResult("", null, null, null, false, false)
if (resolveMethod == "getHttpDnsResultForHostSync(String host, RequestIpType type)") {
httpDnsResult = if (isSdns) {
dnsService.getHttpDnsResultForHostSync(host, requestIpType, sdnsParams, cacheKey)
} else {
dnsService.getHttpDnsResultForHostSync(host, requestIpType)
}
} else if (resolveMethod == "getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback)") {
val lock = CountDownLatch(1)
if (isSdns) {
dnsService.getHttpDnsResultForHostAsync(host, requestIpType, sdnsParams, cacheKey, HttpDnsCallback {
httpDnsResult = it
lock.countDown()
})
} else {
dnsService.getHttpDnsResultForHostAsync(host, requestIpType, HttpDnsCallback {
httpDnsResult = it
lock.countDown()
})
}
lock.await()
} else if (resolveMethod == "getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type)") {
httpDnsResult = if (isSdns) {
dnsService.getHttpDnsResultForHostSyncNonBlocking(host, requestIpType, sdnsParams, cacheKey)
} else {
dnsService.getHttpDnsResultForHostSyncNonBlocking(host, requestIpType)
}
}
Log.d("HttpURLConnection", "httpdns $host 瑙f瀽缁撴灉 $httpDnsResult")
val ipStackType = HttpDnsNetworkDetector.getInstance().getNetType(context)
val isV6 = ipStackType == NetType.v6 || ipStackType == NetType.both
val isV4 = ipStackType == NetType.v4 || ipStackType == NetType.both
if (httpDnsResult.ipv6s != null && httpDnsResult.ipv6s.isNotEmpty() && isV6) {
ipURL = url.replace(host, "[" + httpDnsResult.ipv6s[0] + "]")
} else if (httpDnsResult.ips != null && httpDnsResult.ips.isNotEmpty() && isV4) {
ipURL = url.replace(host, httpDnsResult.ips[0])
}
}
val conn: HttpURLConnection = URL(ipURL ?: url).openConnection() as HttpURLConnection
conn.setRequestProperty("Host", host)
conn.connectTimeout = 30000
conn.readTimeout = 30000
conn.instanceFollowRedirects = false
if (conn is HttpsURLConnection) {
val sslSocketFactory = TLSSNISocketFactory(conn)
// SNI鍦烘櫙锛屽垱寤篠SLSocket
conn.sslSocketFactory = sslSocketFactory
// https鍦烘櫙锛岃瘉涔︽牎楠?
conn.hostnameVerifier = HostnameVerifier { _, session ->
val requestHost = conn.getRequestProperty("Host") ?:conn.getURL().host
HttpsURLConnection.getDefaultHostnameVerifier().verify(requestHost, session)
}
}
val responseCode = conn.responseCode
if (needRedirect(responseCode)) {
//涓存椂閲嶅畾鍚戝拰姘镐箙閲嶅畾鍚憀ocation鐨勫ぇ灏忓啓鏈夊尯鍒?
var location = conn.getHeaderField("Location")
if (location == null) {
location = conn.getHeaderField("location")
}
if (!(location!!.startsWith("http://") || location.startsWith("https://"))) {
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏rl
val originalUrl = URL(url)
location = (originalUrl.protocol + "://"
+ originalUrl.host + location)
}
return getConnection(location)
}
return conn
}
private fun needRedirect(code: Int): Boolean {
return code in 300..399
}
}

View File

@@ -0,0 +1,12 @@
package com.newsdk.ams.emas.demo.net
import com.newsdk.ams.emas.demo.ui.resolve.Response
/**
* @author allen.wy
* @date 2023/5/26
*/
interface IRequest {
fun get(url: String): Response
}

View File

@@ -0,0 +1,150 @@
package com.newsdk.ams.emas.demo.net
import android.content.Context
import android.util.Log
import com.newsdk.ams.emas.demo.HttpDnsServiceHolder
import com.newsdk.sdk.android.httpdns.HTTPDNSResult
import com.newsdk.sdk.android.httpdns.HttpDnsCallback
import com.newsdk.sdk.android.httpdns.NetType
import com.newsdk.sdk.android.httpdns.RequestIpType
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
import okhttp3.ConnectionPool
import okhttp3.Dns
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.lang.ref.WeakReference
import java.net.InetAddress
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
/**
* @author allen.wy
* @date 2023/6/14
*/
class OkHttpClientSingleton private constructor(context: Context
) {
private val mContext = WeakReference(context)
private var mRequestIpType = RequestIpType.v4
private var mResolveMethod: String = "getHttpDnsResultForHostSync(String host, RequestIpType type)"
private var mIsSdns: Boolean = false
private var mSdnsParams: Map<String, String>? = null
private var mCacheKey: String? = null
private val tag: String = "httpdns:hOkHttpClientSingleton"
companion object {
@Volatile
private var instance: OkHttpClientSingleton? = null
fun getInstance(context: Context): OkHttpClientSingleton {
if (instance != null) {
return instance!!
}
return synchronized(this) {
if (instance != null) {
instance!!
} else {
instance = OkHttpClientSingleton(context)
instance!!
}
}
}
}
fun updateConfig(requestIpType: RequestIpType, resolveMethod: String, isSdns: Boolean, params: Map<String, String>?, cacheKey: String): OkHttpClientSingleton {
mRequestIpType = requestIpType
mResolveMethod = resolveMethod
mIsSdns = isSdns
mSdnsParams = params
mCacheKey = cacheKey
return this
}
fun getOkHttpClient(): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor(OkHttpLog())
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
return OkHttpClient.Builder()
.connectionPool(ConnectionPool(0, 10 * 1000, TimeUnit.MICROSECONDS))
.hostnameVerifier { _, _ ->true }
.dns(object : Dns {
override fun lookup(hostname: String): List<InetAddress> {
Log.d(tag, "start lookup $hostname via $mResolveMethod")
val dnsService = HttpDnsServiceHolder.getHttpDnsService(mContext.get()!!)
//淇敼涓烘渶鏂扮殑閫氫織鏄撴噦鐨刟pi
var httpDnsResult: HTTPDNSResult? = null
val inetAddresses = mutableListOf<InetAddress>()
if (mResolveMethod == "getHttpDnsResultForHostSync(String host, RequestIpType type)") {
httpDnsResult = if (mIsSdns) {
dnsService?.getHttpDnsResultForHostSync(hostname, mRequestIpType, mSdnsParams, mCacheKey)
} else {
dnsService?.getHttpDnsResultForHostSync(hostname, mRequestIpType)
}
} else if (mResolveMethod == "getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback)") {
val lock = CountDownLatch(1)
if (mIsSdns) {
dnsService?.getHttpDnsResultForHostAsync(
hostname,
mRequestIpType,
mSdnsParams,
mCacheKey,
HttpDnsCallback {
httpDnsResult = it
lock.countDown()
})
} else {
dnsService?.getHttpDnsResultForHostAsync(
hostname,
mRequestIpType,
HttpDnsCallback {
httpDnsResult = it
lock.countDown()
})
}
lock.await()
} else if (mResolveMethod == "getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type)") {
httpDnsResult = if (mIsSdns) {
dnsService?.getHttpDnsResultForHostSyncNonBlocking(hostname, mRequestIpType, mSdnsParams, mCacheKey)
} else {
dnsService?.getHttpDnsResultForHostSyncNonBlocking(hostname, mRequestIpType)
}
}
Log.d(tag, "httpdns $hostname 瑙f瀽缁撴灉 $httpDnsResult")
httpDnsResult?.let { processDnsResult(it, inetAddresses) }
if (inetAddresses.isEmpty()) {
Log.d(tag, "httpdns 鏈繑鍥濱P锛岃蛋local dns")
return Dns.SYSTEM.lookup(hostname)
}
return inetAddresses
}
})
.addNetworkInterceptor(loggingInterceptor)
.build()
}
fun processDnsResult(httpDnsResult: HTTPDNSResult, inetAddresses: MutableList<InetAddress>) {
val ipStackType = HttpDnsNetworkDetector.getInstance().getNetType(mContext.get())
val isV6 = ipStackType == NetType.v6 || ipStackType == NetType.both
val isV4 = ipStackType == NetType.v4 || ipStackType == NetType.both
if (httpDnsResult.ipv6s != null && httpDnsResult.ipv6s.isNotEmpty() && isV6) {
for (i in httpDnsResult.ipv6s.indices) {
inetAddresses.addAll(
InetAddress.getAllByName(httpDnsResult.ipv6s[i]).toList()
)
}
} else if (httpDnsResult.ips != null && httpDnsResult.ips.isNotEmpty() && isV4) {
for (i in httpDnsResult.ips.indices) {
inetAddresses.addAll(
InetAddress.getAllByName(httpDnsResult.ips[i]).toList()
)
}
}
}
}

View File

@@ -0,0 +1,11 @@
package com.newsdk.ams.emas.demo.net
import android.util.Log
import okhttp3.logging.HttpLoggingInterceptor
class OkHttpLog: HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.d("Okhttp", message)
}
}

View File

@@ -0,0 +1,27 @@
package com.newsdk.ams.emas.demo.net
import android.content.Context
import com.newsdk.ams.emas.demo.ui.resolve.Response
import com.newsdk.sdk.android.httpdns.RequestIpType
/**
* @author allen.wy
* @date 2023/5/25
*/
class OkHttpRequest constructor(
private val context: Context,
private val requestIpType: RequestIpType,
private val resolveMethod: String,
private val mIsSdns: Boolean,
private val mSdnsParams: Map<String, String>?,
private val mCacheKey: String
) : IRequest {
override fun get(url: String): Response {
val request = okhttp3.Request.Builder().url(url).build()
OkHttpClientSingleton.getInstance(context).updateConfig(requestIpType, resolveMethod, mIsSdns, mSdnsParams, mCacheKey).getOkHttpClient().newCall(request).execute()
.use { response -> return Response(response.code, response.body?.string()) }
}
}

View File

@@ -0,0 +1,84 @@
package com.newsdk.ams.emas.demo.net
import android.net.SSLCertificateSocketFactory
import android.os.Build
import java.net.InetAddress
import java.net.Socket
import javax.net.ssl.*
/**
* @author allen.wy
* @date 2023/5/26
*/
class TLSSNISocketFactory(connection: HttpsURLConnection): SSLSocketFactory() {
private var mConnection: HttpsURLConnection
private var hostnameVerifier: HostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
init {
mConnection = connection
}
override fun createSocket(plainSocket: Socket?, host: String?, port: Int, autoClose: Boolean): Socket? {
var peerHost: String? = mConnection.getRequestProperty("Host")
if (peerHost == null) peerHost = host
val address = plainSocket!!.inetAddress
if (autoClose) {
// we don't need the plainSocket
plainSocket.close()
}
// create and connect SSL socket, but don't do hostname/certificate verification yet
// create and connect SSL socket, but don't do hostname/certificate verification yet
val sslSocketFactory =
SSLCertificateSocketFactory.getDefault(0) as SSLCertificateSocketFactory
val ssl = sslSocketFactory.createSocket(address, port) as SSLSocket
// enable TLSv1.1/1.2 if available
// enable TLSv1.1/1.2 if available
ssl.enabledProtocols = ssl.supportedProtocols
// set up SNI before the handshake
// set up SNI before the handshake
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
sslSocketFactory.setHostname(ssl, peerHost)
}
// verify hostname and certificate
// verify hostname and certificate
val session = ssl.session
if (!hostnameVerifier.verify(peerHost, session)) throw SSLPeerUnverifiedException(
"Cannot verify hostname: $peerHost"
)
return ssl
}
override fun createSocket(host: String?, port: Int): Socket? {
return null
}
override fun createSocket(host: String?, port: Int, inetAddress: InetAddress?, localPort: Int): Socket? {
return null
}
override fun createSocket(host: InetAddress?, port: Int): Socket? {
return null
}
override fun createSocket(host: InetAddress?, port: Int, localHost: InetAddress?, localPot: Int): Socket? {
return null
}
override fun getDefaultCipherSuites(): Array<String?> {
return arrayOfNulls(0)
}
override fun getSupportedCipherSuites(): Array<String?> {
return arrayOfNulls(0)
}
}

View File

@@ -0,0 +1,179 @@
package com.newsdk.ams.emas.demo.ui.basic
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatEditText
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.newsdk.ams.emas.demo.constant.KEY_REGION
import com.newsdk.ams.emas.demo.getAccountPreference
import com.newsdk.ams.emas.demo.ui.info.list.ListActivity
import com.newsdk.ams.emas.demo.ui.info.list.kListItemTag
import com.newsdk.ams.httpdns.demo.R
import com.newsdk.ams.httpdns.demo.databinding.FragmentBasicSettingBinding
class BasicSettingFragment : Fragment(), IBasicShowDialog {
private var _binding: FragmentBasicSettingBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: BasicSettingViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel =
ViewModelProvider(this,)[BasicSettingViewModel::class.java]
viewModel.showDialog = this
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBasicSettingBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
val root: View = binding.root
viewModel.initData()
binding.viewModel = viewModel
binding.jumpToAddTag.setOnClickListener {
val intent = Intent(activity, ListActivity::class.java)
intent.putExtra("list_type", kListItemTag)
startActivity(intent)
}
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun showSelectRegionDialog() {
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(R.string.select_region)
val china = getString(R.string.china)
val chinaHK = getString(R.string.china_hk)
val singapore = getString(R.string.singapore)
val germany = getString(R.string.germany)
val america = getString(R.string.america)
val pre = getString(R.string.pre)
val items = arrayOf(china, chinaHK, singapore, germany, america, pre)
var region = ""
val preferences = activity?.let { getAccountPreference(it) }
val index = when (preferences?.getString(KEY_REGION, "cn")) {
"hk" -> 1
"sg" -> 2
"de" -> 3
"us" -> 4
"pre" -> 5
else -> 0
}
setSingleChoiceItems(items, index) { _, which ->
region = when (which) {
1 -> "hk"
2 -> "sg"
3 -> "de"
4 -> "us"
5 -> "pre"
else -> "cn"
}
}
setPositiveButton(getString(R.string.confirm)) { dialog, _ ->
viewModel.saveRegion(region)
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
}
builder?.show()
}
override fun showSetTimeoutDialog() {
val input = LayoutInflater.from(activity).inflate(R.layout.dialog_input, null)
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
editText.hint = getString(R.string.timeout_hint)
editText.inputType = EditorInfo.TYPE_CLASS_NUMBER
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(getString(R.string.set_timeout))
setView(input)
setPositiveButton(R.string.confirm) { dialog, _ ->
when (val timeout = editText.text.toString()) {
"" -> Toast.makeText(activity, R.string.timeout_empty, Toast.LENGTH_SHORT)
.show()
else -> viewModel.saveTimeout(timeout.toInt())
}
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
show()
}
}
override fun showInputHostDialog() {
val input = LayoutInflater.from(activity).inflate(R.layout.dialog_input, null)
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
editText.hint = getString(R.string.clear_cache_hint)
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(getString(R.string.clear_host_cache))
setView(input)
setPositiveButton(R.string.confirm) { dialog, _ ->
viewModel.clearDnsCache(editText.text.toString())
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
show()
}
}
override fun showAddPreResolveDialog() {
val input = LayoutInflater.from(activity).inflate(R.layout.dialog_input, null)
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
editText.hint = getString(R.string.add_pre_resolve_hint)
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(getString(R.string.add_pre_resolve))
setView(input)
setPositiveButton(R.string.confirm) { dialog, _ ->
when (val host = editText.text.toString()) {
"" -> Toast.makeText(activity, R.string.pre_resolve_host_is_empty, Toast.LENGTH_SHORT)
.show()
else -> viewModel.addPreResolveDomain(host)
}
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
show()
}
}
override fun onHttpDnsInit() {
activity?.runOnUiThread(Runnable {
_binding?.initHttpdns?.setText(R.string.inited_httpdns)
_binding?.initHttpdns?.isClickable = false
})
}
}

View File

@@ -0,0 +1,369 @@
package com.newsdk.ams.emas.demo.ui.basic
import android.app.Application
import android.text.TextUtils
import android.util.Log
import android.widget.CompoundButton
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import com.newsdk.ams.emas.demo.*
import com.newsdk.ams.emas.demo.constant.*
import com.newsdk.sdk.android.httpdns.HttpDns
import com.newsdk.sdk.android.httpdns.HttpDnsService
import com.newsdk.sdk.android.httpdns.InitConfig
import com.newsdk.sdk.android.httpdns.NotUseHttpDnsFilter
import com.newsdk.sdk.android.httpdns.RequestIpType
import com.newsdk.sdk.android.httpdns.log.HttpDnsLog
import com.newsdk.ams.httpdns.demo.BuildConfig
import com.newsdk.ams.httpdns.demo.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
class BasicSettingViewModel(application: Application) : AndroidViewModel(application) {
companion object {
private const val DAY_MILLS = 24 * 60 * 60 * 1000
}
private val preferences = getAccountPreference(getApplication())
private var dnsService: HttpDnsService? = null
var secretKeySetByConfig = true
/**
* 鏄惁寮€鍚壌鏉冩ā寮?
*/
var enableAuthMode = true
/**
* 鏄惁寮€鍚姞瀵嗘ā寮?
*/
var enableEncryptMode = true
/**
* 鏄惁鍏佽杩囨湡IP
*/
var enableExpiredIP = false
/**
* 鏄惁寮€鍚湰鍦扮紦瀛?
*/
var enableCacheIP = false
/**
* 鏄惁鍏佽HTTPS
*/
var enableHttps = false
/**
* 鏄惁寮€鍚檷绾?
*/
var enableDegrade = false
/**
* 鏄惁鍏佽缃戠粶鍒囨崲鑷姩鍒锋柊
*/
var enableAutoRefresh = false
/**
* 鏄惁鍏佽鎵撳嵃鏃ュ織
*/
var enableLog = false
/**
* 褰撳墠Region
*/
var currentRegion = SingleLiveData<String>().apply {
value = ""
}
/**
* 褰撳墠瓒呮椂
*/
var currentTimeout = SingleLiveData<String>().apply {
value = "2000ms"
}
var cacheExpireTime = SingleLiveData<String>().apply {
value = "0"
}
var showDialog: IBasicShowDialog? = null
fun initData() {
secretKeySetByConfig = preferences.getBoolean(KEY_SECRET_KEY_SET_BY_CONFIG, true)
enableAuthMode = preferences.getBoolean(KEY_ENABLE_AUTH_MODE, true)
enableEncryptMode = preferences.getBoolean(KEY_ENABLE_ENCRYPT_MODE, true)
enableExpiredIP = preferences.getBoolean(KEY_ENABLE_EXPIRED_IP, false)
enableCacheIP = preferences.getBoolean(KEY_ENABLE_CACHE_IP, false)
cacheExpireTime.value = preferences.getString(KEY_CACHE_EXPIRE_TIME, "0")
enableHttps = preferences.getBoolean(KEY_ENABLE_HTTPS, false)
enableDegrade = preferences.getBoolean(KEY_ENABLE_DEGRADE, false)
enableAutoRefresh = preferences.getBoolean(KEY_ENABLE_AUTO_REFRESH, false)
enableLog = preferences.getBoolean(KEY_ENABLE_LOG, false)
when (preferences.getString(KEY_REGION, "cn")) {
"cn" -> currentRegion.value = getString(R.string.china)
"hk" -> currentRegion.value = getString(R.string.china_hk)
"sg" -> currentRegion.value = getString(R.string.singapore)
"de" -> currentRegion.value = getString(R.string.germany)
"us" -> currentRegion.value = getString(R.string.america)
"pre" -> currentRegion.value = getString(R.string.pre)
}
currentTimeout.value = "${preferences.getInt(KEY_TIMEOUT, 2000)}ms"
if (MainActivity.HttpDns.inited) {
dnsService = HttpDnsServiceHolder.getHttpDnsService(getApplication())
showDialog?.onHttpDnsInit()
}
}
fun toggleSecretKeySet(button: CompoundButton, checked: Boolean) {
secretKeySetByConfig = checked
val editor = preferences.edit()
editor.putBoolean(KEY_SECRET_KEY_SET_BY_CONFIG, checked)
editor.apply()
}
fun toggleAuthMode(button: CompoundButton, checked: Boolean) {
enableAuthMode = checked
val editor = preferences.edit()
editor.putBoolean(KEY_ENABLE_AUTH_MODE, checked)
editor.apply()
}
fun toggleEncryptMode(button: CompoundButton, checked: Boolean) {
enableEncryptMode = checked
val editor = preferences.edit()
editor.putBoolean(KEY_ENABLE_ENCRYPT_MODE, checked)
editor.apply()
}
fun toggleEnableExpiredIp(button: CompoundButton, checked: Boolean) {
enableExpiredIP = checked
val editor = preferences.edit()
editor.putBoolean(KEY_ENABLE_EXPIRED_IP, checked)
editor.apply()
}
fun toggleEnableCacheIp(button: CompoundButton, checked: Boolean) {
enableCacheIP = checked
val editor = preferences.edit()
editor.putBoolean(KEY_ENABLE_CACHE_IP, checked)
editor.apply()
}
fun toggleEnableHttps(button: CompoundButton, checked: Boolean) {
enableHttps = checked
val editor = preferences.edit()
editor.putBoolean(KEY_ENABLE_HTTPS, checked)
editor.apply()
}
fun toggleEnableDegrade(button: CompoundButton, checked: Boolean) {
enableDegrade = checked
val editor = preferences.edit()
editor.putBoolean(KEY_ENABLE_DEGRADE, checked)
editor.apply()
}
fun toggleEnableAutoRefresh(button: CompoundButton, checked: Boolean) {
enableAutoRefresh = checked
val editor = preferences.edit()
editor.putBoolean(KEY_ENABLE_AUTO_REFRESH, checked)
editor.apply()
}
fun toggleEnableLog(button: CompoundButton, checked: Boolean) {
enableLog = checked
val editor = preferences.edit()
editor.putBoolean(KEY_ENABLE_LOG, checked)
editor.apply()
HttpDnsLog.enable(checked)
}
fun setRegion() {
//寮圭獥閫夋嫨region
showDialog?.showSelectRegionDialog()
}
fun saveRegion(region: String) {
currentRegion.value = when (region) {
"cn" -> getString(R.string.china)
"hk" -> getString(R.string.china_hk)
"sg" -> getString(R.string.singapore)
"de" -> getString(R.string.germany)
"pre" -> getString(R.string.pre)
else -> getString(R.string.china)
}
val editor = preferences.edit()
editor.putString(KEY_REGION, region)
editor.apply()
dnsService?.setRegion(region)
}
fun setTimeout() {
showDialog?.showSetTimeoutDialog()
}
fun saveTimeout(timeout: Int) {
currentTimeout.value = "${timeout}ms"
val editor = preferences.edit()
editor.putInt(KEY_TIMEOUT, timeout)
editor.apply()
}
fun showClearCacheDialog() {
showDialog?.showInputHostDialog()
}
fun clearDnsCache(host: String) {
if (TextUtils.isEmpty(host)) {
dnsService?.cleanHostCache(null)
} else {
dnsService?.cleanHostCache(mutableListOf(host) as ArrayList<String>)
}
}
fun batchResolveHosts() {
dnsService?.setPreResolveHosts(BatchResolveCacheHolder.batchResolveV4List, RequestIpType.v4)
dnsService?.setPreResolveHosts(BatchResolveCacheHolder.batchResolveV6List, RequestIpType.v6)
dnsService?.setPreResolveHosts(BatchResolveCacheHolder.batchResolveAutoList, RequestIpType.auto)
dnsService?.setPreResolveHosts(BatchResolveCacheHolder.batchResolveBothList, RequestIpType.both)
}
fun showAddPreResolveDialog() {
showDialog?.showAddPreResolveDialog()
}
fun initHttpDns() {
if (!TextUtils.isEmpty(BuildConfig.ACCOUNT_ID)) {
CoroutineScope(Dispatchers.Default).launch {
withContext(Dispatchers.IO) {
val aesSecretKey = if (enableEncryptMode && !TextUtils.isEmpty(BuildConfig.AES_SECRET_KEY)) BuildConfig.AES_SECRET_KEY else ""
val secretKey = if (enableAuthMode && !TextUtils.isEmpty(BuildConfig.SECRET_KEY)) BuildConfig.SECRET_KEY else ""
val enableExpiredIp = preferences.getBoolean(KEY_ENABLE_EXPIRED_IP, false)
val enableCacheIp = preferences.getBoolean(KEY_ENABLE_CACHE_IP, false)
val enableHttpDns = preferences.getBoolean(KEY_ENABLE_HTTPS, false)
val serviceUrl = BuildConfig.SERVICE_URL.trim()
val timeout = preferences.getInt(KEY_TIMEOUT, 2000)
val region = preferences.getString(KEY_REGION, "cn") ?: "cn"
val enableDegradationLocalDns = preferences.getBoolean(KEY_ENABLE_DEGRADE, false);
//鑷畾涔塼tl
val ttlCacheStr = preferences.getString(KEY_TTL_CHANGER, null)
TtlCacheHolder.convertTtlCacheData(ttlCacheStr)
//IP鎺㈡祴
val ipRankingItemJson = preferences.getString(KEY_IP_RANKING_ITEMS, null)
//涓荤珯鍩熷悕
val hostListWithFixedIpJson =
preferences.getString(KEY_HOST_WITH_FIXED_IP, null)
val tagsJson = preferences.getString(KEY_TAGS, null)
//棰勮В鏋?
val preResolveHostList = preferences.getString(KEY_PRE_RESOLVE_HOST_LIST, null)
preResolveHostList?.let { Log.d("httpdns:HttpDnsApplication", "pre resolve list: $it") }
PreResolveCacheHolder.convertPreResolveCacheData(preResolveHostList)
//鎵归噺瑙f瀽
val batchResolveHostList = preferences.getString(KEY_BATCH_RESOLVE_HOST_LIST, null)
BatchResolveCacheHolder.convertBatchResolveCacheData(batchResolveHostList)
val sdnsGlobalParamStr = preferences.getString(KEY_SDNS_GLOBAL_PARAMS, "")
var sdnsGlobalParams: MutableMap<String, String>? = null
if (!TextUtils.isEmpty(sdnsGlobalParamStr)) {
try {
val sdnsJson = JSONObject(sdnsGlobalParamStr)
val keys = sdnsJson.keys()
sdnsGlobalParams = mutableMapOf()
while (keys.hasNext()) {
val key = keys.next()
sdnsGlobalParams[key] = sdnsJson.getString(key)
}
} catch (e: JSONException) {
}
}
val cacheExpireTimeTemp = cacheExpireTime.value?.toLong() ?: 0
HttpDnsLog.enable(preferences.getBoolean(KEY_ENABLE_LOG, false))
val builder = InitConfig.Builder()
.setEnableCacheIp(enableCacheIp, cacheExpireTimeTemp * DAY_MILLS)
.setEnableExpiredIp(enableExpiredIp)
.setTimeoutMillis(timeout)
.setEnableDegradationLocalDns(enableDegradationLocalDns)
.setIPRankingList(ipRankingItemJson.toIPRankingList())
.configCacheTtlChanger(TtlCacheHolder.cacheTtlChanger)
.configHostWithFixedIp(hostListWithFixedIpJson.toHostList())
.setNotUseHttpDnsFilter(NotUseHttpDnsFilter { host ->
val blackListStr = preferences.getString(KEY_HOST_BLACK_LIST, null)
blackListStr?.let {
return@NotUseHttpDnsFilter blackListStr.contains(host)
}
return@NotUseHttpDnsFilter false
})
.setSdnsGlobalParams(sdnsGlobalParams)
.setBizTags(tagsJson.toTagList())
.setAesSecretKey(aesSecretKey)
if (!TextUtils.isEmpty(serviceUrl)) {
// Prefer fixed service URL mode: https://host:port
builder.setServiceUrl(serviceUrl)
} else {
builder.setEnableHttps(enableHttpDns)
builder.setRegion(region)
}
if (secretKeySetByConfig) {
builder.setContext(this@BasicSettingViewModel.getApplication<HttpDnsApplication>())
builder.setSecretKey(secretKey)
}
HttpDns.init(BuildConfig.ACCOUNT_ID, builder.build())
dnsService = HttpDnsServiceHolder.getHttpDnsService(getApplication())
dnsService?.setPreResolveHosts(PreResolveCacheHolder.preResolveV4List)
dnsService?.setPreResolveHosts(PreResolveCacheHolder.preResolveV6List, RequestIpType.v6)
dnsService?.setPreResolveHosts(PreResolveCacheHolder.preResolveBothList, RequestIpType.both)
dnsService?.setPreResolveHosts(PreResolveCacheHolder.preResolveAutoList, RequestIpType.auto)
showDialog?.onHttpDnsInit()
MainActivity.HttpDns.inited = true
}
}
}
}
fun addPreResolveDomain(host: String) {
val preResolveHostListStr = preferences.getString(KEY_PRE_RESOLVE_HOST_LIST, null)
val hostList: MutableList<String> = if (preResolveHostListStr == null) {
mutableListOf()
} else {
preResolveHostListStr.toHostList()!!
}
if (hostList.contains(host)) {
Toast.makeText(
getApplication(),
getString(R.string.pre_resolve_host_duplicate, host),
Toast.LENGTH_SHORT
).show()
} else {
hostList.add(host)
}
val editor = preferences.edit()
editor.putString(KEY_PRE_RESOLVE_HOST_LIST, convertPreResolveList(hostList))
editor.apply()
}
private fun getString(resId: Int): String {
return getApplication<HttpDnsApplication>().getString(resId)
}
private fun getString(resId: Int, vararg args: String): String {
return getApplication<HttpDnsApplication>().getString(resId, *args)
}
}

View File

@@ -0,0 +1,18 @@
package com.newsdk.ams.emas.demo.ui.basic
/**
* @author allen.wy
* @date 2023/5/24
*/
interface IBasicShowDialog {
fun showSelectRegionDialog()
fun showSetTimeoutDialog()
fun showInputHostDialog()
fun showAddPreResolveDialog()
fun onHttpDnsInit()
}

View File

@@ -0,0 +1,95 @@
package com.newsdk.ams.emas.demo.ui.info
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.newsdk.ams.emas.demo.ui.info.list.*
import com.newsdk.ams.httpdns.demo.BuildConfig
import com.newsdk.ams.httpdns.demo.databinding.FragmentInfoBinding
class InfoFragment : Fragment() {
private var _binding: FragmentInfoBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: InfoViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[InfoViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentInfoBinding.inflate(inflater, container, false)
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.initData()
binding.infoPkgName.text = activity?.packageName
binding.infoSecretView.apply {
visibility = if (TextUtils.isEmpty(BuildConfig.SECRET_KEY)) View.GONE else View.VISIBLE
}
binding.jumpToPreResolve.setOnClickListener {
val intent = Intent(activity, ListActivity::class.java)
intent.putExtra("list_type", kListItemPreResolve)
startActivity(intent)
}
binding.jumpToIpRanking.setOnClickListener {
val intent = Intent(activity, ListActivity::class.java)
intent.putExtra("list_type", kListItemTypeIPRanking)
startActivity(intent)
}
binding.jumpToHostFiexIp.setOnClickListener {
val intent = Intent(activity, ListActivity::class.java)
intent.putExtra("list_type", kListItemTypeHostWithFixedIP)
startActivity(intent)
}
binding.jumpToHostBlackList.setOnClickListener {
val intent = Intent(activity, ListActivity::class.java)
intent.putExtra("list_type", kListItemTypeBlackList)
startActivity(intent)
}
binding.jumpToTtlCache.setOnClickListener {
val intent = Intent(activity, ListActivity::class.java)
intent.putExtra("list_type", kListItemTypeCacheTtl)
startActivity(intent)
}
binding.jumpToSdnsGlobalParams.setOnClickListener {
val intent = Intent(activity, SdnsGlobalSettingActivity::class.java)
startActivity(intent)
}
binding.jumpToBatchResolve.setOnClickListener {
val intent = Intent(activity, ListActivity::class.java)
intent.putExtra("list_type", kListItemBatchResolve)
startActivity(intent)
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -0,0 +1,63 @@
package com.newsdk.ams.emas.demo.ui.info
import android.app.Application
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import com.newsdk.ams.emas.demo.HttpDnsApplication
import com.newsdk.ams.emas.demo.HttpDnsServiceHolder
import com.newsdk.ams.emas.demo.SingleLiveData
import com.newsdk.ams.emas.demo.getAccountPreference
import com.newsdk.sdk.android.httpdns.NetType
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
import com.newsdk.ams.httpdns.demo.BuildConfig
import com.newsdk.ams.httpdns.demo.R
class InfoViewModel(application: Application) : AndroidViewModel(application) {
/**
* 璐︽埛ID
*/
val accountId = SingleLiveData<String>().apply {
value = ""
}
/**
* 璐︽埛secret
*/
val secretKey = SingleLiveData<String?>()
val currentIpStackType = SingleLiveData<String>().apply {
value = "V4"
}
fun initData() {
currentIpStackType.value = when (HttpDnsNetworkDetector.getInstance().getNetType(getApplication())) {
NetType.v4 -> "V4"
NetType.v6 -> "V6"
NetType.both -> "V4&V6"
else -> getApplication<HttpDnsApplication>().getString(R.string.unknown)
}
accountId.value = BuildConfig.ACCOUNT_ID
secretKey.value = BuildConfig.SECRET_KEY
}
fun clearDnsCache() {
val httpdnsService = HttpDnsServiceHolder.getHttpDnsService(getApplication())
var i = 0;
while (i < 500) {
httpdnsService?.cleanHostCache(null)
++i
}
}
fun clearAllCache() {
val preferences = getAccountPreference(getApplication())
preferences.edit().clear().apply()
Toast.makeText(getApplication(), R.string.all_cache_cleared, Toast.LENGTH_SHORT).show()
}
}

View File

@@ -0,0 +1,46 @@
package com.newsdk.ams.emas.demo.ui.info
import android.os.Bundle
import android.text.TextUtils
import androidx.appcompat.app.AppCompatActivity
import com.newsdk.ams.emas.demo.constant.KEY_SDNS_GLOBAL_PARAMS
import com.newsdk.ams.emas.demo.getAccountPreference
import com.newsdk.ams.httpdns.demo.R
import com.newsdk.ams.httpdns.demo.databinding.ActivitySdnsGlobalSettingBinding
import org.json.JSONException
import org.json.JSONObject
class SdnsGlobalSettingActivity: AppCompatActivity() {
private lateinit var binding: ActivitySdnsGlobalSettingBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val preferences = getAccountPreference(this)
binding = ActivitySdnsGlobalSettingBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.toolbar.title = getString(R.string.input_the_sdns_params)
val params = preferences.getString(KEY_SDNS_GLOBAL_PARAMS, "")
binding.sdnsParamsInputLayout.editText?.setText(params)
binding.toolbar.setNavigationOnClickListener {
val sdnsParamsStr = binding.sdnsParamsInputLayout.editText?.text.toString()
if (!TextUtils.isEmpty(sdnsParamsStr)) {
try {
val sdnsJson = JSONObject(sdnsParamsStr)
preferences.edit().putString(KEY_SDNS_GLOBAL_PARAMS, sdnsParamsStr).apply()
onBackPressed()
} catch (e: JSONException) {
binding.sdnsParamsInputLayout.error = getString(R.string.input_the_sdns_params_error)
}
} else {
preferences.edit().putString(KEY_SDNS_GLOBAL_PARAMS, "").apply()
onBackPressed()
}
}
}
}

View File

@@ -0,0 +1,353 @@
package com.newsdk.ams.emas.demo.ui.info.list
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatEditText
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.newsdk.ams.httpdns.demo.R
import com.newsdk.ams.httpdns.demo.databinding.ActivityListBinding
class ListActivity : AppCompatActivity(), ListAdapter.OnDeleteListener {
private lateinit var binding: ActivityListBinding
private val infoList: MutableList<ListItem> = mutableListOf()
private lateinit var listAdapter: ListAdapter
private var listType: Int = kListItemTypeIPRanking
private lateinit var viewModel: ListViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var title = ""
intent?.let {
listType = intent.getIntExtra("list_type", kListItemTypeIPRanking)
title = when (listType) {
kListItemTypeCacheTtl -> getString(R.string.ttl_cache_list)
kListItemTypeHostWithFixedIP -> getString(R.string.host_fixed_ip_list)
kListItemPreResolve -> getString(R.string.pre_resolve_list)
kListItemTypeBlackList -> getString(R.string.host_black_list)
kListItemBatchResolve -> getString(R.string.batch_resolve_list)
kListItemTag -> getString(R.string.add_tag)
else -> getString(R.string.ip_probe_list)
}
}
viewModel = ViewModelProvider(this)[ListViewModel::class.java]
binding = ActivityListBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.infoListToolbar.title = title
setSupportActionBar(binding.infoListToolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)//娣诲姞榛樿鐨勮繑鍥炲浘鏍?
supportActionBar?.setHomeButtonEnabled(true)
binding.infoListView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
viewModel.initData(listType, infoList)
listAdapter = ListAdapter(this, infoList, this)
binding.infoListView.adapter = listAdapter
binding.fab.setOnClickListener {
showAddDialog()
}
}
private fun showAddDialog() {
when (listType) {
kListItemTag -> {
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input, null)
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
editText.hint = getString(R.string.add_tag_hint)
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.add_tag))
.setView(input)
.setPositiveButton(R.string.confirm) { dialog, _ ->
when (val host = editText.text.toString()) {
"" -> Toast.makeText(
this@ListActivity,
R.string.host_fixed_ip_empty,
Toast.LENGTH_SHORT
).show()
else -> {
viewModel.toAddTag(host, listAdapter)
}
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.show()
}
kListItemTypeHostWithFixedIP -> {
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input, null)
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
editText.hint = getString(R.string.add_host_fixed_ip_hint)
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.add_host_fixed_ip))
.setView(input)
.setPositiveButton(R.string.confirm) { dialog, _ ->
when (val host = editText.text.toString()) {
"" -> Toast.makeText(
this@ListActivity,
R.string.host_fixed_ip_empty,
Toast.LENGTH_SHORT
).show()
else -> {
viewModel.toAddHostWithFixedIP(host, listAdapter)
}
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.show()
}
kListItemTypeBlackList -> {
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input, null)
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
editText.hint = getString(R.string.add_host_to_black_list_hint)
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.add_host_to_black_list))
.setView(input)
.setPositiveButton(R.string.confirm) { dialog, _ ->
when (val host = editText.text.toString()) {
"" -> Toast.makeText(
this@ListActivity,
R.string.host_to_black_list_empty,
Toast.LENGTH_SHORT
).show()
else -> {
viewModel.toAddHostInBlackList(host, listAdapter)
}
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.show()
}
kListItemPreResolve -> {
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input_3, null)
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
editText.hint = getString(R.string.add_pre_resolve_hint)
val ipTypeGroup = input.findViewById<RadioGroup>(R.id.ip_type)
var view = createIpTypeRadio(this)
view.text = "IPv4"
view.isChecked = true
view.tag = 0
ipTypeGroup.addView(view)
view = createIpTypeRadio(this)
view.text = "IPv6"
view.tag = 1
ipTypeGroup.addView(view)
view = createIpTypeRadio(this)
view.text = "IPv4&IPv6"
view.tag = 2
ipTypeGroup.addView(view)
view = createIpTypeRadio(this)
view.text = "鑷姩鍒ゆ柇IP绫诲瀷"
view.tag = 3
ipTypeGroup.addView(view)
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.add_pre_resolve))
.setView(input)
.setPositiveButton(R.string.confirm) { dialog, _ ->
when (val host = editText.text.toString()) {
"" -> Toast.makeText(
this@ListActivity,
R.string.pre_resolve_host_is_empty,
Toast.LENGTH_SHORT
).show()
else -> {
viewModel.toAddPreResolveHost(host, listAdapter, ipTypeGroup.findViewById<RadioButton>(ipTypeGroup.checkedRadioButtonId).tag as Int)
}
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.show()
}
kListItemBatchResolve -> {
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input_3, null)
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
editText.hint = getString(R.string.add_batch_resolve_hint)
val ipTypeGroup = input.findViewById<RadioGroup>(R.id.ip_type)
var view = createIpTypeRadio(this)
view.text = "IPv4"
view.isChecked = true
view.tag = 0
ipTypeGroup.addView(view)
view = createIpTypeRadio(this)
view.text = "IPv6"
view.tag = 1
ipTypeGroup.addView(view)
view = createIpTypeRadio(this)
view.text = "IPv4&IPv6"
view.tag = 2
ipTypeGroup.addView(view)
view = createIpTypeRadio(this)
view.text = "鑷姩鍒ゆ柇IP绫诲瀷"
view.tag = 3
ipTypeGroup.addView(view)
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.add_batch_resolve))
.setView(input)
.setPositiveButton(R.string.confirm) { dialog, _ ->
when (val host = editText.text.toString()) {
"" -> Toast.makeText(
this@ListActivity,
R.string.batch_resolve_host_is_empty,
Toast.LENGTH_SHORT
).show()
else -> {
viewModel.toAddBatchResolveHost(host, listAdapter, ipTypeGroup.findViewById<RadioButton>(ipTypeGroup.checkedRadioButtonId).tag as Int)
}
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.show()
}
else -> {
val isTtl = listType == kListItemTypeCacheTtl
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input_2, null)
val hostEditText = input.findViewById<AppCompatEditText>(R.id.input_content_1)
val intEditText = input.findViewById<AppCompatEditText>(R.id.input_content_2)
intEditText.inputType = EditorInfo.TYPE_CLASS_NUMBER
hostEditText.hint =
getString(if (isTtl) R.string.add_ttl_host_hint else R.string.add_ip_probe_host_hint)
intEditText.hint =
getString(if (isTtl) R.string.add_ttl_ttl_hint else R.string.add_ip_probe_port_hint)
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(if (isTtl) R.string.add_custom_ttl else R.string.add_ip_probe))
.setView(input)
.setPositiveButton(R.string.confirm) { dialog, _ ->
when (val host = hostEditText.text.toString()) {
"" -> Toast.makeText(
this@ListActivity,
R.string.host_is_empty,
Toast.LENGTH_SHORT
).show()
else -> {
when (val intValue = intEditText.text.toString()) {
"" -> Toast.makeText(
this@ListActivity,
if (isTtl) R.string.ttl_is_empty else R.string.port_is_empty,
Toast.LENGTH_SHORT
).show()
else -> {
try {
if (isTtl) {
viewModel.toSaveTtlCache(
host,
intValue.toInt(),
listAdapter
)
} else {
viewModel.toSaveIPProbe(
host,
intValue.toInt(),
listAdapter
)
}
} catch (e: NumberFormatException) {
Toast.makeText(
this@ListActivity,
R.string.ttl_is_not_number,
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.show()
}
}
}
fun createIpTypeRadio(context: Context): RadioButton {
val btn = RadioButton(context)
btn.id = View.generateViewId()
val params = RadioGroup.LayoutParams(RadioGroup.LayoutParams.MATCH_PARENT, RadioGroup.LayoutParams.WRAP_CONTENT)
btn.layoutParams = params
return btn
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onTagDeleted(position: Int) {
viewModel.onTagDeleted(position)
}
override fun onHostWithFixedIPDeleted(position: Int) {
//鍙兘閲嶅惎鐢熸晥
viewModel.onHostWithFixedIPDeleted(position)
}
override fun onIPRankingItemDeleted(position: Int) {
viewModel.onIPProbeItemDeleted(position)
}
override fun onTtlDeleted(host: String) {
viewModel.onTtlDeleted(host)
}
override fun onPreResolveDeleted(host: String, intValue: Int) {
Log.d("httpdns", "onPreResolveDeleted")
viewModel.onPreResolveDeleted(host, intValue)
}
override fun onHostBlackListDeleted(position: Int) {
viewModel.onHostBlackListDeleted(position)
}
override fun onBatchResolveDeleted(host: String, intValue: Int) {
viewModel.onBatchResolveDeleted(host, intValue)
}
}

View File

@@ -0,0 +1,151 @@
package com.newsdk.ams.emas.demo.ui.info.list
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.newsdk.ams.httpdns.demo.R
import com.newsdk.ams.httpdns.demo.databinding.InfoListItemBinding;
/**
* @author allen.wy
* @date 2023/6/5
*/
class ListAdapter(private val context: Context,
private val itemList: MutableList<ListItem>,
private val deleteListener: OnDeleteListener) :
RecyclerView.Adapter<ListAdapter.ListViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
val binding = InfoListItemBinding.inflate(LayoutInflater.from(context))
return ListViewHolder(context, binding)
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
if (itemList.isEmpty()) {
return
}
holder.setItemValue(itemList[position]) {
when (itemList[holder.adapterPosition].type) {
kListItemTag -> deleteListener.onTagDeleted(holder.adapterPosition)
kListItemTypeHostWithFixedIP -> deleteListener.onHostWithFixedIPDeleted(holder.adapterPosition)
kListItemTypeBlackList -> deleteListener.onHostBlackListDeleted(holder.adapterPosition)
kListItemTypeCacheTtl -> deleteListener.onTtlDeleted(itemList[holder.adapterPosition].content)
kListItemPreResolve -> deleteListener.onPreResolveDeleted(itemList[holder.adapterPosition].content, itemList[holder.adapterPosition].intValue)
kListItemBatchResolve -> deleteListener.onBatchResolveDeleted(itemList[holder.adapterPosition].content, itemList[holder.adapterPosition].intValue)
else -> deleteListener.onIPRankingItemDeleted(holder.adapterPosition)
}
itemList.removeAt(holder.adapterPosition)
notifyItemRemoved(holder.adapterPosition)
}
}
override fun getItemCount(): Int {
return itemList.size
}
fun addItemData(item: ListItem) {
itemList.add(item)
notifyItemInserted(itemList.size - 1)
}
fun getPositionByContent(content: String): Int {
for (index in itemList.indices) {
if (content == itemList[index].content) {
return index
}
}
return -1
}
fun updateItemByPosition(content:String, intValue: Int, position: Int) {
itemList[position].content = content
itemList[position].intValue = intValue
notifyItemChanged(position)
}
class ListViewHolder(private val context: Context, private val binding: InfoListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun setItemValue(listItem: ListItem, onDeleteListener: View.OnClickListener) {
when (listItem.type) {
kListItemTag -> {
binding.hostFixedIpContainer.visibility = View.VISIBLE
binding.hostAndPortOrTtlContainer.visibility = View.GONE
binding.preHostOrWithFixedIp.text = listItem.content
}
kListItemTypeIPRanking -> {
binding.hostFixedIpContainer.visibility = View.GONE
binding.hostAndPortOrTtlContainer.visibility = View.VISIBLE
binding.hostValue.text = listItem.content
binding.portOrTtlValue.text = listItem.intValue.toString()
binding.portOrTtlIndicate.text = context.getString(R.string.port)
}
kListItemTypeCacheTtl -> {
binding.hostFixedIpContainer.visibility = View.GONE
binding.hostAndPortOrTtlContainer.visibility = View.VISIBLE
binding.hostValue.text = listItem.content
binding.portOrTtlValue.text = listItem.intValue.toString()
binding.portOrTtlIndicate.text = context.getString(R.string.ttl)
}
kListItemTypeHostWithFixedIP -> {
binding.hostFixedIpContainer.visibility = View.VISIBLE
binding.hostAndPortOrTtlContainer.visibility = View.GONE
binding.preHostOrWithFixedIp.text = listItem.content
}
kListItemTypeBlackList -> {
binding.hostFixedIpContainer.visibility = View.VISIBLE
binding.hostAndPortOrTtlContainer.visibility = View.GONE
binding.preHostOrWithFixedIp.text = listItem.content
}
kListItemPreResolve -> {
binding.hostFixedIpContainer.visibility = View.GONE
binding.hostAndPortOrTtlContainer.visibility = View.VISIBLE
binding.hostValue.text = listItem.content
binding.portOrTtlValue.text = when (listItem.intValue) {
0 -> "IPv4"
1 -> "IPv6"
2 -> "IPv4&IPv6"
else -> "鑷姩鍒ゆ柇IP绫诲瀷"
}
binding.portOrTtlIndicate.text = context.getString(R.string.ip_type)
}
kListItemBatchResolve -> {
binding.hostFixedIpContainer.visibility = View.GONE
binding.hostAndPortOrTtlContainer.visibility = View.VISIBLE
binding.hostValue.text = listItem.content
binding.portOrTtlValue.text = when (listItem.intValue) {
0 -> "IPv4"
1 -> "IPv6"
2 -> "IPv4&IPv6"
else -> "鑷姩鍒ゆ柇IP绫诲瀷"
}
binding.portOrTtlIndicate.text = context.getString(R.string.ip_type)
}
}
binding.slideDeleteMenu.setOnClickListener(onDeleteListener)
binding.slideDeleteMenu2.setOnClickListener(onDeleteListener)
}
}
interface OnDeleteListener {
fun onTagDeleted(position: Int)
fun onHostWithFixedIPDeleted(position: Int)
fun onIPRankingItemDeleted(position: Int)
fun onTtlDeleted(host: String)
fun onPreResolveDeleted(host: String, intValue: Int)
fun onHostBlackListDeleted(position: Int)
fun onBatchResolveDeleted(host: String, intValue: Int)
}
}

View File

@@ -0,0 +1,9 @@
package com.newsdk.ams.emas.demo.ui.info.list
/**
* @author allen.wy
* @date 2023/6/5
*/
data class ListItem(var type: Int, var content: String, var intValue: Int)

View File

@@ -0,0 +1,21 @@
package com.newsdk.ams.emas.demo.ui.info.list
/**
* @author allen.wy
* @date 2023/6/5
*/
const val kListItemTypeIPRanking = 0x01
const val kListItemTypeCacheTtl = 0x02
const val kListItemTypeHostWithFixedIP = 0x03
const val kListItemPreResolve = 0x04
const val kListItemTypeBlackList = 0x05
const val kListItemBatchResolve = 0x06
const val kListItemTag = 0x07

View File

@@ -0,0 +1,407 @@
package com.newsdk.ams.emas.demo.ui.info.list
import android.app.Application
import android.content.SharedPreferences
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.newsdk.ams.emas.demo.*
import com.newsdk.ams.emas.demo.TtlCacheHolder.toJsonString
import com.newsdk.ams.emas.demo.constant.KEY_BATCH_RESOLVE_HOST_LIST
import com.newsdk.ams.emas.demo.constant.KEY_HOST_BLACK_LIST
import com.newsdk.ams.emas.demo.constant.KEY_HOST_WITH_FIXED_IP
import com.newsdk.ams.emas.demo.constant.KEY_IP_RANKING_ITEMS
import com.newsdk.ams.emas.demo.constant.KEY_PRE_RESOLVE_HOST_LIST
import com.newsdk.ams.emas.demo.constant.KEY_TAGS
import com.newsdk.ams.emas.demo.constant.KEY_TTL_CHANGER
import com.newsdk.sdk.android.httpdns.ranking.IPRankingBean
import com.newsdk.ams.httpdns.demo.R
import kotlinx.coroutines.launch
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
/**
* @author allen.wy
* @date 2023/6/6
*/
class ListViewModel(application: Application) : AndroidViewModel(application) {
private var hostFixedIpList: MutableList<String> = mutableListOf()
private var ipRankingList: MutableList<IPRankingBean> = mutableListOf()
private var hostBlackList: MutableList<String> = mutableListOf()
private var tagsList: MutableList<String> = mutableListOf()
private lateinit var preferences: SharedPreferences
fun initData(listType: Int, infoList: MutableList<ListItem>) {
preferences = getAccountPreference(getApplication())
viewModelScope.launch {
when (listType) {
kListItemTag -> {
val tagStr = preferences.getString(KEY_TAGS, null)
val list = tagStr.toTagList()
list?.let {
tagsList.addAll(list)
for (tag in tagsList) {
infoList.add(ListItem(kListItemTag, tag, 0))
}
}
}
kListItemTypeHostWithFixedIP -> {
val hostFixedIpStr = preferences.getString(KEY_HOST_WITH_FIXED_IP, null)
val list = hostFixedIpStr.toHostList()
list?.let {
hostFixedIpList.addAll(list)
for (host in hostFixedIpList) {
infoList.add(ListItem(kListItemTypeHostWithFixedIP, host, 0))
}
}
}
kListItemTypeBlackList -> {
val hostBlackListStr = preferences.getString(KEY_HOST_BLACK_LIST, null)
val list = hostBlackListStr.toBlackList()
list?.let {
hostBlackList.addAll(list)
for (host in hostBlackList) {
infoList.add(ListItem(kListItemTypeBlackList, host, 0))
}
}
}
kListItemTypeCacheTtl -> {
val ttlCacheStr = preferences.getString(KEY_TTL_CHANGER, null)
val map = ttlCacheStr.toTtlCacheMap()
map?.let {
TtlCacheHolder.ttlCache.putAll(map)
for ((host, ttl) in TtlCacheHolder.ttlCache) {
infoList.add(ListItem(kListItemTypeCacheTtl, host, ttl))
}
}
}
kListItemPreResolve -> {
for (host in PreResolveCacheHolder.preResolveV4List) {
infoList.add(ListItem(kListItemPreResolve, host, 0))
}
for (host in PreResolveCacheHolder.preResolveV6List) {
infoList.add(ListItem(kListItemPreResolve, host, 1))
}
for (host in PreResolveCacheHolder.preResolveBothList) {
infoList.add(ListItem(kListItemPreResolve, host, 2))
}
for (host in PreResolveCacheHolder.preResolveAutoList) {
infoList.add(ListItem(kListItemPreResolve, host, 3))
}
}
kListItemBatchResolve -> {
for (host in BatchResolveCacheHolder.batchResolveV4List) {
infoList.add(ListItem(kListItemBatchResolve, host, 0))
}
for (host in BatchResolveCacheHolder.batchResolveV6List) {
infoList.add(ListItem(kListItemBatchResolve, host, 1))
}
for (host in BatchResolveCacheHolder.batchResolveBothList) {
infoList.add(ListItem(kListItemBatchResolve, host, 2))
}
for (host in BatchResolveCacheHolder.batchResolveAutoList) {
infoList.add(ListItem(kListItemBatchResolve, host, 3))
}
}
else -> {
val ipRankingListStr = preferences.getString(KEY_IP_RANKING_ITEMS, null)
val rankingList = ipRankingListStr.toIPRankingList()
rankingList?.let {
ipRankingList.addAll(rankingList)
for (rankingItem in ipRankingList) {
infoList.add(
ListItem(
kListItemTypeIPRanking,
rankingItem.hostName,
rankingItem.port
)
)
}
}
}
}
}
}
fun toAddTag(tag: String, listAdapter: ListAdapter) {
tagsList.add(tag)
saveTags()
listAdapter.addItemData(
ListItem(
kListItemTag,
tag,
0
)
)
}
fun toAddHostWithFixedIP(host: String, listAdapter: ListAdapter) {
if (hostFixedIpList.contains(host)) {
Toast.makeText(
getApplication(),
getString(R.string.host_fixed_ip_duplicate, host),
Toast.LENGTH_SHORT
).show()
} else {
hostFixedIpList.add(host)
saveHostWithFixedIP()
listAdapter.addItemData(
ListItem(
kListItemTypeHostWithFixedIP,
host,
0
)
)
}
}
fun toAddHostInBlackList(host: String, listAdapter: ListAdapter) {
if (hostBlackList.contains(host)) {
Toast.makeText(
getApplication(),
getString(R.string.host_black_list_duplicate, host),
Toast.LENGTH_SHORT
).show()
} else {
hostBlackList.add(host)
saveHostInBlackList()
listAdapter.addItemData(
ListItem(
kListItemTypeBlackList,
host,
0
)
)
}
}
private fun saveTags() {
viewModelScope.launch {
val array = JSONArray()
for (tag in tagsList) {
array.put(tag)
}
val tagStr = array.toString()
val editor = preferences.edit()
editor.putString(KEY_TAGS, tagStr)
editor.apply()
}
}
private fun saveHostWithFixedIP() {
viewModelScope.launch {
val array = JSONArray()
for (host in hostFixedIpList) {
array.put(host)
}
val hostStr = array.toString()
val editor = preferences.edit()
editor.putString(KEY_HOST_WITH_FIXED_IP, hostStr)
editor.apply()
}
}
private fun saveHostInBlackList() {
viewModelScope.launch {
val array = JSONArray()
for (host in hostBlackList) {
array.put(host)
}
preferences.edit()
.putString(KEY_HOST_BLACK_LIST, array.toString())
.apply()
}
}
fun toSaveIPProbe(host: String, port: Int, listAdapter: ListAdapter) {
val ipProbeItem =
IPRankingBean(host, port)
if (ipRankingList.contains(ipProbeItem)) {
Toast.makeText(
getApplication(),
getString(R.string.ip_probe_item_duplicate, host, port.toString()),
Toast.LENGTH_SHORT
).show()
} else {
ipRankingList.add(ipProbeItem)
saveIPProbe()
listAdapter.addItemData(
ListItem(
kListItemTypeIPRanking,
host,
port
)
)
}
}
private fun saveIPProbe() {
viewModelScope.launch {
val jsonObject = JSONObject()
for (item in ipRankingList) {
try {
jsonObject.put(item.hostName, item.port)
} catch (e: JSONException) {
e.printStackTrace()
}
}
val ipProbeStr = jsonObject.toString()
val editor = preferences.edit()
editor.putString(KEY_IP_RANKING_ITEMS, ipProbeStr)
editor.apply()
}
}
fun toSaveTtlCache(host: String, ttl: Int, listAdapter: ListAdapter) {
viewModelScope.launch {
val editor = preferences.edit()
editor.putString(KEY_TTL_CHANGER, TtlCacheHolder.ttlCache.toJsonString())
editor.apply()
}
if (TtlCacheHolder.ttlCache.containsKey(host)) {
val position = listAdapter.getPositionByContent(host)
if (position != -1) {
listAdapter.updateItemByPosition(host, ttl, position)
}
} else {
listAdapter.addItemData(
ListItem(kListItemTypeCacheTtl, host, ttl)
)
}
TtlCacheHolder.ttlCache[host] = ttl
}
fun toAddPreResolveHost(host: String, listAdapter: ListAdapter, type: Int) {
val list: MutableList<String> = when (type) {
0 -> PreResolveCacheHolder.preResolveV4List
1 -> PreResolveCacheHolder.preResolveV6List
2 -> PreResolveCacheHolder.preResolveBothList
else -> PreResolveCacheHolder.preResolveAutoList
}
if (list.contains(host)) {
Toast.makeText(
getApplication(),
getString(R.string.pre_resolve_host_duplicate, host),
Toast.LENGTH_SHORT
).show()
} else {
list.add(host)
savePreResolveHost()
listAdapter.addItemData(
ListItem(
kListItemPreResolve,
host,
type
)
)
}
}
fun toAddBatchResolveHost(host: String, listAdapter: ListAdapter, type: Int) {
val list: MutableList<String> = when (type) {
0 -> BatchResolveCacheHolder.batchResolveV4List
1 -> BatchResolveCacheHolder.batchResolveV6List
2 -> BatchResolveCacheHolder.batchResolveBothList
else -> BatchResolveCacheHolder.batchResolveAutoList
}
if (list.contains(host)) {
Toast.makeText(
getApplication(),
getString(R.string.batch_resolve_host_duplicate, host),
Toast.LENGTH_SHORT
).show()
} else {
list.add(host)
saveBatchResolveHost()
listAdapter.addItemData(
ListItem(
kListItemBatchResolve,
host,
type
)
)
}
}
private fun savePreResolveHost() {
viewModelScope.launch {
val editor = preferences.edit()
editor.putString(KEY_PRE_RESOLVE_HOST_LIST, PreResolveCacheHolder.convertPreResolveString())
editor.apply()
}
}
private fun saveBatchResolveHost() {
viewModelScope.launch {
val editor = preferences.edit()
editor.putString(KEY_BATCH_RESOLVE_HOST_LIST, BatchResolveCacheHolder.convertBatchResolveString())
editor.apply()
}
}
fun onTagDeleted(position: Int) {
tagsList.removeAt(position)
saveTags()
}
fun onHostWithFixedIPDeleted(position: Int) {
//鍙兘閲嶅惎鐢熸晥
val deletedHost = hostFixedIpList.removeAt(position)
saveHostWithFixedIP()
}
fun onIPProbeItemDeleted(position: Int) {
ipRankingList.removeAt(position)
saveIPProbe()
}
fun onTtlDeleted(host: String) {
TtlCacheHolder.ttlCache.remove(host)
viewModelScope.launch {
val editor = preferences.edit()
editor.putString(KEY_TTL_CHANGER, TtlCacheHolder.ttlCache.toJsonString())
editor.apply()
}
}
fun onPreResolveDeleted(host: String, intValue: Int) {
val list = when (intValue) {
0 -> PreResolveCacheHolder.preResolveV4List
1 -> PreResolveCacheHolder.preResolveV6List
2 -> PreResolveCacheHolder.preResolveBothList
else -> PreResolveCacheHolder.preResolveAutoList
}
list.remove(host)
savePreResolveHost()
}
fun onBatchResolveDeleted(host: String, intValue: Int) {
val list = when (intValue) {
0 -> BatchResolveCacheHolder.batchResolveV4List
1 -> BatchResolveCacheHolder.batchResolveV6List
2 -> BatchResolveCacheHolder.batchResolveBothList
else -> BatchResolveCacheHolder.batchResolveAutoList
}
list.remove(host)
saveBatchResolveHost()
}
fun onHostBlackListDeleted(position: Int) {
hostBlackList.removeAt(position)
saveHostInBlackList()
}
private fun getString(resId: Int, vararg args: String): String {
return getApplication<HttpDnsApplication>().getString(resId, *args)
}
}

View File

@@ -0,0 +1,68 @@
package com.newsdk.ams.emas.demo.ui.practice
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.newsdk.ams.httpdns.demo.R
import com.newsdk.ams.httpdns.demo.databinding.FragmentBestPracticeBinding
/**
* @author allen.wy
* @date 2023/6/14
*/
class BestPracticeFragment : Fragment(), IBestPracticeShowDialog {
private var _binding: FragmentBestPracticeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBestPracticeBinding.inflate(inflater, container, false)
val viewModel = ViewModelProvider(this)[BestPracticeViewModel::class.java]
viewModel.showDialog = this
binding.viewModel = viewModel
binding.openHttpdnsWebview.setOnClickListener {
val intent = Intent(activity, HttpDnsWebviewGetActivity::class.java)
startActivity(intent)
}
// binding.openHttpdnsWebviewPost.setOnClickListener {
// val intent = Intent(activity, HttpDnsWVWebViewActivity::class.java)
// startActivity(intent)
// }
return binding.root
}
override fun showResponseDialog(message: String) {
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(R.string.sni_request)
setMessage(message)
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
}
builder?.show()
}
override fun showNoNetworkDialog() {
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(R.string.tips)
setMessage(R.string.network_not_connect)
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
}
builder?.show()
}
}

View File

@@ -0,0 +1,127 @@
package com.newsdk.ams.emas.demo.ui.practice
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.newsdk.ams.emas.demo.HttpDnsServiceHolder
import com.newsdk.ams.emas.demo.net.TLSSNISocketFactory
import com.newsdk.ams.emas.demo.readStringFrom
import com.newsdk.sdk.android.httpdns.NetType
import com.newsdk.sdk.android.httpdns.RequestIpType
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
import com.alibaba.sdk.android.tool.NetworkUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
/**
* @author allen.wy
* @date 2023/6/15
*/
class BestPracticeViewModel(application: Application) : AndroidViewModel(application) {
var showDialog: IBestPracticeShowDialog? = null
fun sniRequest() {
if (!NetworkUtils.isNetworkConnected(getApplication())) {
showDialog?.showNoNetworkDialog()
return
}
val testUrl = "https://suggest.taobao.com/sug?code=utf-8&q=phone"
viewModelScope.launch(Dispatchers.IO) {
recursiveRequest(testUrl) { message ->
withContext(Dispatchers.Main) {
showDialog?.showResponseDialog(
message
)
}
}
}
}
private suspend fun recursiveRequest(url: String, callback: suspend (message: String) -> Unit) {
val host = URL(url).host
var ipURL: String? = null
val dnsService = HttpDnsServiceHolder.getHttpDnsService(getApplication())
dnsService?.let {
val httpDnsResult = dnsService.getIpsByHostAsync(host, RequestIpType.both)
Log.d("httpdns", "$host 瑙f瀽缁撴灉 $httpDnsResult")
val ipStackType = HttpDnsNetworkDetector.getInstance().getNetType(getApplication())
val isV6 = ipStackType == NetType.v6 || ipStackType == NetType.both
val isV4 = ipStackType == NetType.v4 || ipStackType == NetType.both
if (httpDnsResult.ipv6s != null && httpDnsResult.ipv6s.isNotEmpty() && isV6) {
ipURL = url.replace(host, "[" + httpDnsResult.ipv6s[0] + "]")
} else if (httpDnsResult.ips != null && httpDnsResult.ips.isNotEmpty() && isV4) {
ipURL = url.replace(host, httpDnsResult.ips[0])
}
}
val conn: HttpsURLConnection =
URL(ipURL ?: url).openConnection() as HttpsURLConnection
conn.setRequestProperty("Host", host)
conn.connectTimeout = 30000
conn.readTimeout = 30000
conn.instanceFollowRedirects = false
//璁剧疆SNI
val sslSocketFactory = TLSSNISocketFactory(conn)
// SNI鍦烘櫙锛屽垱寤篠SLSocket
conn.sslSocketFactory = sslSocketFactory
conn.hostnameVerifier = HostnameVerifier { _, session ->
val requestHost = conn.getRequestProperty("Host") ?: conn.url.host
HttpsURLConnection.getDefaultHostnameVerifier().verify(requestHost, session)
}
val code = conn.responseCode
if (needRedirect(code)) {
//涓存椂閲嶅畾鍚戝拰姘镐箙閲嶅畾鍚憀ocation鐨勫ぇ灏忓啓鏈夊尯鍒?
var location = conn.getHeaderField("Location")
if (location == null) {
location = conn.getHeaderField("location")
}
if (!(location!!.startsWith("http://") || location.startsWith("https://"))) {
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏rl
val originalUrl = URL(url)
location = (originalUrl.protocol + "://"
+ originalUrl.host + location)
}
recursiveRequest(location, callback)
} else {
val inputStream: InputStream?
val streamReader: BufferedReader?
if (code != HttpURLConnection.HTTP_OK) {
inputStream = conn.errorStream
var errMsg: String? = null
if (inputStream != null) {
streamReader = BufferedReader(InputStreamReader(inputStream, "UTF-8"))
errMsg = readStringFrom(streamReader).toString()
}
Log.d("httpdns", "SNI request error: $errMsg")
callback("$code - $errMsg")
} else {
inputStream = conn.inputStream
streamReader = BufferedReader(InputStreamReader(inputStream, "UTF-8"))
val body: String = readStringFrom(streamReader).toString()
Log.d("httpdns", "SNI request response: $body")
callback("$code - $body")
}
}
}
private fun needRedirect(code: Int): Boolean {
return code in 300..399
}
}

View File

@@ -0,0 +1,199 @@
package com.newsdk.ams.emas.demo.ui.practice
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.MenuItem
import android.webkit.*
import androidx.appcompat.app.AppCompatActivity
import com.newsdk.ams.emas.demo.HttpDnsServiceHolder
import com.newsdk.ams.httpdns.demo.R
import com.newsdk.ams.httpdns.demo.databinding.ActivityHttpDnsWebviewBinding
import java.io.IOException
import java.net.*
import javax.net.ssl.*
class HttpDnsWebviewGetActivity : AppCompatActivity() {
private lateinit var binding: ActivityHttpDnsWebviewBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHttpDnsWebviewBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.webviewToolbar.title = getString(R.string.httpdns_webview_best_practice)
setSupportActionBar(binding.webviewToolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)//娣诲姞榛樿鐨勮繑鍥炲浘鏍?
supportActionBar?.setHomeButtonEnabled(true)
binding.httpdnsWebview.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
val url = request?.url.toString()
val schema = request?.url?.scheme?.trim()
val method = request?.method
if ("get" != method && "GET" != method) {
return super.shouldInterceptRequest(view, request)
}
schema?.let {
if (!schema.startsWith("https") && !schema.startsWith("http")) {
return super.shouldInterceptRequest(view, request)
}
val headers = request.requestHeaders
try {
val urlConnection = recursiveRequest(url, headers)
?: return super.shouldInterceptRequest(view, request)
val contentType = urlConnection.contentType
val mimeType = contentType?.split(";")?.get(0)
if (TextUtils.isEmpty(mimeType)) {
//鏃爉imeType寰楄姹備笉鎷︽埅
return super.shouldInterceptRequest(view, request)
}
val charset = getCharset(contentType)
val httpURLConnection = urlConnection as HttpURLConnection
val statusCode = httpURLConnection.responseCode
var response = httpURLConnection.responseMessage
val headerFields = httpURLConnection.headerFields
val isBinaryResource =
mimeType!!.startsWith("image") || mimeType.startsWith("audio") || mimeType.startsWith(
"video"
)
if (!TextUtils.isEmpty(charset) || isBinaryResource) {
val resourceResponse = WebResourceResponse(
mimeType,
charset,
httpURLConnection.inputStream
)
if (TextUtils.isEmpty(response)) {
response = "OK"
}
resourceResponse.setStatusCodeAndReasonPhrase(statusCode, response)
val responseHeader: MutableMap<String?, String> = HashMap()
for ((key) in headerFields) {
// HttpUrlConnection鍙兘鍖呭惈key涓簄ull鐨勬姤澶达紝鎸囧悜璇ttp璇锋眰鐘舵€佺爜
responseHeader[key] = httpURLConnection.getHeaderField(key)
}
resourceResponse.responseHeaders = responseHeader
return resourceResponse
} else {
return super.shouldInterceptRequest(view, request)
}
} catch (e: Exception) {
Log.e("httpdns", Log.getStackTraceString(e))
}
}
return super.shouldInterceptRequest(view, request)
}
}
binding.httpdnsWebview.loadUrl("https://demo.cloudxdr.com")
}
private fun getCharset(contentType: String?): String? {
if (contentType == null) {
return null
}
val fields = contentType.split(";")
if (fields.size <= 1) {
return null
}
var charset = fields[1]
if (!charset.contains("=")) {
return null
}
charset = charset.substring(charset.indexOf("=") + 1)
return charset
}
private fun recursiveRequest(path: String, headers: Map<String, String>?): URLConnection? {
try {
val url = URL(path)
val httpdnsService = HttpDnsServiceHolder.getHttpDnsService(this@HttpDnsWebviewGetActivity)
?: return null
val hostIP: String? = httpdnsService.getIpByHostAsync(url.host) ?: return null
val newUrl = if (hostIP == null) path else path.replaceFirst(url.host, hostIP)
val urlConnection: HttpURLConnection = URL(newUrl).openConnection() as HttpURLConnection
if (headers != null) {
for ((key, value) in headers) {
urlConnection.setRequestProperty(key, value)
}
}
urlConnection.setRequestProperty("Host", url.host)
urlConnection.connectTimeout = 30000
urlConnection.readTimeout = 30000
urlConnection.instanceFollowRedirects = false
if (urlConnection is HttpsURLConnection) {
val sniFactory = SNISocketFactory(urlConnection)
urlConnection.sslSocketFactory = sniFactory
urlConnection.hostnameVerifier = HostnameVerifier { _, session ->
var host: String? = urlConnection.getRequestProperty("Host")
if (null == host) {
host = urlConnection.getURL().host
}
HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session)
}
}
val responseCode = urlConnection.responseCode
if (responseCode in 300..399) {
if (containCookie(headers)) {
return null
}
var location: String? = urlConnection.getHeaderField("Location")
if (location == null) {
location = urlConnection.getHeaderField("location")
}
return if (location != null) {
if (!(location.startsWith("http://") || location.startsWith("https://"))) {
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏rl
val originalUrl = URL(path)
location = (originalUrl.protocol + "://" + originalUrl.host + location)
}
recursiveRequest(location, headers)
} else {
null
}
} else {
return urlConnection
}
} catch (e: MalformedURLException) {
Log.e("httpdns", Log.getStackTraceString(e))
} catch (e: IOException) {
Log.e("httpdns", Log.getStackTraceString(e))
}
return null
}
private fun containCookie(headers: Map<String, String>?): Boolean {
if (headers == null) {
return false
}
for ((key) in headers) {
if (key.contains("Cookie")) {
return true
}
}
return false
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
}

View File

@@ -0,0 +1,12 @@
package com.newsdk.ams.emas.demo.ui.practice
/**
* @author allen.wy
* @date 2023/6/15
*/
interface IBestPracticeShowDialog {
fun showResponseDialog( message: String)
fun showNoNetworkDialog()
}

View File

@@ -0,0 +1,82 @@
package com.newsdk.ams.emas.demo.ui.practice
import android.net.SSLCertificateSocketFactory
import java.net.InetAddress
import java.net.Socket
import javax.net.ssl.*
/**
* @author allen.wy
* @date 2023/6/14
*/
class SNISocketFactory(private val conn: HttpsURLConnection) : SSLSocketFactory() {
private val hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
override fun createSocket(
plainSocket: Socket?,
host: String?,
port: Int,
autoClose: Boolean
): Socket {
var peerHost: String? = conn.getRequestProperty("Host")
if (peerHost == null) {
peerHost = host
}
val address = plainSocket?.inetAddress
if (autoClose) {
plainSocket?.close()
}
val sslSocketFactory: SSLCertificateSocketFactory =
SSLCertificateSocketFactory.getDefault(0) as SSLCertificateSocketFactory
val ssl: SSLSocket =
sslSocketFactory.createSocket(address, port) as SSLSocket
ssl.enabledProtocols = ssl.supportedProtocols
// set up SNI before the handshake
sslSocketFactory.setHostname(ssl, peerHost)
// verify hostname and certificate
val session: SSLSession = ssl.session
if (!hostnameVerifier.verify(peerHost, session)
) throw SSLPeerUnverifiedException("Cannot verify hostname: $peerHost")
return ssl
}
override fun createSocket(host: String?, port: Int): Socket? {
return null
}
override fun createSocket(
host: String?,
port: Int,
localHost: InetAddress?,
localPort: Int
): Socket? {
return null
}
override fun createSocket(host: InetAddress?, port: Int): Socket? {
return null
}
override fun createSocket(
address: InetAddress?,
port: Int,
localAddress: InetAddress?,
localPort: Int
): Socket? {
return null
}
override fun getDefaultCipherSuites(): Array<String> {
return arrayOf()
}
override fun getSupportedCipherSuites(): Array<String> {
return arrayOf()
}
}

View File

@@ -0,0 +1,18 @@
package com.newsdk.ams.emas.demo.ui.resolve
/**
* @author allen.wy
* @date 2023/5/26
*/
interface IResolveShowDialog {
fun showSelectResolveIpTypeDialog()
fun showRequestResultDialog(response: Response)
fun showRequestFailedDialog(e: Throwable)
fun showResolveMethodDialog()
fun showRequestNumberDialog()
}

View File

@@ -0,0 +1,12 @@
package com.newsdk.ams.emas.demo.ui.resolve
/**
* @author allen.wy
* @date 2023/5/26
*/
enum class NetRequestType {
OKHTTP,
HTTP_URL_CONNECTION
}

View File

@@ -0,0 +1,233 @@
package com.newsdk.ams.emas.demo.ui.resolve
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.newsdk.ams.emas.demo.constant.KEY_RESOLVE_IP_TYPE
import com.newsdk.ams.emas.demo.constant.KEY_RESOLVE_METHOD
import com.newsdk.ams.emas.demo.getAccountPreference
import com.newsdk.ams.httpdns.demo.R
import com.newsdk.ams.httpdns.demo.databinding.FragmentResolveBinding
import org.json.JSONException
import org.json.JSONObject
class ResolveFragment : Fragment(), IResolveShowDialog {
private var _binding: FragmentResolveBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: ResolveViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[ResolveViewModel::class.java]
viewModel.showDialog = this
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentResolveBinding.inflate(inflater, container, false)
viewModel.initData()
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.sdnsParamsInputLayout.visibility = if (viewModel.isSdns.value!!) {
View.VISIBLE
} else {
View.GONE
}
binding.sdnsCacheKeyInputLayout.visibility = if (viewModel.isSdns.value!!) {
View.VISIBLE
} else {
View.GONE
}
binding.enableSdnsResolve.setOnCheckedChangeListener{_, isChecked ->
viewModel.toggleSdns(isChecked)
binding.sdnsParamsInputLayout.visibility = if (viewModel.isSdns.value!!) {
View.VISIBLE
} else {
View.GONE
}
binding.sdnsCacheKeyInputLayout.visibility = if (viewModel.isSdns.value!!) {
View.VISIBLE
} else {
View.GONE
}
}
binding.startResolve.setOnClickListener {
binding.resolveHostInputLayout.error = ""
//1. 鏍¢獙鍩熷悕鏄惁濉啓
val host = binding.resolveHostInputLayout.editText?.text.toString()
if (TextUtils.isEmpty(host)) {
binding.resolveHostInputLayout.error = getString(R.string.resolve_host_empty)
return@setOnClickListener
}
var sdnsParams: MutableMap<String, String>? = null
//2. 鏍¢獙sdns鍙傛暟
if (viewModel.isSdns.value!!) {
val sdnsParamsStr = binding.sdnsParamsInputLayout.editText?.text.toString()
if (!TextUtils.isEmpty(sdnsParamsStr)) {
try {
val sdnsJson = JSONObject(sdnsParamsStr)
val keys = sdnsJson.keys()
sdnsParams = HashMap()
while (keys.hasNext()) {
val key = keys.next()
sdnsParams[key] = sdnsJson.getString(key)
}
} catch (e: JSONException) {
binding.sdnsParamsInputLayout.error = getString(R.string.input_the_sdns_params_error)
}
}
}
var api = binding.requestApiInputLayout.editText?.text.toString()
val cacheKey = binding.sdnsCacheKeyInputLayout.editText?.text.toString()
if (!api.startsWith("/")) {
api = "/$api"
}
var index: Int = 0
do {
viewModel.startToResolve(host, api, sdnsParams, cacheKey)
++index
} while (index < viewModel.requestNum.value!!)
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun showSelectResolveIpTypeDialog() {
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(R.string.select_resolve_ip_type)
val items = arrayOf("IPv4", "IPv6", "IPv4&IPv6", getString(R.string.auto_get_ip_type))
val preferences = activity?.let { getAccountPreference(it) }
val index = when (preferences?.getString(KEY_RESOLVE_IP_TYPE, "IPv4")) {
"IPv4" -> 0
"IPv6" -> 1
"IPv4&IPv6" -> 2
else -> 3
}
var resolvedIpType = "IPv4"
setSingleChoiceItems(items, index) { _, which ->
resolvedIpType = when (which) {
0 -> "IPv4"
1 -> "IPv6"
2 -> "IPv4&IPv6"
else -> "Auto"
}
}
setPositiveButton(getString(R.string.confirm)) { dialog, _ ->
viewModel.saveResolveIpType(resolvedIpType)
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
}
builder?.show()
}
override fun showRequestResultDialog(response: Response) {
val code = response.code
val body = response.body
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(R.string.response_title)
val message = if (code == 200 && !TextUtils.isEmpty(body)) {
if (body!!.length <= 100) "$code - $body" else "$code - ${getString(R.string.body_large_see_log)}"
} else {
code.toString()
}
setMessage(message)
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
}
builder?.show()
}
override fun showRequestFailedDialog(e: Throwable) {
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(R.string.response_title)
setMessage(getString(R.string.request_exception, e.message))
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
}
builder?.show()
}
override fun showResolveMethodDialog() {
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(R.string.select_resolve_method)
val items = arrayOf("Sync", "Async", "Sync NonBlocking")
val preferences = activity?.let { getAccountPreference(it) }
var resolvedMethod = preferences?.getString(KEY_RESOLVE_METHOD, "getHttpDnsResultForHostSync(String host, RequestIpType type)").toString()
val index = when (resolvedMethod) {
"getHttpDnsResultForHostSync(String host, RequestIpType type)" -> 0
"getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback)" -> 1
"getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type)" -> 2
else -> 3
}
setSingleChoiceItems(items, index) { _, which ->
resolvedMethod = when (which) {
0 -> "getHttpDnsResultForHostSync(String host, RequestIpType type)"
1 -> "getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback)"
2 -> "getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type)"
else -> "getHttpDnsResultForHostSync(String host, RequestIpType type)"
}
}
setPositiveButton(getString(R.string.confirm)) { dialog, _ ->
viewModel.saveResolveMethod(resolvedMethod)
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
}
builder?.show()
}
override fun showRequestNumberDialog() {
val builder = activity?.let { act -> AlertDialog.Builder(act) }
builder?.apply {
setTitle(R.string.select_request_num)
val items = arrayOf("1", "2", "3", "4", "5")
val index = viewModel.requestNum.value!! - 1
var num = viewModel.requestNum.value
setSingleChoiceItems(items, index) { _, which ->
num = which + 1
}
setPositiveButton(getString(R.string.confirm)) { dialog, _ ->
viewModel.saveRequestNumber(num!!)
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
}
builder?.show()
}
}

View File

@@ -0,0 +1,153 @@
package com.newsdk.ams.emas.demo.ui.resolve
import android.app.Application
import android.util.Log
import android.widget.RadioGroup
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.newsdk.ams.emas.demo.HttpDnsApplication
import com.newsdk.ams.emas.demo.SingleLiveData
import com.newsdk.ams.emas.demo.constant.KEY_RESOLVE_IP_TYPE
import com.newsdk.ams.emas.demo.constant.KEY_RESOLVE_METHOD
import com.newsdk.ams.emas.demo.constant.KEY_SDNS_RESOLVE
import com.newsdk.ams.emas.demo.getAccountPreference
import com.newsdk.ams.emas.demo.net.HttpURLConnectionRequest
import com.newsdk.ams.emas.demo.net.OkHttpRequest
import com.newsdk.sdk.android.httpdns.RequestIpType
import com.newsdk.ams.httpdns.demo.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ResolveViewModel(application: Application) : AndroidViewModel(application) {
private val preferences = getAccountPreference(getApplication())
val currentIpType = SingleLiveData<String>().apply {
value = "IPv4"
}
val requestNum = SingleLiveData<Int>().apply {
value = 1
}
val currentResolveMethod = SingleLiveData<String>().apply {
value = "getHttpDnsResultForHostSync(String host, RequestIpType type)"
}
val isSdns = SingleLiveData<Boolean>().apply {
value = false
}
var showDialog:IResolveShowDialog? = null
private var requestType: NetRequestType = NetRequestType.OKHTTP
private var schemaType: SchemaType = SchemaType.HTTPS
fun initData() {
isSdns.value = preferences.getBoolean(KEY_SDNS_RESOLVE, false)
val ipType = preferences.getString(KEY_RESOLVE_IP_TYPE, "IPv4")
currentIpType.value = when(ipType) {
"Auto" -> getApplication<HttpDnsApplication>().getString(R.string.auto_get_ip_type)
else -> ipType
}
currentResolveMethod.value = preferences.getString(KEY_RESOLVE_METHOD, "getHttpDnsResultForHostSync(String host, RequestIpType type)")
requestNum.value = 1
}
fun onNetRequestTypeChanged(radioGroup: RadioGroup, id: Int) {
requestType = when(id) {
R.id.http_url_connection -> NetRequestType.HTTP_URL_CONNECTION
else -> NetRequestType.OKHTTP
}
}
fun toggleSdns(checked: Boolean) {
isSdns.value = checked
viewModelScope.launch {
val editor = preferences.edit()
editor.putBoolean(KEY_SDNS_RESOLVE, checked)
editor.apply()
}
}
fun onSchemaTypeChanged(radioGroup: RadioGroup, id: Int) {
schemaType = when(id) {
R.id.schema_http -> SchemaType.HTTP
else -> SchemaType.HTTPS
}
}
fun setResolveIpType() {
showDialog?.showSelectResolveIpTypeDialog()
}
fun setResolveMethod() {
showDialog?.showResolveMethodDialog()
}
fun setRequestNumber() {
showDialog?.showRequestNumberDialog()
}
fun saveResolveIpType(ipType: String) {
viewModelScope.launch {
val editor = preferences.edit()
editor.putString(KEY_RESOLVE_IP_TYPE, ipType)
editor.apply()
}
currentIpType.value = when (ipType) {
"Auto" -> getApplication<HttpDnsApplication>().getString(R.string.auto_get_ip_type)
else -> ipType
}
}
fun saveResolveMethod(resolveMethod: String) {
viewModelScope.launch {
val editor = preferences.edit()
editor.putString(KEY_RESOLVE_METHOD, resolveMethod)
editor.apply()
}
currentResolveMethod.value = resolveMethod
}
fun saveRequestNumber(num: Int) {
requestNum.value = num
}
fun startToResolve(host: String, api: String, sdnsParams: Map<String, String>?, cacheKey: String) {
val requestUrl = if (schemaType == SchemaType.HTTPS) "https://$host$api" else "http://$host$api"
val requestIpType = when (currentIpType.value) {
"IPv4" -> RequestIpType.v4
"IPv6" -> RequestIpType.v6
"IPv4&IPv6" -> RequestIpType.both
else -> RequestIpType.auto
}
Log.d("httpdns", "api: ${currentResolveMethod.value}, " + "requestIp: $requestIpType")
val requestClient = if (requestType == NetRequestType.OKHTTP) OkHttpRequest(getApplication(), requestIpType,
currentResolveMethod.value!!, isSdns.value!!, sdnsParams, cacheKey
) else HttpURLConnectionRequest(getApplication(), requestIpType, currentResolveMethod.value!!, isSdns.value!!, sdnsParams, cacheKey)
viewModelScope.launch(Dispatchers.IO) {
try {
Log.d("httpdns", "before request $requestUrl");
val response = requestClient.get(requestUrl)
withContext(Dispatchers.Main) {
showDialog?.showRequestResultDialog(response)
}
} catch (e: Exception) {
Log.e("httpdns", Log.getStackTraceString(e))
withContext(Dispatchers.Main) {
showDialog?.showRequestFailedDialog(e)
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
package com.newsdk.ams.emas.demo.ui.resolve
/**
* @author allen.wy
* @date 2023/6/14
*/
data class Response(val code: Int, val body: String?)

View File

@@ -0,0 +1,12 @@
package com.newsdk.ams.emas.demo.ui.resolve
/**
* @author allen.wy
* @date 2023/6/7
*/
enum class SchemaType {
HTTPS,
HTTP
}

View File

@@ -0,0 +1,364 @@
package com.newsdk.ams.emas.demo.widget
import android.content.Context
import android.graphics.PointF
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.Scroller
import com.newsdk.ams.httpdns.demo.R
import java.lang.ref.WeakReference
import kotlin.math.abs
/**
* @author allen.wy
* @date 2023/6/5
*/
class SwipeLayout(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
ViewGroup(context, attrs, defStyleAttr) {
private val mMatchParentChildren = mutableListOf<View>()
private var menuViewResId = 0
private var contentViewResId = 0
private var menuView: View? = null
private var contentView: View? = null
private var contentViewLayoutParam: MarginLayoutParams? = null
private var isSwiping = false
private var lastP: PointF? = null
private var firstP: PointF? = null
private var fraction = 0.2f
private var scaledTouchSlop = 0
private var scroller: Scroller? = null
private var finalDistanceX = 0f
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
/**
* 鍒濆鍖栨柟娉?
*
* @param context
* @param attrs
* @param defStyleAttr
*/
private fun init(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
//鍒涘缓杈呭姪瀵硅薄
val viewConfiguration = ViewConfiguration.get(context)
scaledTouchSlop = viewConfiguration.scaledTouchSlop
scroller = Scroller(context)
//1銆佽幏鍙栭厤缃殑灞炴€у€?
val typedArray = context.theme
.obtainStyledAttributes(attrs, R.styleable.SwipeLayout, defStyleAttr, 0)
try {
val indexCount: Int = typedArray.indexCount
for (i in 0 until indexCount) {
when (typedArray.getIndex(i)) {
R.styleable.SwipeLayout_menuView -> {
menuViewResId =
typedArray.getResourceId(R.styleable.SwipeLayout_menuView, -1)
}
R.styleable.SwipeLayout_contentView -> {
contentViewResId =
typedArray.getResourceId(R.styleable.SwipeLayout_contentView, -1)
}
R.styleable.SwipeLayout_fraction -> {
fraction = typedArray.getFloat(R.styleable.SwipeLayout_fraction, 0.5f)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
typedArray.recycle()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//鑾峰彇childView鐨勪釜鏁?
isClickable = true
var count = childCount
val measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY
mMatchParentChildren.clear()
var maxHeight = 0
var maxWidth = 0
var childState = 0
for (i in 0 until count) {
val child: View = getChildAt(i)
if (child.visibility != View.GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
val lp = child.layoutParams as MarginLayoutParams
maxWidth =
maxWidth.coerceAtLeast(child.measuredWidth + lp.leftMargin + lp.rightMargin)
maxHeight =
maxHeight.coerceAtLeast(child.measuredHeight + lp.topMargin + lp.bottomMargin)
childState = combineMeasuredStates(childState, child.measuredState)
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT
) {
mMatchParentChildren.add(child)
}
}
}
}
// Check against our minimum height and width
maxHeight = maxHeight.coerceAtLeast(suggestedMinimumHeight)
maxWidth = maxWidth.coerceAtLeast(suggestedMinimumWidth)
setMeasuredDimension(
resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(
maxHeight, heightMeasureSpec,
childState shl MEASURED_HEIGHT_STATE_SHIFT
)
)
count = mMatchParentChildren.size
if (count < 1) {
return
}
for (i in 0 until count) {
val child: View = mMatchParentChildren[i]
val lp = child.layoutParams as MarginLayoutParams
val childWidthMeasureSpec = if (lp.width == LayoutParams.MATCH_PARENT) {
val width = 0.coerceAtLeast(
measuredWidth - lp.leftMargin - lp.rightMargin
)
MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY
)
} else {
getChildMeasureSpec(
widthMeasureSpec,
lp.leftMargin + lp.rightMargin,
lp.width
)
}
val childHeightMeasureSpec = if (lp.height == FrameLayout.LayoutParams.MATCH_PARENT) {
val height = 0.coerceAtLeast(
measuredHeight - lp.topMargin - lp.bottomMargin
)
MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY
)
} else {
getChildMeasureSpec(
heightMeasureSpec,
lp.topMargin + lp.bottomMargin,
lp.height
)
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
}
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val count = childCount
val left = 0 + paddingLeft
val top = 0 + paddingTop
for (i in 0 until count) {
val child: View = getChildAt(i)
if (menuView == null && child.id == menuViewResId) {
menuView = child
menuView!!.isClickable = true
} else if (contentView == null && child.id == contentViewResId) {
contentView = child
contentView!!.isClickable = true
}
}
//甯冨眬contentView
val cRight: Int
if (contentView != null) {
contentViewLayoutParam = contentView!!.layoutParams as MarginLayoutParams?
val cTop = top + contentViewLayoutParam!!.topMargin
val cLeft = left + contentViewLayoutParam!!.leftMargin
cRight = left + contentViewLayoutParam!!.leftMargin + contentView!!.measuredWidth
val cBottom: Int = cTop + contentView!!.measuredHeight
contentView!!.layout(cLeft, cTop, cRight, cBottom)
}
if (menuView != null) {
val rightViewLp = menuView!!.layoutParams as MarginLayoutParams
val lTop = top + rightViewLp.topMargin
val lLeft =
contentView!!.right + contentViewLayoutParam!!.rightMargin + rightViewLp.leftMargin
val lRight: Int = lLeft + menuView!!.measuredWidth
val lBottom: Int = lTop + menuView!!.measuredHeight
menuView!!.layout(lLeft, lTop, lRight, lBottom)
}
}
private var result: State? = null
init {
init(context, attrs, defStyleAttr)
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
isSwiping = false
if (lastP == null) {
lastP = PointF()
}
lastP!!.set(ev.rawX, ev.rawY)
if (firstP == null) {
firstP = PointF()
}
firstP!!.set(ev.rawX, ev.rawY)
if (viewCache != null) {
if (viewCache!!.get() != this) {
viewCache!!.get()!!.handlerSwipeMenu(State.CLOSE)
}
parent.requestDisallowInterceptTouchEvent(true)
}
}
MotionEvent.ACTION_MOVE -> run {
val distanceX: Float = lastP!!.x - ev.rawX
val distanceY: Float = lastP!!.y - ev.rawY
if (abs(distanceY) > scaledTouchSlop && abs(distanceY) > abs(distanceX)) {
return@run
}
scrollBy(distanceX.toInt(), 0)
//瓒婄晫淇
if (scrollX < 0) {
scrollTo(0, 0)
} else if (scrollX > 0) {
if (scrollX > menuView!!.right - contentView!!.right - contentViewLayoutParam!!.rightMargin) {
scrollTo(
menuView!!.right - contentView!!.right - contentViewLayoutParam!!.rightMargin,
0
)
}
}
//褰撳浜庢按骞虫粦鍔ㄦ椂锛岀姝㈢埗绫绘嫤鎴?
if (abs(distanceX) > scaledTouchSlop) {
parent.requestDisallowInterceptTouchEvent(true)
}
lastP!!.set(ev.rawX, ev.rawY)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
finalDistanceX = firstP!!.x - ev.rawX
if (abs(finalDistanceX) > scaledTouchSlop) {
isSwiping = true
}
result = isShouldOpen()
handlerSwipeMenu(result)
}
else -> {}
}
return super.dispatchTouchEvent(ev)
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {}
MotionEvent.ACTION_MOVE -> {
//婊戝姩鏃舵嫤鎴偣鍑绘椂闂?
if (abs(finalDistanceX) > scaledTouchSlop) {
return true
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
//婊戝姩鍚庝笉瑙﹀彂contentView鐨勭偣鍑讳簨浠?
if (isSwiping) {
isSwiping = false
finalDistanceX = 0f
return true
}
}
}
return super.onInterceptTouchEvent(event)
}
/**
* 鑷姩璁剧疆鐘舵€?
*
* @param result
*/
private fun handlerSwipeMenu(result: State?) {
if (result === State.RIGHT_OPEN) {
viewCache = WeakReference(this)
scroller!!.startScroll(
scrollX,
0,
menuView!!.right - contentView!!.right - contentViewLayoutParam!!.rightMargin - scrollX,
0
)
mStateCache = result
} else {
scroller!!.startScroll(scrollX, 0, -scrollX, 0)
viewCache = null
mStateCache = null
}
invalidate()
}
override fun computeScroll() {
//鍒ゆ柇Scroller鏄惁鎵ц瀹屾瘯锛?
if (scroller!!.computeScrollOffset()) {
scrollTo(scroller!!.currX, scroller!!.currY)
invalidate()
}
}
/**
* 鏍规嵁褰撳墠鐨剆crollX鐨勫€煎垽鏂澗寮€鎵嬪悗搴斿浜庝綍绉嶇姸鎬?
*
* @param
* @param scrollX
* @return
*/
private fun isShouldOpen(): State? {
if (scaledTouchSlop >= abs(finalDistanceX)) {
return mStateCache
}
if (finalDistanceX < 0) {
//鍏抽棴鍙宠竟
if (scrollX > 0 && menuView != null) {
return State.CLOSE
}
} else if (finalDistanceX > 0) {
//寮€鍚彸杈?
if (scrollX > 0 && menuView != null) {
if (abs(menuView!!.width * fraction) < abs(scrollX)) {
return State.RIGHT_OPEN
}
}
}
return State.CLOSE
}
override fun onDetachedFromWindow() {
if (this == viewCache?.get()) {
viewCache!!.get()!!.handlerSwipeMenu(State.CLOSE)
}
super.onDetachedFromWindow()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (this == viewCache?.get()) {
viewCache!!.get()!!.handlerSwipeMenu(mStateCache)
}
}
companion object {
var viewCache: WeakReference<SwipeLayout>? = null
private set
private var mStateCache: State? = null
}
enum class State {
RIGHT_OPEN, CLOSE
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M548.6,170.7v304.8H853.3v73.1H548.5L548.6,853.3h-73.1l-0,-304.8H170.7v-73.1h304.8V170.7h73.1z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M384,512L731.7,202.7c17.1,-14.9 19.2,-42.7 4.3,-59.7 -14.9,-17.1 -42.7,-19.2 -59.7,-4.3l-384,341.3c-10.7,8.5 -14.9,19.2 -14.9,32s4.3,23.5 14.9,32l384,341.3c8.5,6.4 19.2,10.7 27.7,10.7 12.8,0 23.5,-4.3 32,-14.9 14.9,-17.1 14.9,-44.8 -4.3,-59.7L384,512z"
android:fillColor="#666666"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M731.7,480l-384,-341.3c-17.1,-14.9 -44.8,-14.9 -59.7,4.3 -14.9,17.1 -14.9,44.8 4.3,59.7L640,512 292.3,821.3c-17.1,14.9 -19.2,42.7 -4.3,59.7 8.5,8.5 19.2,14.9 32,14.9 10.7,0 19.2,-4.3 27.7,-10.7l384,-341.3c8.5,-8.5 14.9,-19.2 14.9,-32s-4.3,-23.5 -14.9,-32z"
android:fillColor="#666666"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M672,896c-8.5,0 -17.1,-2.1 -21.3,-8.5l-362.7,-352c-6.4,-6.4 -10.7,-14.9 -10.7,-23.5 0,-8.5 4.3,-17.1 10.7,-23.5L652.8,136.5c12.8,-12.8 32,-12.8 44.8,0s12.8,32 0,44.8L356.3,512l339.2,328.5c12.8,12.8 12.8,32 0,44.8 -6.4,8.5 -14.9,10.7 -23.5,10.7z"
android:fillColor="#ffffff"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M853.3,554.7l-682.7,0c-23.5,0 -42.7,19.2 -42.7,42.7l0,256c0,23.5 19.2,42.7 42.7,42.7l682.7,0c23.5,0 42.7,-19.2 42.7,-42.7l0,-256c0,-23.5 -19.2,-42.7 -42.7,-42.7zM298.7,810.7c-47.1,0 -85.3,-38.2 -85.3,-85.3s38.2,-85.3 85.3,-85.3 85.3,38.2 85.3,85.3 -38.2,85.3 -85.3,85.3zM853.3,128l-682.7,0c-23.5,0 -42.7,19.2 -42.7,42.7l0,256c0,23.5 19.2,42.7 42.7,42.7l682.7,0c23.5,0 42.7,-19.2 42.7,-42.7l0,-256c0,-23.5 -19.2,-42.7 -42.7,-42.7zM298.7,384c-47.1,0 -85.3,-38.2 -85.3,-85.3s38.2,-85.3 85.3,-85.3 85.3,38.2 85.3,85.3 -38.2,85.3 -85.3,85.3z"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M615.3,515.8a19.7,19.7 0,0 0,-9.4 -8.5,35.1 35.1,0 0,0 -13.2,-2.3h-12.4v110.3h12.4c5.1,0 9.6,-0.9 13.4,-2.5a20.9,20.9 0,0 0,9.4 -8.9c2.5,-4.2 4.4,-9.9 5.6,-16.9 1.2,-7.1 1.9,-15.9 1.9,-26.5 0,-11.3 -0.7,-20.5 -1.9,-27.7 -1.3,-7.1 -3.2,-12.8 -5.7,-16.9M506.4,509.5a14.3,14.3 0,0 0,-6.9 -5.1,30.8 30.8,0 0,0 -10.3,-1.5h-10.4V563.2h10.4c4.2,0 7.6,-0.5 10.4,-1.4a13.5,13.5 0,0 0,6.8 -4.9c1.8,-2.4 3,-5.5 3.8,-9.2 0.7,-3.8 1.1,-8.6 1.1,-14.5 0,-5.6 -0.4,-10.3 -1.1,-14.2a22.7,22.7 0,0 0,-3.8 -9.3"
android:fillColor="#ffffff"/>
<path
android:pathData="M161.4,369.7h701.2L862.6,339.4L161.4,339.4v30.3zM852.7,637.1c-6.6,9 -17.5,13.5 -32.7,13.5 -6.5,0 -12.7,-0.7 -18.5,-2a78.7,78.7 0,0 1,-13.8 -4.3v-33.6c1.7,1 3.8,1.9 6.2,2.8 2.4,0.9 5.1,1.6 7.8,2.2 2.8,0.6 5.6,1.1 8.5,1.5 2.9,0.4 5.9,0.6 8.7,0.6 3.6,0 6.7,-0.3 9.2,-1a12.4,12.4 0,0 0,6.1 -3.5,14.5 14.5,0 0,0 3.3,-6.6c0.6,-2.7 1,-6 1,-9.9 0,-3.4 -0.3,-6.2 -0.8,-8.6a15.9,15.9 0,0 0,-2.9 -6.3,20.3 20.3,0 0,0 -6.1,-5.1 77,77 0,0 0,-10.3 -4.8,70 70,0 0,1 -14.8,-7.2 31.3,31.3 0,0 1,-9.4 -9.6,39.8 39.8,0 0,1 -4.9,-13.8 117.8,117.8 0,0 1,-1.4 -20c0,-18.6 3.5,-31.7 10.6,-39.6 7.1,-7.8 17.1,-11.7 30,-11.7 6.3,0 11.8,0.6 16.7,1.9 4.8,1.3 8.8,2.7 11.7,4.4v31.6a74.7,74.7 0,0 0,-20.1 -5.8,63.7 63.7,0 0,0 -7.4,-0.5c-6.3,0 -10.8,1.3 -13.7,3.8 -2.8,2.5 -4.2,7.5 -4.2,14.9 0,2.9 0.2,5.3 0.6,7.3 0.4,2 1.3,3.9 2.6,5.5a19.1,19.1 0,0 0,5.7 4.4c2.5,1.3 5.8,2.8 9.8,4.3 6.6,2.5 12,5.3 16.3,8.5 4.2,3.1 7.5,6.8 9.9,11.1 2.4,4.3 4,9.4 4.9,15.2 0.9,5.8 1.3,12.8 1.3,21.1 0,17.2 -3.3,30.3 -9.9,39.3zM766.5,648.2h-33.4l-38.4,-133.5h-0.9v133.5h-24v-175.5h37.1l34.9,125.4h0.9v-125.4h23.8v175.5zM644.1,602.8c-2.6,11.2 -6.2,20.2 -10.9,26.9 -4.8,6.7 -10.5,11.5 -17.1,14.3 -6.7,2.8 -14,4.2 -22.1,4.2h-38.1v-175.5h38.1c8.1,0 15.5,1.1 22.1,3.4 6.7,2.3 12.4,6.6 17.1,13 4.8,6.4 8.4,15.3 11,26.8 2.5,11.5 3.8,26.3 3.8,44.5 0,17 -1.3,31.2 -3.8,42.4zM533.7,561.3c-1.6,7.8 -4.2,14.2 -7.7,19.1 -3.5,4.9 -8,8.4 -13.6,10.5 -5.5,2.1 -12.2,3.2 -20,3.2h-13.6v54.1h-24.3v-175.5h37.9c8.3,0 15.3,1.1 20.8,3.4 5.6,2.3 10.1,5.8 13.4,10.6 3.3,4.8 5.8,11 7.2,18.5 1.5,7.5 2.2,16.6 2.2,27.2 0,11.5 -0.8,21.1 -2.4,28.9zM439.4,506.3L410.2,506.3v141.9h-24.1v-141.9h-29.1v-33.6h82.3v33.6zM351.3,506.3h-29.1v141.9L298.1,648.1v-141.9L269,506.3v-33.6h82.3v33.6zM253.6,648.1h-24.1v-77.6L185.7,570.5v77.6h-24.3v-175.5h24.3v65.7h43.8v-65.7h24.1v175.5zM179.9,252.7a20.1,20.1 0,1 1,-0 40.3,20.1 20.1,0 0,1 0,-40.3zM242.8,252.7a20.1,20.1 0,1 1,0 40.3,20.1 20.1,0 0,1 0,-40.3zM305.7,252.7a20.1,20.1 0,1 1,0 40.3,20.1 20.1,0 0,1 0,-40.3zM832.7,192L191.3,192A106,106 0,0 0,85.3 297.9v408.7a106,106 0,0 0,105.9 105.9h641.5A106,106 0,0 0,938.7 706.7L938.7,297.9A106,106 0,0 0,832.7 192z"
android:fillColor="#ffffff"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M512,1024A512,512 0,1 1,512 0a512,512 0,0 1,0 1024zM448,448v384h128L576,448L448,448zM448,192v128h128L576,192L448,192z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M288,918.4l544,-352c19.2,-12.8 28.8,-32 28.8,-54.4 0,0 0,0 0,0 -0,-22.4 -9.6,-41.6 -28.8,-54.4l-275.2,-179.2L288,108.8c-19.2,-12.8 -44.8,-12.8 -64,-3.2 -19.2,9.6 -32,32 -32,54.4l0,704c0,22.4 12.8,44.8 32,54.4C243.2,931.2 268.8,931.2 288,918.4z" />
</vector>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.NewHttpDnsDemo.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/webview_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.NewHttpDnsDemo.PopupOverlay"
app:titleTextColor="@color/white"
app:navigationIcon="@drawable/ic_back"/>
</com.google.android.material.appbar.AppBarLayout>
<WebView
android:id="@+id/httpdns_webview"
android:layout_marginTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.newsdk.ams.emas.demo.ui.info.list.ListActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.NewHttpDnsDemo.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/info_list_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.NewHttpDnsDemo.PopupOverlay"
app:titleTextColor="@color/white"
app:navigationIcon="@drawable/ic_back"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/info_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
android:backgroundTint="@color/cloud_blue"
app:srcCompat="@drawable/ic_add"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.NewHttpDnsDemo.AppBarOverlay"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.NewHttpDnsDemo.PopupOverlay"
app:titleTextColor="@color/white"
app:navigationIcon="@drawable/ic_back"/>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/sdns_params_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/app_bar_layout"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@android:color/transparent"
android:backgroundTint="@color/white"
android:hint="@string/input_the_sdns_params"
app:boxBackgroundColor="@android:color/transparent"
app:errorEnabled="true"
app:helperText="@string/input_the_sdns_params_help_text"
tools:ignore="MissingConstraints">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false"
android:paddingStart="2dp"
android:paddingEnd="2dp" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/add_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
>
</androidx.appcompat.widget.AppCompatEditText>
</LinearLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/input_content_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="5dp"
android:textSize="14dp"
/>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/input_content_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="10dp"
android:textSize="14dp"
/>
</LinearLayout>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/add_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
>
</androidx.appcompat.widget.AppCompatEditText>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="@string/ip_type"/>
<RadioGroup android:id="@+id/ip_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp" />
</LinearLayout>

View File

@@ -0,0 +1,268 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.newsdk.ams.httpdns.demo.BuildConfig" />
<variable
name="viewModel"
type="com.newsdk.ams.emas.demo.ui.basic.BasicSettingViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.info.InfoFragment"
android:layout_marginBottom="60dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/basic_scroll_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:scrollbarStyle="insideOverlay"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="@dimen/content_max_width_percent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/secret_key_config"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.secretKeySetByConfig}"
android:onCheckedChanged="@{viewModel::toggleSecretKeySet}"
android:text="@string/secret_key_location"
android:textSize="16dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_auth_mode"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.enableAuthMode}"
android:onCheckedChanged="@{viewModel::toggleAuthMode}"
android:text="@string/enable_auth_mode"
android:textSize="16dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_encrypt_mode"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.enableEncryptMode}"
android:onCheckedChanged="@{viewModel::toggleEncryptMode}"
android:text="@string/enable_encrypt_mode"
android:textSize="16dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_expired_ip"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.enableExpiredIP}"
android:onCheckedChanged="@{viewModel::toggleEnableExpiredIp}"
android:text="@string/enable_expired_ip"
android:textSize="16dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_cache_ip"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.enableCacheIP}"
android:onCheckedChanged="@{viewModel::toggleEnableCacheIp}"
android:text="@string/enable_local_cache"
android:textSize="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/cache_expire_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@android:color/transparent"
android:backgroundTint="@color/white"
android:hint="@string/cache_expire_time"
app:boxBackgroundColor="@android:color/transparent"
app:helperText="@string/cache_expire_time_unit"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.cacheExpireTime}"
android:paddingStart="2dp"
android:paddingEnd="2dp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_https"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.enableHttps}"
android:onCheckedChanged="@{viewModel::toggleEnableHttps}"
android:text="@string/enable_https"
android:textSize="16dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_downgrade"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.enableDegrade}"
android:onCheckedChanged="@{viewModel::toggleEnableDegrade}"
android:text="@string/enable_downgrade"
android:textSize="16dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_network_change_pre"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.enableAutoRefresh}"
android:onCheckedChanged="@{viewModel::toggleEnableAutoRefresh}"
android:text="@string/enable_network_changed_pre_resolve"
android:textSize="16dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_log"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.enableLog}"
android:onCheckedChanged="@{viewModel::toggleEnableLog}"
android:text="@string/enable_httpdns_log"
android:textSize="16dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:onClick="@{() -> viewModel.setRegion()}"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Region"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/info_region"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{String.valueOf(viewModel.currentRegion)}"
android:textColor="@color/black"
android:textSize="14dp"
tools:text="12345" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:onClick="@{() -> viewModel.setTimeout()}"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/timeout"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/info_timeout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{String.valueOf(viewModel.currentTimeout)}"
android:textColor="@color/black"
android:textSize="14dp"
tools:text="12345" />
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_arrow_right"
android:onClick="@{() -> viewModel.showClearCacheDialog()}"
android:padding="18dp"
android:text="@string/clear_host_cache"
android:textColor="@color/black"
android:background="?selectableItemBackground"
android:textSize="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_arrow_right"
android:onClick="@{() -> viewModel.batchResolveHosts()}"
android:padding="18dp"
android:text="@string/batch_resolve"
android:textColor="@color/black"
android:background="?selectableItemBackground"
android:textSize="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_arrow_right"
android:onClick="@{() -> viewModel.showAddPreResolveDialog()}"
android:padding="18dp"
android:text="@string/add_pre_resolve"
android:textColor="@color/black"
android:background="?selectableItemBackground"
android:visibility="gone"
android:textSize="16dp" />
<TextView android:id="@+id/jump_to_add_tag"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_arrow_right"
android:padding="18dp"
android:text="@string/add_tag"
android:textColor="@color/black"
android:background="?selectableItemBackground"
android:textSize="16dp" />
<Button android:id="@+id/init_httpdns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/init_httpdns"
android:onClick="@{() -> viewModel.initHttpDns()}"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.newsdk.ams.emas.demo.ui.practice.BestPracticeViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:id="@+id/open_httpdns_webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_arrow_right"
android:padding="18dp"
android:text="@string/httpdns_webview_best_practice"
android:textColor="@color/black"
android:textSize="16dp" />
<!-- <TextView-->
<!-- android:id="@+id/open_httpdns_webview_post"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="?selectableItemBackground"-->
<!-- android:drawableEnd="@drawable/ic_arrow_right"-->
<!-- android:padding="18dp"-->
<!-- android:text="@string/httpdns_webview_post_best_practice"-->
<!-- android:textColor="@color/black"-->
<!-- android:textSize="16dp" />-->
<TextView
android:id="@+id/open_httpdns_sni"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_arrow_right"
android:padding="18dp"
android:text="@string/httpdns_sni"
android:textColor="@color/black"
android:textSize="16dp"
android:onClick="@{() -> viewModel.sniRequest()}"
/>
</LinearLayout>
</layout>

View File

@@ -0,0 +1,378 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.newsdk.ams.httpdns.demo.BuildConfig" />
<variable
name="viewModel"
type="com.newsdk.ams.emas.demo.ui.info.InfoViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.info.InfoFragment"
android:paddingBottom="60dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/basic_scroll_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:scrollbarStyle="insideOverlay"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="@dimen/content_max_width_percent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="15dp"
android:paddingTop="10dp"
android:paddingEnd="15dp"
android:paddingBottom="10dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/info_app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="16dp"
android:textStyle="bold" />
<TextView
android:id="@+id/info_pkg_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="15dp"
tools:text="com.newsdk.ams.httpdns.demo" />
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:background="@color/light_grey" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="AccoutId"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/info_account_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{String.valueOf(viewModel.accountId)}"
android:textColor="@color/black"
android:textSize="14dp"
tools:text="12345" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/info_secret_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="SecretKey"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/info_secret_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{String.valueOf(viewModel.secretKey)}"
android:textColor="@color/black"
android:textSize="14dp"
tools:text="12345" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/jump_to_pre_resolve"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/pre_resolve_list"
android:textColor="@color/black"
android:textSize="16dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:src="@drawable/ic_arrow_right" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/jump_to_ip_ranking"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/ip_probe_list"
android:textColor="@color/black"
android:textSize="16dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:src="@drawable/ic_arrow_right" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/jump_to_ttl_cache"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/ttl_cache_list"
android:textColor="@color/black"
android:textSize="16dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:src="@drawable/ic_arrow_right" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/jump_to_host_fiex_ip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/host_fixed_ip_list"
android:textColor="@color/black"
android:textSize="16dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:src="@drawable/ic_arrow_right" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/jump_to_host_black_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/host_black_list"
android:textColor="@color/black"
android:textSize="16dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:src="@drawable/ic_arrow_right" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/jump_to_sdns_global_params"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/sdns_global_params"
android:textColor="@color/black"
android:textSize="16dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:src="@drawable/ic_arrow_right" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/jump_to_batch_resolve"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/batch_resolve"
android:textColor="@color/black"
android:textSize="16dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:src="@drawable/ic_arrow_right" />
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_arrow_right"
android:onClick="@{() -> viewModel.clearDnsCache()}"
android:padding="18dp"
android:text="@string/clear_dns_cache"
android:textColor="@color/black"
android:textSize="16dp"
android:background="?selectableItemBackground"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/httpdns_sdk_version"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{BuildConfig.HTTPDNS_VERSION}"
android:textColor="@color/black"
android:textSize="14dp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/ip_stack"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{String.valueOf(viewModel.currentIpStackType)}"
android:textColor="@color/black"
android:textSize="14dp" />
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_arrow_right"
android:onClick="@{() -> viewModel.clearAllCache()}"
android:padding="18dp"
android:text="@string/clear_all_cache"
android:textColor="@color/black"
android:textSize="16dp"
android:background="?selectableItemBackground"
/>
FragmentBestPracticeBinding
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@@ -0,0 +1,336 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.newsdk.ams.emas.demo.ui.resolve.ResolveViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.resolve.ResolveFragment"
android:paddingBottom="60dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/basic_scroll_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:scrollbarStyle="insideOverlay"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="@dimen/content_max_width_percent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="5dp"
android:text="@string/network_request_type"
android:textColor="@color/black"
android:textSize="16dp" />
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:checkedButton="@id/okhttp"
android:onCheckedChanged="@{viewModel::onNetRequestTypeChanged}"
android:orientation="horizontal">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/okhttp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OkHttp"
android:textSize="14dp" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/http_url_connection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="HttpURLConnection"
android:textSize="14dp" />
</RadioGroup>
</RelativeLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/enable_sdns_resolve"
style="@style/Widget.HttpDnsDemo.Settings.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{viewModel.isSdns}"
android:text="@string/sdns_resolve"
android:textSize="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/sdns_params_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@android:color/transparent"
android:backgroundTint="@color/white"
android:hint="@string/input_the_sdns_params"
app:boxBackgroundColor="@android:color/transparent"
app:errorEnabled="true"
app:helperText="@string/input_the_sdns_params_help_text">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="2dp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/sdns_cache_key_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@android:color/transparent"
android:backgroundTint="@color/white"
android:hint="@string/input_the_sdns_cache_key"
app:boxBackgroundColor="@android:color/transparent"
app:errorEnabled="true"
app:helperText="@string/input_the_sdns_cache_key_help_text">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="2dp" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="12dp"
android:onClick="@{() -> viewModel.setResolveMethod()}"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="5dp"
android:text="@string/resolve_method"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/resolve_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{String.valueOf(viewModel.currentResolveMethod)}"
android:textColor="@color/black"
android:textSize="12dp" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:onClick="@{() -> viewModel.setResolveIpType()}"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="5dp"
android:text="@string/ip_type"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/info_region"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{String.valueOf(viewModel.currentIpType)}"
android:textColor="@color/black"
android:textSize="14dp"
tools:text="V4" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:onClick="@{() -> viewModel.setRequestNumber()}"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="5dp"
android:text="@string/request_num"
android:textColor="@color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/request_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:padding="5dp"
android:text="@{String.valueOf(viewModel.requestNum)}"
android:textColor="@color/black"
android:textSize="14dp"
tools:text="1" />
</RelativeLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/resolve_host_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@android:color/transparent"
android:backgroundTint="@color/white"
android:hint="@string/input_the_resolve_host"
app:boxBackgroundColor="@android:color/transparent"
app:errorEnabled="true"
app:helperText="@string/input_the_resolve_host_help_text">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="2dp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/request_api_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:background="@android:color/transparent"
android:backgroundTint="@color/white"
android:hint="@string/input_the_request_api"
app:boxBackgroundColor="@android:color/transparent"
app:errorEnabled="true"
app:helperText="@string/input_the_request_api_help_text">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="2dp" />
</com.google.android.material.textfield.TextInputLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="5dp"
android:text="@string/schema_type"
android:textColor="@color/black"
android:textSize="16dp" />
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:checkedButton="@id/schema_https"
android:onCheckedChanged="@{viewModel::onSchemaTypeChanged}"
android:orientation="horizontal">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/schema_https"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Https"
android:textSize="14dp" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/schema_http"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="Http"
android:textSize="14dp" />
</RadioGroup>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/start_resolve"
style="@style/Widget.MaterialComponents.Button.Icon"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="@string/resolve_and_request"
app:cornerRadius="20dp"
app:icon="@drawable/ic_httpdns"
app:iconGravity="textStart"
app:iconPadding="8dp"
app:iconSize="24dp"
app:iconTint="@color/white"
app:rippleColor="@color/cloud_blue"
app:strokeColor="@color/white"
app:strokeWidth="2dp" />
</RelativeLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.newsdk.ams.emas.demo.widget.SwipeLayout
android:id="@+id/host_and_port_or_ttl_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentView="@id/host_port_ttl_item"
app:menuView="@id/slide_delete_menu">
<LinearLayout
android:id="@+id/host_port_ttl_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="4dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:padding="5dp"
android:text="@string/host"
android:textColor="@color/black"
android:textSize="15dp" />
<TextView
android:id="@+id/host_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:padding="5dp"
android:textColor="@color/black"
android:textSize="15dp"
android:maxLines="2"
android:ellipsize="marquee"
tools:text="demo.cloudxdr.com" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="4dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/port_or_ttl_indicate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:padding="5dp"
android:textColor="@color/black"
android:textSize="15dp"
tools:text="@string/port" />
<TextView
android:id="@+id/port_or_ttl_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:padding="5dp"
android:textColor="@color/black"
android:textSize="15dp"
android:maxLines="2"
android:ellipsize="marquee"
tools:text="8888" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1.6dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:background="#30BDBDBD" />
</LinearLayout>
<RelativeLayout
android:id="@+id/slide_delete_menu"
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_marginBottom="6dp"
android:background="#D32F2F"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="5dp"
android:text="@string/delete"
android:textColor="@color/white"
android:textSize="17dp" />
</RelativeLayout>
</com.newsdk.ams.emas.demo.widget.SwipeLayout>
<com.newsdk.ams.emas.demo.widget.SwipeLayout
android:id="@+id/host_fixed_ip_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentView="@id/host_fixed_ip_item"
app:menuView="@id/slide_delete_menu2">
<LinearLayout
android:id="@+id/host_fixed_ip_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/pre_host_or_with_fixed_ip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:padding="5dp"
android:textColor="@color/black"
android:textSize="17dp"
tools:text="demo.cloudxdr.com" />
<View
android:layout_width="match_parent"
android:layout_height="1.6dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:background="#30BDBDBD" />
</LinearLayout>
<RelativeLayout
android:id="@+id/slide_delete_menu2"
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_marginBottom="6dp"
android:background="#D32F2F"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="5dp"
android:text="@string/delete"
android:textColor="@color/white"
android:textSize="17dp" />
</RelativeLayout>
</com.newsdk.ams.emas.demo.widget.SwipeLayout>
</LinearLayout>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_basic"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_basic" />
<item
android:id="@+id/navigation_resolve"
android:icon="@drawable/ic_dns"
android:title="@string/title_resolve" />
<item
android:id="@+id/navigation_best_practice"
android:icon="@drawable/ic_practice"
android:title="@string/title_best_practice" />
<item
android:id="@+id/navigation_information"
android:icon="@drawable/ic_information"
android:title="@string/title_info" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_basic">
<fragment
android:id="@+id/navigation_basic"
android:name="com.newsdk.ams.emas.demo.ui.basic.BasicSettingFragment"
android:label="@string/title_basic"
tools:layout="@layout/fragment_basic_setting" />
<fragment
android:id="@+id/navigation_resolve"
android:name="com.newsdk.ams.emas.demo.ui.resolve.ResolveFragment"
android:label="@string/title_resolve"
tools:layout="@layout/fragment_resolve" />
<fragment
android:id="@+id/navigation_best_practice"
android:name="com.newsdk.ams.emas.demo.ui.practice.BestPracticeFragment"
android:label="@string/title_best_practice"
tools:layout="@layout/fragment_best_practice" />
<fragment
android:id="@+id/navigation_information"
android:name="com.newsdk.ams.emas.demo.ui.info.InfoFragment"
android:label="@string/title_info"
tools:layout="@layout/fragment_info" />
</navigation>

View File

@@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View File

@@ -0,0 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Newandroidsdkhttpdns_for_open_source" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryDark">@color/purple_700</item>
<item name="colorAccent">@color/teal_200</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">200dp</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View File

@@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">闃块噷浜慔ttpDNS瀹樻柟Demo</string>
<string name="title_basic">鍩虹璁剧疆</string>
<string name="title_resolve">HttpDNS瑙</string>
<string name="title_best_practice">鏈€浣冲疄璺</string>
<string name="title_info">淇℃伅</string>
<string name="enable_auth_mode">寮€鍚壌鏉冩ā寮</string>
<string name="secret_key_location">SecretKey閫氳繃config閰嶇疆</string>
<string name="enable_encrypt_mode">寮€鍚姞瀵嗘ā寮</string>
<string name="enable_expired_ip">鍏佽杩囨湡IP</string>
<string name="enable_local_cache">寮€鍚寔涔呭寲缂撳瓨IP</string>
<string name="cache_expire_time">缂撳瓨杩囨湡鏃堕棿</string>
<string name="cache_expire_time_unit">缂撳瓨杩囨湡鏃堕棿鐨勫崟浣嶆槸澶?</string>
<string name="enable_https">寮€鍚疕TTPS</string>
<string name="enable_downgrade">鍏佽闄嶇骇</string>
<string name="enable_network_changed_pre_resolve">缃戠粶鍒囨崲鑷姩鍒锋柊</string>
<string name="enable_httpdns_log">鍏佽SDK鎵撳嵃鏃ュ織</string>
<string name="timeout">瓒呮椂鏃堕棿</string>
<string name="init_tip">璇峰厛鍒濆鍖朒ttpDns</string>
<string name="inited_httpdns">宸茬粡鍒濆鍖</string>
<string name="china">涓浗</string>
<string name="china_hk">涓浗棣欐腐</string>
<string name="singapore">鏂板姞鍧</string>
<string name="germany">寰峰浗</string>
<string name="america">缇庡浗</string>
<string name="pre">棰勫彂</string>
<string name="select_region">閫夋嫨Region</string>
<string name="confirm">纭</string>
<string name="cancel">鍙栨秷</string>
<string name="timeout_hint">璇疯緭鍏ヨ秴鏃舵椂闂达紝姣涓哄崟浣</string>
<string name="set_timeout">璁剧疆瓒呮椂</string>
<string name="timeout_empty">瓒呮椂鏃堕棿涓虹┖</string>
<string name="ip_probe_list">鎺㈡祴IP鍒楄〃</string>
<string name="ttl_cache_list">鑷畾涔塗TL缂撳瓨鍒楄〃</string>
<string name="host_fixed_ip_list">涓荤珯鍩熷悕鍒楄〃</string>
<string name="host_black_list">鍩熷悕榛戝悕鍗曞垪琛</string>
<string name="sdns_global_params">鑷畾涔夎В鏋愬叏灞€鍙傛暟</string>
<string name="batch_resolve">鎵归噺瑙f瀽鍩熷悕</string>
<string name="httpdns_sdk_version">HttpDNS鐗堟湰鍙</string>
<string name="unknown">鏈煡</string>
<string name="ip_stack">褰撳墠鎵€杩炴帴缃戠粶鐨勭綉缁滄爤绫诲瀷</string>
<string name="clear_host_cache">娓呯┖鎸囧畾鍩熷悕缂撳瓨</string>
<string name="clear_cache_hint">璇疯緭鍏ヨ娓呯┖缂撳瓨鐨勫煙鍚</string>
<string name="network_request_type">缃戠粶璇锋眰绫诲瀷</string>
<string name="async_resolve">寮傛瑙瀽鑾峰彇IP</string>
<string name="sdns_resolve">鑷畾涔夊煙鍚嶈В鏋</string>
<string name="ip_type">瑕佽В鏋愮殑IP绫诲瀷</string>
<string name="resolve_method">浣跨敤鐨勮В鏋愭柟娉</string>
<string name="request_num">骞跺彂璇锋眰娆℃暟</string>
<string name="select_resolve_ip_type">閫夋嫨IP绫诲瀷</string>
<string name="select_resolve_method">閫夋嫨瑙f瀽鏂规硶</string>
<string name="select_request_num">閫夋嫨骞跺彂璇锋眰娆℃暟</string>
<string name="auto_get_ip_type">鑷姩鍒ゆ柇IP绫诲瀷</string>
<string name="add_pre_resolve">娣诲姞棰勮В鏋愬煙鍚</string>
<string name="add_pre_resolve_hint">璇疯緭鍏ヨ棰勮В鏋愮殑鍩熷悕</string>
<string name="pre_resolve_list">棰勮В鏋愬煙鍚嶅垪琛</string>
<string name="add_tag">娣诲姞Tag</string>
<string name="pre_resolve_host_duplicate">%s鍩熷悕宸茬粡琚坊鍔犺嚦棰勮В鏋愬垪琛紝璇峰嬁閲嶅娣诲姞</string>
<string name="init_httpdns">鍒濆鍖朒ttpDns</string>
<string name="batch_resolve_list">鎵归噺瑙f瀽鍩熷悕鍒楄〃</string>
<string name="add_batch_resolve_hint">璇疯緭鍏ヨ鎵归噺瑙f瀽鐨勫煙鍚</string>
<string name="add_batch_resolve">璇疯緭鍏ヨ鎵归噺瑙f瀽鐨勫煙鍚</string>
<string name="batch_resolve_host_duplicate">%s鍩熷悕宸茬粡琚坊鍔犺嚦鎵归噺瑙瀽鍒楄〃锛岃鍕块噸澶嶆坊鍔</string>
<string name="host">鍩熷悕锛</string>
<string name="port">绔彛锛</string>
<string name="ttl">TTL鏃堕暱: </string>
<string name="add_host_fixed_ip_hint">璇疯緭鍏ヤ富绔欏煙鍚</string>
<string name="add_host_fixed_ip">娣诲姞涓荤珯鍩熷悕</string>
<string name="host_fixed_ip_empty">涓荤珯鍩熷悕涓虹┖</string>
<string name="host_fixed_ip_duplicate">%s涓荤珯鍩熷悕宸茬粡琚坊鍔狅紝璇峰嬁閲嶅娣诲姞</string>
<string name="add_tag_hint">璇疯緭鍏ユ爣绛</string>
<string name="add_host_to_black_list_hint">璇疯緭鍏ヤ笉浣跨敤HttpDns瑙瀽鐨勫煙鍚</string>
<string name="add_host_to_black_list">娣诲姞涓嶄娇鐢℉ttpDns鐨勫煙鍚</string>
<string name="host_to_black_list_empty">鍩熷悕涓虹┖</string>
<string name="host_black_list_duplicate">%s鍩熷悕宸茬粡鍦ㄩ粦鍚嶅崟涓紝璇峰嬁閲嶅娣诲姞</string>
<string name="add_ip_probe_host_hint">璇疯緭鍏ユ帰娴婭P鐨勫煙鍚</string>
<string name="add_ip_probe_port_hint">璇疯緭鍏ユ帰娴婭P鐨勭鍙</string>
<string name="add_ip_probe">娣诲姞IP鎺㈡祴</string>
<string name="port_is_empty">绔彛鍙蜂负绌</string>
<string name="ip_probe_item_duplicate">%s:%s宸茬粡琚坊鍔犺嚦IP鎺㈡祴鍒楄〃锛岃鍕块噸澶嶆坊鍔</string>
<string name="host_is_empty">鍩熷悕涓虹┖</string>
<string name="pre_resolve_host_is_empty">棰勮В鏋愮殑鍩熷悕涓虹┖</string>
<string name="batch_resolve_host_is_empty">鎵归噺瑙f瀽鐨勫煙鍚嶄负绌</string>
<string name="add_ttl_host_hint">璇疯緭鍏ョ紦瀛樼殑鍩熷悕</string>
<string name="add_ttl_ttl_hint">璇疯緭鍏ョ紦瀛樼殑ttl鏃堕棿锛屽崟浣嶏細绉</string>
<string name="add_custom_ttl">娣诲姞鑷畾涔塗TL</string>
<string name="ttl_is_empty">TTL鏃堕棿涓虹┖</string>
<string name="ttl_is_not_number">璇疯緭鍏ユ纭牸寮忕殑TTL鏃堕暱</string>
<string name="delete">鍒犻櫎</string>
<string name="clear_all_cache">娓呴櫎鎵€鏈塇ttpDNS閰嶇疆缂撳瓨</string>
<string name="all_cache_cleared">鎵€鏈塇ttpDNS閰嶇疆缂撳瓨閮藉凡娓呴櫎</string>
<string name="clear_dns_cache">娓呯┖Dns缂撳瓨锛?00娆★級</string>
<string name="input_the_sdns_params">鑷畾涔夎В鏋愮殑鍙傛暟</string>
<string name="input_the_sdns_params_help_text">json瀵硅薄鏍煎紡</string>
<string name="input_the_sdns_params_error">鑷畾涔夎В鏋愮殑鍙傛暟蹇呴』鏄痡son瀵硅薄</string>
<string name="input_the_sdns_cache_key">鑷畾涔夎В鏋愮殑cache key</string>
<string name="input_the_sdns_cache_key_help_text">Cache key鐢ㄤ簬鍞竴鏍囪瘑缂撳瓨涓殑瑙瀽缁撴灉</string>
<string name="input_the_resolve_host">瑕佽В鏋愮殑鍩熷悕</string>
<string name="input_the_resolve_host_help_text">渚嬪锛歞emo.cloudxdr.com</string>
<string name="input_the_request_api">瑕佽姹傜殑鎺ュ彛</string>
<string name="input_the_request_api_help_text">渚嬪: /document_detail/434554.html</string>
<string name="resolve_and_request">瑙f瀽骞惰姹</string>
<string name="resolve_host_empty">鍩熷悕涓嶈兘涓虹┖</string>
<string name="host_is_ip">鍩熷悕涓嶈兘鏄疘P鍦板潃</string>
<string name="host_illegal">璇疯緭鍏ユ纭牸寮忕殑鍩熷悕</string>
<string name="schema_type">Schema绫诲瀷</string>
<string name="httpdns_webview_best_practice">HttpDNS WebView 鎷︽埅GET璇锋眰</string>
<string name="httpdns_webview_post_best_practice">HttpDNS WebView POST璇锋眰閫氳繃Native鍙戦€</string>
<string name="httpdns_sni">HttpDNS IP鐩磋繛鏂规</string>
<string name="ok">濂界殑</string>
<string name="response_title">璇锋眰缁撴灉</string>
<string name="body_large_see_log">Body璇烽€氳繃鏃ュ織鏌ョ湅锛屽叧閿瓧涓篽ttpdns</string>
<string name="sni_request">IP鐩磋繛鏂规</string>
<string name="tips">鎻愮ず</string>
<string name="network_not_connect">缃戠粶鏈繛鎺ワ紝璇锋鏌ョ綉缁</string>
<string name="request_exception">璇锋眰寮傚父: %s</string>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SwipeLayout">
<attr name="menuView" format="reference" />
<attr name="contentView" format="reference" />
<attr name="fraction" format="float" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="light_grey">#e6e6e6</color>
<color name="cloud_blue">#1B58F4</color>
</resources>

View File

@@ -0,0 +1,11 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<!-- Maximum width of the content area (in percent) to use in single pane. -->
<dimen name="content_max_width_percent">1.0</dimen>
<dimen name="spacing_normal">8dp</dimen>
<dimen name="margin_small">8dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,141 @@
<resources>
<string name="app_name">New Cloud HttpDNS Demo</string>
<string name="title_basic">Basic Settings</string>
<string name="title_resolve">HttpDNS Resolve</string>
<string name="title_best_practice">Best Practice</string>
<string name="title_info">Information</string>
<string name="init_tip">Please init HttpDns first</string>
<string name="enable_auth_mode">Enable Auth Mode</string>
<string name="secret_key_location">Secret key is in config</string>
<string name="enable_encrypt_mode">Enable Encrypt Mode</string>
<string name="enable_expired_ip">Enable Expired IP</string>
<string name="enable_local_cache">Enable Cache IP</string>
<string name="cache_expire_time">cache expire time</string>
<string name="cache_expire_time_unit">the unit of cache expire time is day.</string>
<string name="enable_https">Enable HTTPS</string>
<string name="enable_downgrade">Enable Degrade</string>
<string name="enable_network_changed_pre_resolve">Enable Automatic Refresh</string>
<string name="enable_httpdns_log">Enable Log</string>
<string name="timeout">Timeout</string>
<string name="china">China</string>
<string name="china_hk">China-HongKong</string>
<string name="singapore">Singapore</string>
<string name="germany">Germany</string>
<string name="america">America</string>
<string name="pre">Pre</string>
<string name="select_region">Choose Region</string>
<string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>
<string name="timeout_hint">Please input timeout, ms unit</string>
<string name="set_timeout">Set Timeout</string>
<string name="timeout_empty">Timeout is empty</string>
<string name="ip_probe_list">Probe IP List</string>
<string name="ttl_cache_list">TTL Cache List</string>
<string name="host_fixed_ip_list">Host with fixed IP List</string>
<string name="host_black_list">Host black list</string>
<string name="sdns_global_params">Custom DNS global params</string>
<string name="batch_resolve"> Batch resolve hosts</string>
<string name="httpdns_sdk_version">HttpDNS Version</string>
<string name="unknown">Unknown</string>
<string name="ip_stack">IP stack type of current network</string>
<string name="clear_host_cache">Clear the cached DNS record</string>
<string name="clear_cache_hint">Please input the domain name which you want to clear the cache</string>
<string name="network_request_type">Network request type</string>
<string name="async_resolve">Asynchronously resolve host</string>
<string name="sdns_resolve">Custom resolve host</string>
<string name="ip_type">Resolve IP Type</string>
<string name="resolve_method">Resolve Method</string>
<string name="request_num">Number of concurrent requests</string>
<string name="select_resolve_ip_type">Select IP Type</string>
<string name="select_resolve_method">Select resolve Method</string>
<string name="select_request_num">Select number of concurrent requests</string>
<string name="auto_get_ip_type">Get IP automatically</string>
<string name="add_pre_resolve">Add Pre-Resolve Domain</string>
<string name="add_pre_resolve_hint">Please input the domain you want to pre-resolve</string>
<string name="pre_resolve_list">Pre-Resolve Domain List</string>
<string name="add_tag">Add Tag</string>
<string name="pre_resolve_host_duplicate">The host %s is already added to pre-resolve List</string>
<string name="init_httpdns">Init HttpDns</string>
<string name="inited_httpdns">HttpDns Inited</string>
<string name="batch_resolve_list">Batch resolve domain list</string>
<string name="add_batch_resolve_hint">Please input the domain you want to batch resolve</string>
<string name="add_batch_resolve">Add batch resolve domain</string>
<string name="batch_resolve_host_duplicate">The host %s is already added to batch resolve List</string>
<string name="host">Host: </string>
<string name="port">Port: </string>
<string name="ttl">Ttl: </string>
<string name="add_host_fixed_ip_hint">Please input the host with fixed IP</string>
<string name="add_host_fixed_ip">Add Host with fixed IP</string>
<string name="host_fixed_ip_empty">Host with fixed IP is empty</string>
<string name="host_fixed_ip_duplicate">The host %s is already added to fixed IP List</string>
<string name="add_tag_hint">Please input the tag</string>
<string name="add_host_to_black_list_hint">Please input the host not use HttpDns</string>
<string name="add_host_to_black_list">Add Host not use HttpDns</string>
<string name="host_to_black_list_empty">Host not use HttpDns is empty</string>
<string name="host_black_list_duplicate">The host %s is already added to Black List</string>
<string name="add_ip_probe_host_hint">Please input the host of IP Probe</string>
<string name="add_ip_probe_port_hint">Please input the port of IP Probe</string>
<string name="add_ip_probe">Add IP probe</string>
<string name="port_is_empty">Port is empty</string>
<string name="ip_probe_item_duplicate">The ip probe item - %s:%s is already added</string>
<string name="host_is_empty">Host is empty</string>
<string name="pre_resolve_host_is_empty">Pre-Resolve Host is empty</string>
<string name="batch_resolve_host_is_empty">Batch resolve host is empty</string>
<string name="add_ttl_host_hint">Please input the host of custom TTL</string>
<string name="add_ttl_ttl_hint">Please input the ttl, unit is second</string>
<string name="add_custom_ttl">Add custom ttl</string>
<string name="ttl_is_empty">TTL is empty</string>
<string name="ttl_is_not_number">Please input correct ttl value</string>
<string name="delete">Delete</string>
<string name="clear_all_cache">Clear all HttpDNS config cache</string>
<string name="all_cache_cleared">All HttpDNDS config cache have been cleared</string>
<string name="clear_dns_cache">Clear Dns Cache (500)</string>
<string name="input_the_sdns_params">The sdns params</string>
<string name="input_the_sdns_params_help_text">json object format</string>
<string name="input_the_sdns_params_error">The sdns params must be json object</string>
<string name="input_the_sdns_cache_key">The sdns cache key</string>
<string name="input_the_sdns_cache_key_help_text">The cache key is the uniquely identifies of the cache dns result</string>
<string name="input_the_resolve_host">The Host will be resolved</string>
<string name="input_the_resolve_host_help_text">eg: demo.cloudxdr.com</string>
<string name="input_the_request_api">The API to be requested</string>
<string name="input_the_request_api_help_text">eg: /document_detail/434554.html</string>
<string name="resolve_and_request">Resolve and request</string>
<string name="resolve_host_empty">The host can not be empty</string>
<string name="host_is_ip">The input host can not be an IP</string>
<string name="host_illegal">The input a valid host</string>
<string name="schema_type">Schema Type</string>
<string name="httpdns_webview_best_practice">HttpDNS WebView intercept GET request</string>
<string name="httpdns_webview_post_best_practice">HttpDNS WebView POST request forward to native</string>
<string name="httpdns_sni">HttpDNS Request By IP Address</string>
<string name="ok">OK</string>
<string name="response_title">Response</string>
<string name="body_large_see_log">Check the logs to get the response body with the keyword httpdns</string>
<string name="sni_request">Request By IP Address</string>
<string name="tips">Tips</string>
<string name="network_not_connect">Network has issues, please check the network</string>
<string name="request_exception">Request exception: %s</string>
</resources>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.HttpDnsDemo" parent="@android:style/Widget.Material" />
<style name="Widget.HttpDnsDemo.Settings.Switch" parent="Widget.MaterialComponents.CompoundButton.Switch">
<item name="android:background">@null</item>
<item name="android:foreground">?selectableItemBackground</item>
<item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
<item name="android:paddingBottom">@dimen/spacing_normal</item>
<item name="android:paddingEnd">?listPreferredItemPaddingEnd</item>
<item name="android:paddingStart">?listPreferredItemPaddingStart</item>
<item name="android:paddingTop">@dimen/spacing_normal</item>
<item name="android:textAlignment">viewStart</item>
<item name="switchPadding">@dimen/margin_small</item>
<item name="switchTextAppearance">?attr/textAppearanceBody2</item>
</style>
</resources>

View File

@@ -0,0 +1,20 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.NewHttpDnsDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/cloud_blue</item>
<item name="colorPrimaryDark">@color/cloud_blue</item>
<item name="colorAccent">@color/cloud_blue</item>
<item name="colorOnSecondary">@color/white</item>
<!-- Customize your theme here. -->
</style>
<style name="Theme.NewHttpDnsDemo.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.NewHttpDnsDemo.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.NewHttpDnsDemo.PopupOverlay" parent="ThemeOverlay.AppCompat.Dark" />
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>

View File

@@ -0,0 +1,18 @@
package com.newsdk.ams.emas.demo
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}