feat: sync httpdns sdk/platform updates without large binaries
This commit is contained in:
128
HttpDNSSDK/sdk/android/demo/build.gradle
Normal file
128
HttpDNSSDK/sdk/android/demo/build.gradle
Normal 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'
|
||||
}
|
||||
19
HttpDNSSDK/sdk/android/demo/gradle.properties
Normal file
19
HttpDNSSDK/sdk/android/demo/gradle.properties
Normal 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
|
||||
22
HttpDNSSDK/sdk/android/demo/proguard-rules.pro
vendored
Normal file
22
HttpDNSSDK/sdk/android/demo/proguard-rules.pro
vendored
Normal 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.**{*;}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
59
HttpDNSSDK/sdk/android/demo/src/main/AndroidManifest.xml
Normal file
59
HttpDNSSDK/sdk/android/demo/src/main/AndroidManifest.xml
Normal 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>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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?)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.newsdk.ams.emas.demo.ui.resolve
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/7
|
||||
*/
|
||||
enum class SchemaType {
|
||||
HTTPS,
|
||||
|
||||
HTTP
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">48dp</dimen>
|
||||
</resources>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">200dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">48dp</dimen>
|
||||
</resources>
|
||||
@@ -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瑙f瀽</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">寮傛瑙f瀽鑾峰彇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鍩熷悕宸茬粡琚坊鍔犺嚦鎵归噺瑙f瀽鍒楄〃锛岃鍕块噸澶嶆坊鍔</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瑙f瀽鐨勫煙鍚</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鐢ㄤ簬鍞竴鏍囪瘑缂撳瓨涓殑瑙f瀽缁撴灉</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>
|
||||
@@ -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>
|
||||
13
HttpDNSSDK/sdk/android/demo/src/main/res/values/colors.xml
Normal file
13
HttpDNSSDK/sdk/android/demo/src/main/res/values/colors.xml
Normal 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>
|
||||
11
HttpDNSSDK/sdk/android/demo/src/main/res/values/dimens.xml
Normal file
11
HttpDNSSDK/sdk/android/demo/src/main/res/values/dimens.xml
Normal 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>
|
||||
141
HttpDNSSDK/sdk/android/demo/src/main/res/values/strings.xml
Normal file
141
HttpDNSSDK/sdk/android/demo/src/main/res/values/strings.xml
Normal 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>
|
||||
|
||||
17
HttpDNSSDK/sdk/android/demo/src/main/res/values/styles.xml
Normal file
17
HttpDNSSDK/sdk/android/demo/src/main/res/values/styles.xml
Normal 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>
|
||||
20
HttpDNSSDK/sdk/android/demo/src/main/res/values/themes.xml
Normal file
20
HttpDNSSDK/sdk/android/demo/src/main/res/values/themes.xml
Normal 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>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true" />
|
||||
</network-security-config>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user