parent
							
								
									3803d53afe
								
							
						
					
					
						commit
						d5ec200617
					
				| 
						 | 
					@ -4,12 +4,12 @@ plugins {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
android {
 | 
					android {
 | 
				
			||||||
    compileSdk 32
 | 
					    compileSdk 34
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    defaultConfig {
 | 
					    defaultConfig {
 | 
				
			||||||
        applicationId "com.example.guruanalytics"
 | 
					        applicationId "com.example.guruanalytics"
 | 
				
			||||||
        minSdk 21
 | 
					        minSdk 23
 | 
				
			||||||
        targetSdk 32
 | 
					        targetSdk 33
 | 
				
			||||||
        versionCode 1
 | 
					        versionCode 1
 | 
				
			||||||
        versionName "1.1.0"
 | 
					        versionName "1.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,6 +41,10 @@ dependencies {
 | 
				
			||||||
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
 | 
					    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
 | 
				
			||||||
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
 | 
					    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    implementation "androidx.work:work-runtime:2.7.1"
 | 
				
			||||||
 | 
					    implementation "androidx.work:work-runtime-ktx:2.7.1"
 | 
				
			||||||
 | 
					    implementation "androidx.work:work-rxjava2:2.7.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    implementation(project(':guru_analytics'))
 | 
					    implementation(project(':guru_analytics'))
 | 
				
			||||||
//    implementation 'guru.core.analytics:guru_analytics:0.1.0'
 | 
					//    implementation 'guru.core.analytics:guru_analytics:0.1.0'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,150 @@
 | 
				
			||||||
package com.example.guruanalytics
 | 
					package com.example.guruanalytics
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import androidx.appcompat.app.AppCompatActivity
 | 
					import android.app.usage.NetworkStats
 | 
				
			||||||
 | 
					import android.app.usage.NetworkStatsManager
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.pm.PackageManager
 | 
				
			||||||
 | 
					import android.net.ConnectivityManager
 | 
				
			||||||
 | 
					import android.net.NetworkCapabilities
 | 
				
			||||||
 | 
					import android.net.TrafficStats
 | 
				
			||||||
 | 
					import android.os.Build
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
 | 
					import android.os.RemoteException
 | 
				
			||||||
import android.util.Log
 | 
					import android.util.Log
 | 
				
			||||||
import android.widget.TextView
 | 
					import android.widget.TextView
 | 
				
			||||||
 | 
					import androidx.annotation.RequiresApi
 | 
				
			||||||
 | 
					import androidx.appcompat.app.AppCompatActivity
 | 
				
			||||||
 | 
					import androidx.work.BackoffPolicy
 | 
				
			||||||
 | 
					import androidx.work.Constraints
 | 
				
			||||||
 | 
					import androidx.work.ExistingWorkPolicy
 | 
				
			||||||
 | 
					import androidx.work.NetworkType
 | 
				
			||||||
 | 
					import androidx.work.OneTimeWorkRequestBuilder
 | 
				
			||||||
 | 
					import androidx.work.WorkManager
 | 
				
			||||||
import guru.core.analytics.GuruAnalytics
 | 
					import guru.core.analytics.GuruAnalytics
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.DnsMode
 | 
				
			||||||
import guru.core.analytics.data.db.model.EventPriority
 | 
					import guru.core.analytics.data.db.model.EventPriority
 | 
				
			||||||
import guru.core.analytics.data.model.AnalyticsOptions
 | 
					import guru.core.analytics.data.model.AnalyticsOptions
 | 
				
			||||||
 | 
					import guru.core.analytics.impl.ActiveWorker
 | 
				
			||||||
 | 
					import guru.core.analytics.impl.AnalyticsWorker
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object NetworkUsageUtils {
 | 
				
			||||||
 | 
					    fun getCurrentAppNetworkUsage(context: Context) {
 | 
				
			||||||
 | 
					        val uid = getApplicationUid(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val mobileRxBytes = TrafficStats.getUidRxBytes(uid) // 获取移动数据接收的字节数
 | 
				
			||||||
 | 
					        val mobileTxBytes = TrafficStats.getUidTxBytes(uid) // 获取移动数据发送的字节数
 | 
				
			||||||
 | 
					        val wifiRxBytes = TrafficStats.getTotalRxBytes() - TrafficStats.getMobileRxBytes() // 获取 Wi-Fi 接收的字节数
 | 
				
			||||||
 | 
					        val wifiTxBytes = TrafficStats.getTotalTxBytes() - TrafficStats.getMobileTxBytes() // 获取 Wi-Fi 发送的字节数
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (mobileRxBytes == TrafficStats.UNSUPPORTED.toLong() || mobileTxBytes == TrafficStats.UNSUPPORTED.toLong()) {
 | 
				
			||||||
 | 
					            println("该设备不支持 TrafficStats API")
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            println("当前应用的移动数据接收字节数: $mobileRxBytes")
 | 
				
			||||||
 | 
					            println("当前应用的移动数据发送字节数: $mobileTxBytes")
 | 
				
			||||||
 | 
					            println("当前应用的 Wi-Fi 数据接收字节数: $wifiRxBytes")
 | 
				
			||||||
 | 
					            println("当前应用的 Wi-Fi 数据发送字节数: $wifiTxBytes")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getApplicationUid(context: Context): Int {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            val applicationInfo = context.packageManager.getApplicationInfo(context.packageName, 0)
 | 
				
			||||||
 | 
					            return applicationInfo.uid
 | 
				
			||||||
 | 
					        } catch (e: PackageManager.NameNotFoundException) {
 | 
				
			||||||
 | 
					            e.printStackTrace()
 | 
				
			||||||
 | 
					            return -1
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun isBackgroundDataRestricted(context: Context): Boolean {
 | 
				
			||||||
 | 
					        val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
 | 
				
			||||||
 | 
					        if (connectivityManager != null) {
 | 
				
			||||||
 | 
					            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 | 
				
			||||||
 | 
					                val status = connectivityManager.restrictBackgroundStatus
 | 
				
			||||||
 | 
					                if (status == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED) {
 | 
				
			||||||
 | 
					                    // 限制背景数据
 | 
				
			||||||
 | 
					                    return true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @RequiresApi(api = Build.VERSION_CODES.M)
 | 
				
			||||||
 | 
					    fun getMobileDataUsage(context: Context, uid: Int): Long {
 | 
				
			||||||
 | 
					        val networkStatsManager = context.getSystemService(Context.NETWORK_STATS_SERVICE) as NetworkStatsManager
 | 
				
			||||||
 | 
					        val bucket: NetworkStats.Bucket
 | 
				
			||||||
 | 
					        var dataUsage = 0L
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            bucket = networkStatsManager.querySummaryForDevice(NetworkCapabilities.TRANSPORT_CELLULAR, null, 0, System.currentTimeMillis())
 | 
				
			||||||
 | 
					            dataUsage = bucket.rxBytes + bucket.txBytes
 | 
				
			||||||
 | 
					        } catch (e: RemoteException) {
 | 
				
			||||||
 | 
					            e.printStackTrace()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return dataUsage
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun dumpCapabilitiesList(capabilities: NetworkCapabilities?) {
 | 
				
			||||||
 | 
					        val types: MutableList<String> = ArrayList()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (capabilities == null
 | 
				
			||||||
 | 
					            || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					//            types.add(Connectivity.CONNECTIVITY_NONE)
 | 
				
			||||||
 | 
					            Log.d("hasCapability", "NetworkCapabilities.NET_CAPABILITY_INTERNET")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities == null
 | 
				
			||||||
 | 
					            || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            Log.d("hasTransport", "NetworkCapabilities.TRANSPORT_WIFI")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities == null || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            Log.d("hasTransport", "NetworkCapabilities.TRANSPORT_WIFI_AWARE")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities == null || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
 | 
				
			||||||
 | 
					            Log.d("hasTransport", "NetworkCapabilities.TRANSPORT_ETHERNET")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities == null || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
 | 
				
			||||||
 | 
					            Log.d("hasTransport", "NetworkCapabilities.TRANSPORT_VPN")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities == null || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
 | 
				
			||||||
 | 
					            Log.d("hasTransport", "NetworkCapabilities.TRANSPORT_CELLULAR")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities == null || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
 | 
				
			||||||
 | 
					            Log.d("hasTransport", "NetworkCapabilities.TRANSPORT_BLUETOOTH")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities == null || capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            Log.d("hasCapability", "NetworkCapabilities.NET_CAPABILITY_INTERNET")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun dumpCap(context: Context) {
 | 
				
			||||||
 | 
					        val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
 | 
				
			||||||
 | 
					        val network = connectivityManager.activeNetwork
 | 
				
			||||||
 | 
					        val capabilities = connectivityManager.getNetworkCapabilities(network)
 | 
				
			||||||
 | 
					        dumpCapabilitiesList(capabilities)
 | 
				
			||||||
 | 
					        val isActiveNetworkMetered = connectivityManager.isActiveNetworkMetered;
 | 
				
			||||||
 | 
					        Log.d("dumpCap", "isActiveNetworkMetered: $isActiveNetworkMetered")
 | 
				
			||||||
 | 
					//        val appUid = getApplicationUid(context)
 | 
				
			||||||
 | 
					//        Log.d("dumpCap", "appUid: $appUid")
 | 
				
			||||||
 | 
					//        val rx = getMobileDataUsage(context, appUid)
 | 
				
			||||||
 | 
					//        Log.d("dumpCap", "dataUsage: $rx")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MainActivity : AppCompatActivity() {
 | 
					class MainActivity : AppCompatActivity() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var enableUpload = true
 | 
					    private var enableUpload = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @RequiresApi(Build.VERSION_CODES.N)
 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
        setContentView(R.layout.activity_main)
 | 
					        setContentView(R.layout.activity_main)
 | 
				
			||||||
| 
						 | 
					@ -24,6 +157,28 @@ class MainActivity : AppCompatActivity() {
 | 
				
			||||||
//            callbackEventHandler = true,
 | 
					//            callbackEventHandler = true,
 | 
				
			||||||
//        )
 | 
					//        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        findViewById<TextView>(R.id.tvSetProperty).setOnClickListener {
 | 
				
			||||||
 | 
					            GuruAnalytics.INSTANCE.setScreen("main")
 | 
				
			||||||
 | 
					            GuruAnalytics.INSTANCE.setAdId("AD_ID_01")
 | 
				
			||||||
 | 
					            GuruAnalytics.INSTANCE.setFirebaseId("FIREBASE_ID")
 | 
				
			||||||
 | 
					            GuruAnalytics.INSTANCE.setUid("MBK-XXXXX")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        findViewById<TextView>(R.id.tvLogEvent).setOnClickListener {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (i in 0..200) {
 | 
				
			||||||
 | 
					                val map = mutableMapOf<String, Any>()
 | 
				
			||||||
 | 
					                map["key_$i"] = "value_$i"
 | 
				
			||||||
 | 
					                map["percent"] = 0.4
 | 
				
			||||||
 | 
					                map["level"] = 2
 | 
				
			||||||
 | 
					                map["from"] = "game"
 | 
				
			||||||
 | 
					                GuruAnalytics.INSTANCE.logEvent("test_event", "game", "main", 10, map)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        findViewById<TextView>(R.id.tvOpenTestProcessActivity).setOnClickListener {
 | 
				
			||||||
 | 
					//            TestProcessActivity.startActivity(this)
 | 
				
			||||||
            GuruAnalytics.Builder(this)
 | 
					            GuruAnalytics.Builder(this)
 | 
				
			||||||
                .setBatchLimit(25)
 | 
					                .setBatchLimit(25)
 | 
				
			||||||
                .setUploadPeriodInSeconds(60)
 | 
					                .setUploadPeriodInSeconds(60)
 | 
				
			||||||
| 
						 | 
					@ -31,39 +186,72 @@ class MainActivity : AppCompatActivity() {
 | 
				
			||||||
                .setEventExpiredInDays(7)
 | 
					                .setEventExpiredInDays(7)
 | 
				
			||||||
                .isPersistableLog(true)
 | 
					                .isPersistableLog(true)
 | 
				
			||||||
                .setEventHandlerCallback(eventHandler)
 | 
					                .setEventHandlerCallback(eventHandler)
 | 
				
			||||||
            .isInitPeriodicWork(false)
 | 
					                .isInitPeriodicWork(true)
 | 
				
			||||||
                .isDebug(BuildConfig.DEBUG)
 | 
					                .isDebug(BuildConfig.DEBUG)
 | 
				
			||||||
//            .setUploadEventBaseUrl("https://www.baidu.com")
 | 
					//            .setUploadEventBaseUrl("https://www.baidu.com")
 | 
				
			||||||
//            .setFgEventPeriodInSeconds(60L)
 | 
					//            .setFgEventPeriodInSeconds(60L)
 | 
				
			||||||
                .setXAppId("test_x_app_id")
 | 
					                .setXAppId("test_x_app_id")
 | 
				
			||||||
                .setXDeviceInfo("test_x_device_info")
 | 
					                .setXDeviceInfo("test_x_device_info")
 | 
				
			||||||
                .setMainProcess("com.example.guruanalytics")
 | 
					                .setMainProcess("com.example.guruanalytics")
 | 
				
			||||||
            .isEnableCronet(true)
 | 
					//            .isEnableCronet(true)
 | 
				
			||||||
            .setUploadIpAddress(listOf("3.210.96.186", "34.196.69.199"))
 | 
					                .setUploadEventBaseUrl("https://collect4.fungame.cloud")
 | 
				
			||||||
 | 
					                .setDnsMode(DnsMode.COMPOSITE)
 | 
				
			||||||
                .build()
 | 
					                .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        findViewById<TextView>(R.id.tvLogEvent).setOnClickListener {
 | 
					            Log.w("GuruAnalytics", "GuruAnalytics.INSTANCE: completed")
 | 
				
			||||||
            val map = mutableMapOf<String, Any>()
 | 
					 | 
				
			||||||
            map["percent"] = 0.4
 | 
					 | 
				
			||||||
            map["level"] = 2
 | 
					 | 
				
			||||||
            map["from"] = "game"
 | 
					 | 
				
			||||||
            GuruAnalytics.INSTANCE.logEvent("test_event", "game", "main", 10, map)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        findViewById<TextView>(R.id.tvOpenTestProcessActivity).setOnClickListener {
 | 
					 | 
				
			||||||
            TestProcessActivity.startActivity(this)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        findViewById<TextView>(R.id.tvLocalLog).setOnClickListener {
 | 
					        findViewById<TextView>(R.id.tvLocalLog).setOnClickListener {
 | 
				
			||||||
            val startTime = System.currentTimeMillis()
 | 
					            val startTime = System.currentTimeMillis()
 | 
				
			||||||
            GuruAnalytics.INSTANCE.zipLogs(this)
 | 
					//            GuruAnalytics.INSTANCE.zipLogs(this)
 | 
				
			||||||
            Log.i("get_local_log", "${System.currentTimeMillis() - startTime}")
 | 
					//            Log.i("get_local_log", "${System.currentTimeMillis() - startTime}")
 | 
				
			||||||
        }
 | 
					//            val request = OneTimeWorkRequestBuilder<AnalyticsWorker>(
 | 
				
			||||||
        findViewById<TextView>(R.id.tvEventStatistic).setOnClickListener {
 | 
					//            ).build()
 | 
				
			||||||
            GuruAnalytics.INSTANCE.getEventsStatics().run {
 | 
					////                .setBackoffCriteria(BackoffPolicy.LINEAR, 15, TimeUnit.MINUTES)
 | 
				
			||||||
 | 
					////                .addTag(AnalyticsWorker.WORKER_TAG)
 | 
				
			||||||
 | 
					////                .setConstraints(constraints)
 | 
				
			||||||
 | 
					////                .build()
 | 
				
			||||||
 | 
					//            WorkManager.getInstance(this)
 | 
				
			||||||
 | 
					//                .enqueue(
 | 
				
			||||||
 | 
					//                    request
 | 
				
			||||||
 | 
					//                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val constraints = Constraints.Builder()
 | 
				
			||||||
 | 
					                .setRequiredNetworkType(NetworkType.UNMETERED)
 | 
				
			||||||
 | 
					                .setTriggerContentMaxDelay(1, TimeUnit.MINUTES)
 | 
				
			||||||
 | 
					                .build()
 | 
				
			||||||
 | 
					            val request = OneTimeWorkRequestBuilder<ActiveWorker>()
 | 
				
			||||||
 | 
					                .setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES)
 | 
				
			||||||
 | 
					                .addTag(ActiveWorker.WORKER_TAG)
 | 
				
			||||||
 | 
					                .setConstraints(constraints)
 | 
				
			||||||
 | 
					                .setInitialDelay(5, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					                .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            WorkManager.getInstance(this)
 | 
				
			||||||
 | 
					                .enqueueUniqueWork(
 | 
				
			||||||
 | 
					                    ActiveWorker.WORKER_NAME,
 | 
				
			||||||
 | 
					                    ExistingWorkPolicy.KEEP,
 | 
				
			||||||
 | 
					                    request
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            Log.i(
 | 
					            Log.i(
 | 
				
			||||||
                    "UploadEventDaemon_main",
 | 
					                "Worker",
 | 
				
			||||||
                    "$eventCountAll $eventCountDeleted $eventCountUploaded"
 | 
					                "dispatchActiveWorker"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        findViewById<TextView>(R.id.tvEventStatistic).setOnClickListener {
 | 
				
			||||||
 | 
					            GuruAnalytics.INSTANCE.snapshotAnalyticsAudit().run {
 | 
				
			||||||
 | 
					                Log.i(
 | 
				
			||||||
 | 
					                    "UploadEventDaemon_main",
 | 
				
			||||||
 | 
					                    this
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val eventStatistic = GuruAnalytics.INSTANCE.getEventsStatics()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//            val restricted = NetworkUsageUtils.isBackgroundDataRestricted(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Log.i("getEventsStatics", "eventStatistic:$eventStatistic")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            NetworkUsageUtils.dumpCap(this)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        findViewById<TextView>(R.id.tvEventEmergence).setOnClickListener {
 | 
					        findViewById<TextView>(R.id.tvEventEmergence).setOnClickListener {
 | 
				
			||||||
            val map = mutableMapOf<String, Any>()
 | 
					            val map = mutableMapOf<String, Any>()
 | 
				
			||||||
| 
						 | 
					@ -77,7 +265,7 @@ class MainActivity : AppCompatActivity() {
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        findViewById<TextView>(R.id.tvBaseUrl).setOnClickListener {
 | 
					        findViewById<TextView>(R.id.tvBaseUrl).setOnClickListener {
 | 
				
			||||||
            GuruAnalytics.INSTANCE.setUploadEventBaseUrl(this, "https://www.castbox.fm/")
 | 
					//            GuruAnalytics.INSTANCE.setUploadEventBaseUrl(this, "https://www.castbox.fm/")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val tvEnable = findViewById<TextView>(R.id.tvEnable)
 | 
					        val tvEnable = findViewById<TextView>(R.id.tvEnable)
 | 
				
			||||||
| 
						 | 
					@ -88,11 +276,20 @@ class MainActivity : AppCompatActivity() {
 | 
				
			||||||
            tvEnable.text = if (enableUpload) "Enable Upload" else "Disable Upload"
 | 
					            tvEnable.text = if (enableUpload) "Enable Upload" else "Disable Upload"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val tvClearStatistic = findViewById<TextView>(R.id.tvClearStatistic)
 | 
				
			||||||
 | 
					        tvClearStatistic?.setOnClickListener {
 | 
				
			||||||
 | 
					            GuruAnalytics.INSTANCE.clearStatistic(this)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GuruAnalytics.INSTANCE.setScreen("main")
 | 
					        GuruAnalytics.INSTANCE.setScreen("main")
 | 
				
			||||||
        GuruAnalytics.INSTANCE.setAdId("AD_ID_01")
 | 
					        GuruAnalytics.INSTANCE.setAdId("AD_ID_01")
 | 
				
			||||||
 | 
					        GuruAnalytics.INSTANCE.setFirebaseId("FIREBASE_ID")
 | 
				
			||||||
 | 
					        GuruAnalytics.INSTANCE.setUid("MBK-YYYYY")
 | 
				
			||||||
        GuruAnalytics.INSTANCE.setUserProperty("uid", "110051")
 | 
					        GuruAnalytics.INSTANCE.setUserProperty("uid", "110051")
 | 
				
			||||||
        GuruAnalytics.INSTANCE.setUserProperty("age", "12")
 | 
					        GuruAnalytics.INSTANCE.setUserProperty("age", "12")
 | 
				
			||||||
        GuruAnalytics.INSTANCE.setUserProperty("sex", "male")
 | 
					        GuruAnalytics.INSTANCE.setUserProperty("sex", "male")
 | 
				
			||||||
 | 
					        Log.d("Test", "setAdId")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val properties = GuruAnalytics.INSTANCE.peakUserProperties()
 | 
					        val properties = GuruAnalytics.INSTANCE.peakUserProperties()
 | 
				
			||||||
        Log.i("peakUserProperties", "properties:$properties")
 | 
					        Log.i("peakUserProperties", "properties:$properties")
 | 
				
			||||||
| 
						 | 
					@ -100,11 +297,6 @@ class MainActivity : AppCompatActivity() {
 | 
				
			||||||
        GuruAnalytics.INSTANCE.getUserProperties {
 | 
					        GuruAnalytics.INSTANCE.getUserProperties {
 | 
				
			||||||
            Log.i("getUserProperties", "properties:$it")
 | 
					            Log.i("getUserProperties", "properties:$it")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        tvEnable?.postDelayed({
 | 
					 | 
				
			||||||
            val snapshot = GuruAnalytics.INSTANCE.snapshotAnalyticsAudit()
 | 
					 | 
				
			||||||
            Log.i("snapshotAnalyticsAudit", "snapshot:$snapshot")
 | 
					 | 
				
			||||||
        }, 10_000)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val eventHandler: (Int, String?) -> Unit = { code, ext ->
 | 
					    private val eventHandler: (Int, String?) -> Unit = { code, ext ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,8 @@ class TestProcessActivity : AppCompatActivity() {
 | 
				
			||||||
            .setXAppId("test_x_app_id")
 | 
					            .setXAppId("test_x_app_id")
 | 
				
			||||||
            .setXDeviceInfo("test_x_device_info")
 | 
					            .setXDeviceInfo("test_x_device_info")
 | 
				
			||||||
            .setMainProcess("com.example.guruanalytics")
 | 
					            .setMainProcess("com.example.guruanalytics")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .setUploadIpAddress(listOf("13.248.248.135", "3.33.195.44"))
 | 
				
			||||||
            .build()
 | 
					            .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        findViewById<TextView>(R.id.tvFinish).setOnClickListener {
 | 
					        findViewById<TextView>(R.id.tvFinish).setOnClickListener {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,72 +11,82 @@
 | 
				
			||||||
        android:id="@+id/tvSetProperty"
 | 
					        android:id="@+id/tvSetProperty"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
        android:layout_marginTop="50dp"
 | 
					        android:layout_marginTop="50dp"
 | 
				
			||||||
        android:padding="10dp"
 | 
					        android:padding="10dp"
 | 
				
			||||||
        android:text="SET PROPERTY"
 | 
					        android:text="SET PROPERTY" />
 | 
				
			||||||
        android:layout_gravity="center_horizontal"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/tvLogEvent"
 | 
					        android:id="@+id/tvLogEvent"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
        android:layout_marginTop="30dp"
 | 
					        android:layout_marginTop="30dp"
 | 
				
			||||||
        android:padding="10dp"
 | 
					        android:padding="10dp"
 | 
				
			||||||
        android:text="LOG EVENT"
 | 
					        android:text="LOG EVENT" />
 | 
				
			||||||
        android:layout_gravity="center_horizontal"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/tvEventEmergence"
 | 
					        android:id="@+id/tvEventEmergence"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
        android:layout_marginTop="30dp"
 | 
					        android:layout_marginTop="30dp"
 | 
				
			||||||
        android:padding="10dp"
 | 
					        android:padding="10dp"
 | 
				
			||||||
        android:text="Event_EMERGENCE"
 | 
					        android:text="Event_EMERGENCE" />
 | 
				
			||||||
        android:layout_gravity="center_horizontal"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/tvOpenTestProcessActivity"
 | 
					        android:id="@+id/tvOpenTestProcessActivity"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
        android:layout_marginTop="30dp"
 | 
					        android:layout_marginTop="30dp"
 | 
				
			||||||
        android:padding="10dp"
 | 
					        android:padding="10dp"
 | 
				
			||||||
        android:text="Open TestProcessActivity"
 | 
					        android:text="Open TestProcessActivity" />
 | 
				
			||||||
        android:layout_gravity="center_horizontal" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/tvLocalLog"
 | 
					        android:id="@+id/tvLocalLog"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
        android:layout_marginTop="30dp"
 | 
					        android:layout_marginTop="30dp"
 | 
				
			||||||
        android:padding="10dp"
 | 
					        android:padding="10dp"
 | 
				
			||||||
        android:text="get local log file"
 | 
					        android:text="get local log file" />
 | 
				
			||||||
        android:layout_gravity="center_horizontal"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/tvEventStatistic"
 | 
					        android:id="@+id/tvEventStatistic"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
        android:layout_marginTop="30dp"
 | 
					        android:layout_marginTop="30dp"
 | 
				
			||||||
        android:padding="10dp"
 | 
					        android:padding="10dp"
 | 
				
			||||||
        android:text="Event Statistic"
 | 
					        android:text="Event Statistic" />
 | 
				
			||||||
        android:layout_gravity="center_horizontal" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/tvBaseUrl"
 | 
					        android:id="@+id/tvBaseUrl"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
        android:layout_marginTop="30dp"
 | 
					        android:layout_marginTop="30dp"
 | 
				
			||||||
        android:padding="10dp"
 | 
					        android:padding="10dp"
 | 
				
			||||||
        android:text="Update BaseUrl"
 | 
					        android:text="Update BaseUrl" />
 | 
				
			||||||
        android:layout_gravity="center_horizontal" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <TextView
 | 
					    <TextView
 | 
				
			||||||
        android:id="@+id/tvEnable"
 | 
					        android:id="@+id/tvEnable"
 | 
				
			||||||
        android:layout_width="wrap_content"
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
        android:layout_marginTop="30dp"
 | 
					        android:layout_marginTop="30dp"
 | 
				
			||||||
        android:padding="10dp"
 | 
					        android:padding="10dp"
 | 
				
			||||||
        android:text="Enable Upload"
 | 
					        android:text="Enable Upload" />
 | 
				
			||||||
        android:layout_gravity="center_horizontal" />
 | 
					
 | 
				
			||||||
 | 
					    <TextView
 | 
				
			||||||
 | 
					        android:id="@+id/tvClearStatistic"
 | 
				
			||||||
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_gravity="center_horizontal"
 | 
				
			||||||
 | 
					        android:layout_marginTop="30dp"
 | 
				
			||||||
 | 
					        android:padding="10dp"
 | 
				
			||||||
 | 
					        android:text="Clear Statistic"
 | 
				
			||||||
 | 
					        android:visibility="visible" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</LinearLayout>
 | 
					</LinearLayout>
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
buildscript {
 | 
					buildscript {
 | 
				
			||||||
    ext.kotlin_version = '1.6.10'
 | 
					    ext.kotlin_version = '1.9.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    repositories {
 | 
					    repositories {
 | 
				
			||||||
//        maven { url 'http://localhost:8081/repository/maven-public/' }
 | 
					//        maven { url 'http://localhost:8081/repository/maven-public/' }
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ buildscript {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dependencies {
 | 
					    dependencies {
 | 
				
			||||||
        classpath "com.android.tools.build:gradle:4.2.1"
 | 
					        classpath "com.android.tools.build:gradle:7.1.2"
 | 
				
			||||||
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 | 
					        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
distributionBase=GRADLE_USER_HOME
 | 
					distributionBase=GRADLE_USER_HOME
 | 
				
			||||||
distributionPath=wrapper/dists
 | 
					distributionPath=wrapper/dists
 | 
				
			||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
 | 
					distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
 | 
				
			||||||
zipStoreBase=GRADLE_USER_HOME
 | 
					zipStoreBase=GRADLE_USER_HOME
 | 
				
			||||||
zipStorePath=wrapper/dists
 | 
					zipStorePath=wrapper/dists
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					import java.text.SimpleDateFormat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
plugins {
 | 
					plugins {
 | 
				
			||||||
    id 'com.android.library'
 | 
					    id 'com.android.library'
 | 
				
			||||||
    id 'org.jetbrains.kotlin.android'
 | 
					    id 'org.jetbrains.kotlin.android'
 | 
				
			||||||
| 
						 | 
					@ -8,6 +10,15 @@ plugins {
 | 
				
			||||||
apply from: 'dependencies.gradle'
 | 
					apply from: 'dependencies.gradle'
 | 
				
			||||||
apply from: 'maven-publish.gradle'
 | 
					apply from: 'maven-publish.gradle'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static def buildTs() {
 | 
				
			||||||
 | 
					    def date = GregorianCalendar.getInstance(TimeZone.getTimeZone('UTC'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss")
 | 
				
			||||||
 | 
					    String ts = dateFormat.format(date.getTime())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return "\"" + ts + "\""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
android {
 | 
					android {
 | 
				
			||||||
    compileSdk android.compileSdk
 | 
					    compileSdk android.compileSdk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +29,8 @@ android {
 | 
				
			||||||
        versionCode 1
 | 
					        versionCode 1
 | 
				
			||||||
        versionName "1.0"
 | 
					        versionName "1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        buildConfigField "String", "buildTs", buildTs()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
					        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,26 @@
 | 
				
			||||||
ext {
 | 
					ext {
 | 
				
			||||||
    compiler = [
 | 
					    compiler = [
 | 
				
			||||||
            java  : JavaVersion.VERSION_1_8,
 | 
					            java  : JavaVersion.VERSION_17,
 | 
				
			||||||
            kotlin: '1.6.10'
 | 
					            kotlin: '1.9.0'
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    android = [
 | 
					    android = [
 | 
				
			||||||
            buildTools: '30.0.3',
 | 
					            buildTools: '30.0.3',
 | 
				
			||||||
            minSdk    : 21,
 | 
					            minSdk    : 21,
 | 
				
			||||||
            targetSdk : 32,
 | 
					            targetSdk : 33,
 | 
				
			||||||
            compileSdk: 32
 | 
					            compileSdk: 34
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    androidXCoreVersion = '1.7.0'
 | 
					    androidXCoreVersion = '1.7.0'
 | 
				
			||||||
    timberVersion = '4.7.1'
 | 
					    timberVersion = '4.7.1'
 | 
				
			||||||
    roomVersion = '2.4.3'
 | 
					    roomVersion = '2.6.1'
 | 
				
			||||||
    gsonVersion = '2.8.5'
 | 
					    gsonVersion = '2.8.5'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    retrofitVersion = '2.7.1'
 | 
					    retrofitVersion = '2.7.1'
 | 
				
			||||||
    okhttpVersion = '4.9.3'
 | 
					    okhttpVersion = '4.12.0'
 | 
				
			||||||
    preferenceVersion = '1.2.0'
 | 
					    preferenceVersion = '1.2.0'
 | 
				
			||||||
    processVersion = '2.4.0'
 | 
					    processVersion = '2.4.0'
 | 
				
			||||||
    workVersion = '2.7.1'
 | 
					    workVersion = '2.9.0'
 | 
				
			||||||
    cronetOkhttpVersion = '0.1.0'
 | 
					    cronetOkhttpVersion = '0.1.0'
 | 
				
			||||||
    playServicesCronetVersion = '18.0.1'
 | 
					    playServicesCronetVersion = '18.0.1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,7 +47,8 @@ ext {
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    okhttpDependencies = [
 | 
					    okhttpDependencies = [
 | 
				
			||||||
            "com.squareup.okhttp3:okhttp:$okhttpVersion"
 | 
					            "com.squareup.okhttp3:okhttp:$okhttpVersion",
 | 
				
			||||||
 | 
					            "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion"
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    workerDependencies = [
 | 
					    workerDependencies = [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ publishing {    // Repositories *to* which Gradle can publish artifacts
 | 
				
			||||||
        maven(MavenPublication) {
 | 
					        maven(MavenPublication) {
 | 
				
			||||||
            groupId 'guru.core.analytics'
 | 
					            groupId 'guru.core.analytics'
 | 
				
			||||||
            artifactId 'guru_analytics'
 | 
					            artifactId 'guru_analytics'
 | 
				
			||||||
            version '1.0.3'             // Your package version
 | 
					            version '1.1.0'             // Your package version
 | 
				
			||||||
//                artifact publishArtifact    //Example: *./target/myJavaClasses.jar*
 | 
					//                artifact publishArtifact    //Example: *./target/myJavaClasses.jar*
 | 
				
			||||||
//            artifact "build/outputs/aar/aar-test-release.aar"//aar包的目录
 | 
					//            artifact "build/outputs/aar/aar-test-release.aar"//aar包的目录
 | 
				
			||||||
            afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
 | 
					            afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,6 +43,7 @@ object Constants {
 | 
				
			||||||
        const val SCREEN_W = "screenW"
 | 
					        const val SCREEN_W = "screenW"
 | 
				
			||||||
        const val OS_VERSION = "osVersion"
 | 
					        const val OS_VERSION = "osVersion"
 | 
				
			||||||
        const val LANGUAGE = "language"
 | 
					        const val LANGUAGE = "language"
 | 
				
			||||||
 | 
					        const val SDK_INFO = "sdkInfo"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    object Properties {
 | 
					    object Properties {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
package guru.core.analytics
 | 
					package guru.core.analytics
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.DnsMode
 | 
				
			||||||
import guru.core.analytics.data.db.model.EventStatistic
 | 
					import guru.core.analytics.data.db.model.EventStatistic
 | 
				
			||||||
import guru.core.analytics.data.model.AnalyticsInfo
 | 
					import guru.core.analytics.data.model.AnalyticsInfo
 | 
				
			||||||
import guru.core.analytics.data.model.AnalyticsOptions
 | 
					import guru.core.analytics.data.model.AnalyticsOptions
 | 
				
			||||||
| 
						 | 
					@ -29,6 +30,7 @@ abstract class GuruAnalytics {
 | 
				
			||||||
        mainProcess: String? = null,
 | 
					        mainProcess: String? = null,
 | 
				
			||||||
        isEnableCronet: Boolean? = null,
 | 
					        isEnableCronet: Boolean? = null,
 | 
				
			||||||
        uploadIpAddress: List<String>? = null,
 | 
					        uploadIpAddress: List<String>? = null,
 | 
				
			||||||
 | 
					        dnsMode: Int? = null
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    abstract fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String)
 | 
					    abstract fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String)
 | 
				
			||||||
| 
						 | 
					@ -83,6 +85,10 @@ abstract class GuruAnalytics {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    abstract fun snapshotAnalyticsAudit(): String
 | 
					    abstract fun snapshotAnalyticsAudit(): String
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    abstract fun clearStatistic(context: Context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    abstract fun forceUpload(scene: String = "unknown"): Boolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        val INSTANCE: GuruAnalytics by lazy() {
 | 
					        val INSTANCE: GuruAnalytics by lazy() {
 | 
				
			||||||
            GuruAnalyticsImpl()
 | 
					            GuruAnalyticsImpl()
 | 
				
			||||||
| 
						 | 
					@ -134,6 +140,9 @@ abstract class GuruAnalytics {
 | 
				
			||||||
        fun setUploadIpAddress(uploadIpAddress: List<String>) =
 | 
					        fun setUploadIpAddress(uploadIpAddress: List<String>) =
 | 
				
			||||||
            apply { analyticsInfo.uploadIpAddress = uploadIpAddress }
 | 
					            apply { analyticsInfo.uploadIpAddress = uploadIpAddress }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fun setDnsMode(@DnsMode dnsMode: Int) =
 | 
				
			||||||
 | 
					            apply { analyticsInfo.dnsMode = dnsMode }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fun build(): GuruAnalytics {
 | 
					        fun build(): GuruAnalytics {
 | 
				
			||||||
            analyticsInfo.run {
 | 
					            analyticsInfo.run {
 | 
				
			||||||
                INSTANCE.initialize(
 | 
					                INSTANCE.initialize(
 | 
				
			||||||
| 
						 | 
					@ -153,6 +162,7 @@ abstract class GuruAnalytics {
 | 
				
			||||||
                    mainProcess,
 | 
					                    mainProcess,
 | 
				
			||||||
                    isEnableCronet,
 | 
					                    isEnableCronet,
 | 
				
			||||||
                    uploadIpAddress,
 | 
					                    uploadIpAddress,
 | 
				
			||||||
 | 
					                    dnsMode
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return INSTANCE
 | 
					            return INSTANCE
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,33 +4,36 @@ import android.content.Context
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
import android.os.SystemClock
 | 
					import android.os.SystemClock
 | 
				
			||||||
import guru.core.analytics.data.api.cronet.CastboxCronetInterceptor
 | 
					import guru.core.analytics.data.api.cronet.CastboxCronetInterceptor
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.CompositeDns
 | 
				
			||||||
import guru.core.analytics.data.api.dns.CustomDns
 | 
					import guru.core.analytics.data.api.dns.CustomDns
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.DnsMode
 | 
				
			||||||
import guru.core.analytics.data.api.dns.GoogleDnsApi
 | 
					import guru.core.analytics.data.api.dns.GoogleDnsApi
 | 
				
			||||||
import guru.core.analytics.data.api.dns.GoogleDnsApiHost
 | 
					import guru.core.analytics.data.api.dns.GoogleDnsApiHost
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.StaticDns
 | 
				
			||||||
import guru.core.analytics.data.api.logging.Level
 | 
					import guru.core.analytics.data.api.logging.Level
 | 
				
			||||||
import guru.core.analytics.data.api.logging.LoggingInterceptor
 | 
					import guru.core.analytics.data.api.logging.LoggingInterceptor
 | 
				
			||||||
import guru.core.analytics.data.local.PreferencesManager
 | 
					import guru.core.analytics.data.local.PreferencesManager
 | 
				
			||||||
 | 
					import guru.core.analytics.data.model.GuruAnalyticsAudit
 | 
				
			||||||
import guru.core.analytics.handler.AnalyticsCode
 | 
					import guru.core.analytics.handler.AnalyticsCode
 | 
				
			||||||
import guru.core.analytics.handler.EventHandler
 | 
					import guru.core.analytics.handler.EventHandler
 | 
				
			||||||
import guru.core.analytics.utils.AndroidUtils
 | 
					 | 
				
			||||||
import guru.core.analytics.utils.DateTimeUtils
 | 
					import guru.core.analytics.utils.DateTimeUtils
 | 
				
			||||||
import guru.core.analytics.utils.GsonUtil
 | 
					import guru.core.analytics.utils.GsonUtil
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
import okhttp3.Dispatcher
 | 
					import okhttp3.Dispatcher
 | 
				
			||||||
import okhttp3.Headers
 | 
					import okhttp3.Headers
 | 
				
			||||||
import okhttp3.Interceptor
 | 
					import okhttp3.Interceptor
 | 
				
			||||||
import okhttp3.OkHttpClient
 | 
					import okhttp3.OkHttpClient
 | 
				
			||||||
import okhttp3.Protocol
 | 
					 | 
				
			||||||
import okhttp3.Request
 | 
					 | 
				
			||||||
import okhttp3.Response
 | 
					 | 
				
			||||||
import okhttp3.ResponseBody
 | 
					 | 
				
			||||||
import okhttp3.ResponseBody.Companion.toResponseBody
 | 
					 | 
				
			||||||
import okhttp3.internal.platform.Platform
 | 
					import okhttp3.internal.platform.Platform
 | 
				
			||||||
import retrofit2.Converter
 | 
					import retrofit2.Converter
 | 
				
			||||||
import retrofit2.Retrofit
 | 
					import retrofit2.Retrofit
 | 
				
			||||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
 | 
					import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
 | 
				
			||||||
import retrofit2.converter.gson.GsonConverterFactory
 | 
					import retrofit2.converter.gson.GsonConverterFactory
 | 
				
			||||||
 | 
					import timber.log.Timber
 | 
				
			||||||
import java.util.concurrent.TimeUnit
 | 
					import java.util.concurrent.TimeUnit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Todo: 该类都需要重构,业务不清晰,不符合打点库使用
 | 
				
			||||||
object ServiceLocator {
 | 
					object ServiceLocator {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var debug = false
 | 
					    private var debug = false
 | 
				
			||||||
| 
						 | 
					@ -38,11 +41,16 @@ object ServiceLocator {
 | 
				
			||||||
    @Volatile
 | 
					    @Volatile
 | 
				
			||||||
    private var guruRepository: GuruRepository? = null
 | 
					    private var guruRepository: GuruRepository? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Volatile
 | 
					 | 
				
			||||||
    private var googleDnsApi: GoogleDnsApi? = null
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private val headerParams = mutableMapOf<String, String>()
 | 
					    private val headerParams = mutableMapOf<String, String>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val compositeDns: CompositeDns by lazy {
 | 
				
			||||||
 | 
					        return@lazy CompositeDns()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var isEnabledCronet: Boolean = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var dnsMode: Int = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var uploadIpAddress: List<String>? = null
 | 
					    private var uploadIpAddress: List<String>? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun addHeaderParam(key: String, value: String?) {
 | 
					    fun addHeaderParam(key: String, value: String?) {
 | 
				
			||||||
| 
						 | 
					@ -54,10 +62,36 @@ object ServiceLocator {
 | 
				
			||||||
        this.debug = debug
 | 
					        this.debug = debug
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Synchronized
 | 
				
			||||||
    fun setUploadIpAddress(ipList: List<String>?) {
 | 
					    fun setUploadIpAddress(ipList: List<String>?) {
 | 
				
			||||||
 | 
					        if (compositeDns.ipAddress != ipList) {
 | 
				
			||||||
 | 
					            compositeDns.setCandidateIpAddress(ipAddress = ipList)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        uploadIpAddress = ipList
 | 
					        uploadIpAddress = ipList
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun setCronet(isEnabledCronet: Boolean) {
 | 
				
			||||||
 | 
					        this.isEnabledCronet = isEnabledCronet
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun setDnsMode(dnsMode: Int) {
 | 
				
			||||||
 | 
					        this.dnsMode = dnsMode
 | 
				
			||||||
 | 
					        GuruAnalyticsAudit.dnsMode = dnsMode
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun preloadDns(hostname: String?) {
 | 
				
			||||||
 | 
					        Timber.tag("ServiceLocator").i("preloadDns: $hostname")
 | 
				
			||||||
 | 
					        if (dnsMode == DnsMode.COMPOSITE && !hostname.isNullOrBlank()) {
 | 
				
			||||||
 | 
					            CoroutineScope(Dispatchers.IO).launch {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    val result = compositeDns.lookup(hostname)
 | 
				
			||||||
 | 
					                    Timber.tag(CompositeDns.tag).i("preloadDns: $hostname, result: $result")
 | 
				
			||||||
 | 
					                } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun provideGuruRepository(context: Context, baseUri: Uri? = null): GuruRepository {
 | 
					    fun provideGuruRepository(context: Context, baseUri: Uri? = null): GuruRepository {
 | 
				
			||||||
        synchronized(this) {
 | 
					        synchronized(this) {
 | 
				
			||||||
            return guruRepository
 | 
					            return guruRepository
 | 
				
			||||||
| 
						 | 
					@ -97,14 +131,8 @@ object ServiceLocator {
 | 
				
			||||||
        guruRepository?.analyticsApi = createAnalyticsApi(context, baseUrl)
 | 
					        guruRepository?.analyticsApi = createAnalyticsApi(context, baseUrl)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun provideGoogleDnsApi(context: Context): GoogleDnsApi {
 | 
					 | 
				
			||||||
        synchronized(this) {
 | 
					 | 
				
			||||||
            return googleDnsApi
 | 
					 | 
				
			||||||
                ?: createGoogleDnsApi(context).apply { googleDnsApi = this }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun createGoogleDnsApi(context: Context): GoogleDnsApi {
 | 
					    fun createGoogleDnsApi(context: Context): GoogleDnsApi {
 | 
				
			||||||
        return GoogleDnsApi.Creator.newInstance(
 | 
					        return GoogleDnsApi.Creator.newInstance(
 | 
				
			||||||
            Retrofit.Builder()
 | 
					            Retrofit.Builder()
 | 
				
			||||||
                .baseUrl(GoogleDnsApiHost.API)
 | 
					                .baseUrl(GoogleDnsApiHost.API)
 | 
				
			||||||
| 
						 | 
					@ -120,30 +148,37 @@ object ServiceLocator {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun createOkHttpClient(
 | 
					    private fun createOkHttpClient(
 | 
				
			||||||
        context: Context,
 | 
					        context: Context,
 | 
				
			||||||
        readTimeOut: Long = 30L,
 | 
					        readTimeOut: Long = 90L,
 | 
				
			||||||
        writeTimeOut: Long = 30L
 | 
					        writeTimeOut: Long = 90L,
 | 
				
			||||||
    ): OkHttpClient {
 | 
					    ): OkHttpClient {
 | 
				
			||||||
 | 
					        val dns = when (dnsMode) {
 | 
				
			||||||
 | 
					            DnsMode.COMPOSITE -> compositeDns
 | 
				
			||||||
 | 
					            DnsMode.STATIC -> StaticDns(uploadIpAddress)
 | 
				
			||||||
 | 
					            else -> CustomDns(context, uploadIpAddress = uploadIpAddress)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        val builder = OkHttpClient.Builder()
 | 
					        val builder = OkHttpClient.Builder()
 | 
				
			||||||
            .dispatcher(Dispatcher().apply {
 | 
					            .dispatcher(Dispatcher().apply {
 | 
				
			||||||
                maxRequests = 128
 | 
					                maxRequests = 128
 | 
				
			||||||
                maxRequestsPerHost = 10
 | 
					                maxRequestsPerHost = 5
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .dns(CustomDns(context, uploadIpAddress))
 | 
					            .dns(dns)
 | 
				
			||||||
            .connectTimeout(20L, TimeUnit.SECONDS)
 | 
					            .connectTimeout(90L, TimeUnit.SECONDS)
 | 
				
			||||||
            .readTimeout(readTimeOut, TimeUnit.SECONDS)
 | 
					            .readTimeout(readTimeOut, TimeUnit.SECONDS)
 | 
				
			||||||
            .writeTimeout(writeTimeOut, TimeUnit.SECONDS)
 | 
					            .writeTimeout(writeTimeOut, TimeUnit.SECONDS)
 | 
				
			||||||
            .addInterceptor(createCacheControlInterceptor(context))
 | 
					 | 
				
			||||||
            .addInterceptor(createAnalyticsApiInterceptor())
 | 
					            .addInterceptor(createAnalyticsApiInterceptor())
 | 
				
			||||||
            .addInterceptor(createLoggingInterceptor())
 | 
					            .addInterceptor(createLoggingInterceptor())
 | 
				
			||||||
            .addInterceptor(createCronetInterceptor())
 | 
					        if (isEnabledCronet) {
 | 
				
			||||||
 | 
					            builder.addInterceptor(createCronetInterceptor())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return builder.build()
 | 
					        return builder.build()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun createDnsOkHttpClient(context: Context): OkHttpClient {
 | 
					    private fun createDnsOkHttpClient(context: Context): OkHttpClient {
 | 
				
			||||||
        val builder = OkHttpClient.Builder()
 | 
					        val builder = OkHttpClient.Builder()
 | 
				
			||||||
            .addInterceptor(createCacheControlInterceptor(context))
 | 
					            .connectTimeout(90L, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .readTimeout(90L, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .writeTimeout(90L, TimeUnit.SECONDS)
 | 
				
			||||||
            .addInterceptor(createLoggingInterceptor())
 | 
					            .addInterceptor(createLoggingInterceptor())
 | 
				
			||||||
            .addInterceptor(createCronetInterceptor())
 | 
					 | 
				
			||||||
        return builder.build()
 | 
					        return builder.build()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -164,32 +199,6 @@ object ServiceLocator {
 | 
				
			||||||
        return CastboxCronetInterceptor()
 | 
					        return CastboxCronetInterceptor()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun createCacheControlInterceptor(context: Context) = Interceptor { chain ->
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            val originalResponse = chain.proceed(chain.request())
 | 
					 | 
				
			||||||
            when {
 | 
					 | 
				
			||||||
                originalResponse.headers.names().contains("Cache-Control") -> {
 | 
					 | 
				
			||||||
                    return@Interceptor originalResponse.newBuilder().build()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                AndroidUtils.isInternetAvailable(context) -> {
 | 
					 | 
				
			||||||
                    val maxAge = 60 * 5 // read from cache for 5 minute
 | 
					 | 
				
			||||||
                    return@Interceptor originalResponse.newBuilder()
 | 
					 | 
				
			||||||
                        .header("Cache-Control", "public, max-age=$maxAge")
 | 
					 | 
				
			||||||
                        .build()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else -> {
 | 
					 | 
				
			||||||
                    val maxStale = 60 * 60 * 24 * 28 // tolerate 4-weeks stale
 | 
					 | 
				
			||||||
                    return@Interceptor originalResponse.newBuilder()
 | 
					 | 
				
			||||||
                        .header("Cache-Control", "public, only-if-cached, max-stale=$maxStale")
 | 
					 | 
				
			||||||
                        .build()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } catch (e: Exception) {
 | 
					 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_CACHE_CONTROL, e.message)
 | 
					 | 
				
			||||||
            throw e
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun createAnalyticsApiInterceptor(): Interceptor {
 | 
					    private fun createAnalyticsApiInterceptor(): Interceptor {
 | 
				
			||||||
        return Interceptor { chain ->
 | 
					        return Interceptor { chain ->
 | 
				
			||||||
            val request = chain.request()
 | 
					            val request = chain.request()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					package guru.core.analytics.data.api.dns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import okhttp3.Dns
 | 
				
			||||||
 | 
					import java.net.InetAddress
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CandidateDns(private var ipAddress: List<String>? = null) : Dns {
 | 
				
			||||||
 | 
					    private fun convert(ip: String): InetAddress? {
 | 
				
			||||||
 | 
					        return runCatching {
 | 
				
			||||||
 | 
					            val byteArr = ip.split(".").map { Integer.parseInt(it).toByte() }.toByteArray()
 | 
				
			||||||
 | 
					            InetAddress.getByAddress(byteArr)
 | 
				
			||||||
 | 
					        }.getOrNull()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun setIpAddress(ipAddress: List<String>? = null) {
 | 
				
			||||||
 | 
					        this.ipAddress = ipAddress
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun lookup(hostname: String): List<InetAddress> {
 | 
				
			||||||
 | 
					        val resultIpList = ipAddress?.mapNotNull { convert(it) }
 | 
				
			||||||
 | 
					        if (!resultIpList.isNullOrEmpty()) {
 | 
				
			||||||
 | 
					            return resultIpList
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Dns.SYSTEM.lookup(hostname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					package guru.core.analytics.data.api.dns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import guru.core.analytics.data.model.GuruAnalyticsAudit
 | 
				
			||||||
 | 
					import guru.core.analytics.handler.AnalyticsCode
 | 
				
			||||||
 | 
					import guru.core.analytics.handler.EventHandler
 | 
				
			||||||
 | 
					import okhttp3.Cache
 | 
				
			||||||
 | 
					import okhttp3.Dns
 | 
				
			||||||
 | 
					import okhttp3.HttpUrl.Companion.toHttpUrl
 | 
				
			||||||
 | 
					import okhttp3.OkHttpClient
 | 
				
			||||||
 | 
					import okhttp3.dnsoverhttps.DnsOverHttps
 | 
				
			||||||
 | 
					import timber.log.Timber
 | 
				
			||||||
 | 
					import java.io.File
 | 
				
			||||||
 | 
					import java.net.InetAddress
 | 
				
			||||||
 | 
					import java.net.UnknownHostException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CompositeDns(
 | 
				
			||||||
 | 
					    val ipAddress: List<String>? = null
 | 
				
			||||||
 | 
					) : Dns {
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        @JvmStatic
 | 
				
			||||||
 | 
					        val tag = "CompositeDns"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val dnsOverHttps: Dns by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
 | 
				
			||||||
 | 
					        val dnsCache = Cache(File("cacheDir", "dnscache"), 10 * 1024 * 1024)
 | 
				
			||||||
 | 
					        val bootstrapClient = OkHttpClient.Builder().cache(dnsCache).build()
 | 
				
			||||||
 | 
					        return@lazy DnsOverHttps.Builder().client(bootstrapClient)
 | 
				
			||||||
 | 
					            .url("https://dns.google/dns-query".toHttpUrl())
 | 
				
			||||||
 | 
					            .bootstrapDnsHosts(InetAddress.getByName("8.8.4.4"), InetAddress.getByName("8.8.8.8"))
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val googleDns: GoogleDns by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
 | 
				
			||||||
 | 
					        GoogleDns()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val candidateDns: CandidateDns by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
 | 
				
			||||||
 | 
					        CandidateDns(ipAddress)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val lookupService: List<(hostname: String) -> List<InetAddress>> by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
 | 
				
			||||||
 | 
					        listOf(
 | 
				
			||||||
 | 
					            { hostname -> dnsOverHttps.lookup(hostname) },
 | 
				
			||||||
 | 
					            { hostname -> candidateDns.lookup(hostname) }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun setCandidateIpAddress(ipAddress: List<String>? = null) {
 | 
				
			||||||
 | 
					        candidateDns.setIpAddress(ipAddress)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun lookup(hostname: String): List<InetAddress> {
 | 
				
			||||||
 | 
					        var primaryException: UnknownHostException? = null
 | 
				
			||||||
 | 
					        for ((depth, invokeLookup) in lookupService.withIndex()) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                val ipList = invokeLookup(hostname)
 | 
				
			||||||
 | 
					                Timber.tag(tag).i("lookup: $hostname, depth: $depth, ipList: $ipList")
 | 
				
			||||||
 | 
					                GuruAnalyticsAudit.serverIp = ipList.toString()
 | 
				
			||||||
 | 
					                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_LOOKUP, "[Composite($depth)]:${ipList}")
 | 
				
			||||||
 | 
					                return ipList
 | 
				
			||||||
 | 
					            } catch (e: UnknownHostException) {
 | 
				
			||||||
 | 
					                primaryException = primaryException ?: e
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw (primaryException ?: UnknownHostException("Broken dns lookup of $hostname"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import android.content.Context
 | 
				
			||||||
import com.google.gson.reflect.TypeToken
 | 
					import com.google.gson.reflect.TypeToken
 | 
				
			||||||
import guru.core.analytics.data.api.ServiceLocator
 | 
					import guru.core.analytics.data.api.ServiceLocator
 | 
				
			||||||
import guru.core.analytics.data.local.PreferencesManager
 | 
					import guru.core.analytics.data.local.PreferencesManager
 | 
				
			||||||
 | 
					import guru.core.analytics.data.model.GuruAnalyticsAudit
 | 
				
			||||||
import guru.core.analytics.handler.AnalyticsCode
 | 
					import guru.core.analytics.handler.AnalyticsCode
 | 
				
			||||||
import guru.core.analytics.handler.EventHandler
 | 
					import guru.core.analytics.handler.EventHandler
 | 
				
			||||||
import guru.core.analytics.utils.GsonUtil
 | 
					import guru.core.analytics.utils.GsonUtil
 | 
				
			||||||
| 
						 | 
					@ -14,18 +15,28 @@ import java.net.UnknownHostException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CustomDns(private val context: Context, private val uploadIpAddress: List<String>? = null) : Dns {
 | 
					class CustomDns(private val context: Context, private val uploadIpAddress: List<String>? = null) : Dns {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Volatile
 | 
				
			||||||
 | 
					    private var googleDnsApi: GoogleDnsApi? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val cachedHostAddress by lazy {
 | 
					    private val cachedHostAddress by lazy {
 | 
				
			||||||
        val hostAddressJson = PreferencesManager.getInstance(context).hostAddressJson
 | 
					        val hostAddressJson = PreferencesManager.getInstance(context).hostAddressJson
 | 
				
			||||||
        return@lazy runCatching {
 | 
					        return@lazy runCatching {
 | 
				
			||||||
            if (!hostAddressJson.isNullOrBlank()) {
 | 
					            if (!hostAddressJson.isNullOrBlank()) {
 | 
				
			||||||
                val mapType = object: TypeToken<Map<String, List<String>>>() {}.type
 | 
					                val mapType = object : TypeToken<Map<String, List<String>>>() {}.type
 | 
				
			||||||
                GsonUtil.gson.fromJson(hostAddressJson, mapType) as? MutableMap<String, List<String>>
 | 
					                GsonUtil.gson.fromJson(hostAddressJson, mapType) as? MutableMap<String, List<String>>
 | 
				
			||||||
            } else null
 | 
					            } else null
 | 
				
			||||||
        }.getOrNull() ?: mutableMapOf()
 | 
					        }.getOrNull() ?: mutableMapOf()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun provideGoogleDnsApi(context: Context): GoogleDnsApi {
 | 
				
			||||||
 | 
					        synchronized(this) {
 | 
				
			||||||
 | 
					            return googleDnsApi
 | 
				
			||||||
 | 
					                ?: ServiceLocator.createGoogleDnsApi(context).apply { googleDnsApi = this }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun lookup(hostname: String): List<InetAddress> {
 | 
					    override fun lookup(hostname: String): List<InetAddress> {
 | 
				
			||||||
        return try {
 | 
					        val ipList = try {
 | 
				
			||||||
            Dns.SYSTEM.lookup(hostname).also { list ->
 | 
					            Dns.SYSTEM.lookup(hostname).also { list ->
 | 
				
			||||||
                cacheHostAddress(hostname, list.map { it.hostAddress })
 | 
					                cacheHostAddress(hostname, list.map { it.hostAddress })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -41,10 +52,13 @@ class CustomDns(private val context: Context, private val uploadIpAddress: List<
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        GuruAnalyticsAudit.serverIp = ipList.toString()
 | 
				
			||||||
 | 
					        EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_LOOKUP, "[Custom]:${ipList}")
 | 
				
			||||||
 | 
					        return ipList
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun lookupByGoogleDns(hostname: String): List<InetAddress> {
 | 
					    private fun lookupByGoogleDns(hostname: String): List<InetAddress> {
 | 
				
			||||||
        val dnsApi = ServiceLocator.provideGoogleDnsApi(context)
 | 
					        val dnsApi = provideGoogleDnsApi(context)
 | 
				
			||||||
        val ipList = runBlocking {
 | 
					        val ipList = runBlocking {
 | 
				
			||||||
            return@runBlocking dnsApi.ip(hostname).answer?.toMutableList()
 | 
					            return@runBlocking dnsApi.ip(hostname).answer?.toMutableList()
 | 
				
			||||||
                ?.filter { it.type == 1 }
 | 
					                ?.filter { it.type == 1 }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					package guru.core.analytics.data.api.dns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.annotation.IntDef
 | 
				
			||||||
 | 
					import guru.core.analytics.data.db.model.EventPriority
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@IntDef(DnsMode.DEFAULT, DnsMode.COMPOSITE, DnsMode.STATIC)
 | 
				
			||||||
 | 
					@Retention(AnnotationRetention.BINARY)
 | 
				
			||||||
 | 
					@Target(
 | 
				
			||||||
 | 
					    AnnotationTarget.FUNCTION,
 | 
				
			||||||
 | 
					    AnnotationTarget.VALUE_PARAMETER,
 | 
				
			||||||
 | 
					    AnnotationTarget.FIELD,
 | 
				
			||||||
 | 
					    AnnotationTarget.LOCAL_VARIABLE,
 | 
				
			||||||
 | 
					    AnnotationTarget.CLASS
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					annotation class DnsMode {
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        const val DEFAULT = 0
 | 
				
			||||||
 | 
					        const val COMPOSITE = 1
 | 
				
			||||||
 | 
					        const val STATIC = 2
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					package guru.core.analytics.data.api.dns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.logging.Level
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.logging.LoggingInterceptor
 | 
				
			||||||
 | 
					import guru.core.analytics.utils.GsonUtil
 | 
				
			||||||
 | 
					import kotlinx.coroutines.runBlocking
 | 
				
			||||||
 | 
					import okhttp3.Dns
 | 
				
			||||||
 | 
					import okhttp3.OkHttpClient
 | 
				
			||||||
 | 
					import okhttp3.internal.platform.Platform
 | 
				
			||||||
 | 
					import retrofit2.Retrofit
 | 
				
			||||||
 | 
					import retrofit2.converter.gson.GsonConverterFactory
 | 
				
			||||||
 | 
					import java.net.InetAddress
 | 
				
			||||||
 | 
					import java.net.UnknownHostException
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GoogleDns : Dns {
 | 
				
			||||||
 | 
					    private val loggingInterceptor by lazy {
 | 
				
			||||||
 | 
					        LoggingInterceptor.Builder()
 | 
				
			||||||
 | 
					            .setLevel(Level.BASIC)
 | 
				
			||||||
 | 
					            .log(Platform.INFO)
 | 
				
			||||||
 | 
					            .request("DnsRequest")
 | 
				
			||||||
 | 
					            .response("DnsResponse")
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val googleDnsOkHttpClient by lazy {
 | 
				
			||||||
 | 
					        return@lazy OkHttpClient.Builder()
 | 
				
			||||||
 | 
					            .connectTimeout(90L, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .readTimeout(90L, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .writeTimeout(90L, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .addInterceptor(loggingInterceptor)
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val googleDnsApi: GoogleDnsApi by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
 | 
				
			||||||
 | 
					        return@lazy GoogleDnsApi.Creator.newInstance(
 | 
				
			||||||
 | 
					            Retrofit.Builder()
 | 
				
			||||||
 | 
					                .baseUrl(GoogleDnsApiHost.API)
 | 
				
			||||||
 | 
					                .client(googleDnsOkHttpClient)
 | 
				
			||||||
 | 
					                .addConverterFactory(GsonConverterFactory.create(GsonUtil.gson))
 | 
				
			||||||
 | 
					                .build()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun convert(ip: String): InetAddress? {
 | 
				
			||||||
 | 
					        return runCatching {
 | 
				
			||||||
 | 
					            val byteArr = ip.split(".").map { Integer.parseInt(it).toByte() }.toByteArray()
 | 
				
			||||||
 | 
					            InetAddress.getByAddress(byteArr)
 | 
				
			||||||
 | 
					        }.getOrNull()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun lookup(hostname: String): List<InetAddress> {
 | 
				
			||||||
 | 
					        val ipList = runBlocking {
 | 
				
			||||||
 | 
					            googleDnsApi.ip(hostname).answer?.toMutableList()
 | 
				
			||||||
 | 
					                ?.filter { it.type == 1 }
 | 
				
			||||||
 | 
					                ?.mapNotNull { it.data }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!ipList.isNullOrEmpty()) {
 | 
				
			||||||
 | 
					            val resultIpList = ipList.mapNotNull { convert(it) }
 | 
				
			||||||
 | 
					            if (resultIpList.isNotEmpty()) {
 | 
				
			||||||
 | 
					                return resultIpList
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw UnknownHostException("Broken Google dns lookup of $hostname")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,30 @@
 | 
				
			||||||
 | 
					package guru.core.analytics.data.api.dns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import guru.core.analytics.data.model.GuruAnalyticsAudit
 | 
				
			||||||
 | 
					import guru.core.analytics.handler.AnalyticsCode
 | 
				
			||||||
 | 
					import guru.core.analytics.handler.EventHandler
 | 
				
			||||||
 | 
					import okhttp3.Dns
 | 
				
			||||||
 | 
					import java.net.InetAddress
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StaticDns(private val ipAddress: List<String>? = null) : Dns {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun convert(ip: String): InetAddress? {
 | 
				
			||||||
 | 
					        return runCatching {
 | 
				
			||||||
 | 
					            val byteArr = ip.split(".").map { Integer.parseInt(it).toByte() }.toByteArray()
 | 
				
			||||||
 | 
					            InetAddress.getByAddress(byteArr)
 | 
				
			||||||
 | 
					        }.getOrNull()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun lookup(hostname: String): List<InetAddress> {
 | 
				
			||||||
 | 
					        val resultIpList = ipAddress?.mapNotNull { convert(it) }
 | 
				
			||||||
 | 
					        if (!resultIpList.isNullOrEmpty()) {
 | 
				
			||||||
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_LOOKUP, "[Static]:${resultIpList}")
 | 
				
			||||||
 | 
					            GuruAnalyticsAudit.serverIp = resultIpList.toString()
 | 
				
			||||||
 | 
					            return resultIpList
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val ipList = Dns.SYSTEM.lookup(hostname)
 | 
				
			||||||
 | 
					        GuruAnalyticsAudit.serverIp = ipList.toString()
 | 
				
			||||||
 | 
					        EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_LOOKUP, "[Default]:${ipList}")
 | 
				
			||||||
 | 
					        return ipList
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,6 @@ import guru.core.analytics.data.db.migrations.MIGRATIONS
 | 
				
			||||||
import guru.core.analytics.data.db.model.EventEntity
 | 
					import guru.core.analytics.data.db.model.EventEntity
 | 
				
			||||||
import guru.core.analytics.data.db.utils.Converters
 | 
					import guru.core.analytics.data.db.utils.Converters
 | 
				
			||||||
import guru.core.analytics.data.db.utils.TransactionResult
 | 
					import guru.core.analytics.data.db.utils.TransactionResult
 | 
				
			||||||
import guru.core.analytics.data.db.utils.runInTransactionEx
 | 
					 | 
				
			||||||
import io.reactivex.Maybe
 | 
					import io.reactivex.Maybe
 | 
				
			||||||
import timber.log.Timber
 | 
					import timber.log.Timber
 | 
				
			||||||
import java.lang.ref.SoftReference
 | 
					import java.lang.ref.SoftReference
 | 
				
			||||||
| 
						 | 
					@ -66,12 +65,12 @@ abstract class GuruAnalyticsDatabase : RoomDatabase() {
 | 
				
			||||||
                .build()
 | 
					                .build()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fun <T> runInTransaction(callback: () -> TransactionResult<T>, defVal: T?): Maybe<T> {
 | 
					//        fun <T> runInTransaction(callback: () -> TransactionResult<T>, defVal: T?): Maybe<T> {
 | 
				
			||||||
            return INSTANCE?.runInTransactionEx(callback, defVal) ?: Maybe.empty()
 | 
					//            return INSTANCE?.runInTransactionEx(callback, defVal) ?: Maybe.empty()
 | 
				
			||||||
        }
 | 
					//        }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
        fun <T> runInTransaction(callback: () -> TransactionResult<T>): Maybe<T> {
 | 
					//        fun <T> runInTransaction(callback: () -> TransactionResult<T>): Maybe<T> {
 | 
				
			||||||
            return INSTANCE?.runInTransactionEx(callback) ?: Maybe.empty()
 | 
					//            return INSTANCE?.runInTransactionEx(callback) ?: Maybe.empty()
 | 
				
			||||||
        }
 | 
					//        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -51,13 +51,6 @@ abstract class EventDao {
 | 
				
			||||||
    @Transaction
 | 
					    @Transaction
 | 
				
			||||||
    open fun loadAndMarkUploadEvents(limit: Int): List<EventEntity> {
 | 
					    open fun loadAndMarkUploadEvents(limit: Int): List<EventEntity> {
 | 
				
			||||||
        val events = getEvents(limit).toMutableList()
 | 
					        val events = getEvents(limit).toMutableList()
 | 
				
			||||||
//        if (events.isNotEmpty()) {
 | 
					 | 
				
			||||||
//            Timber.tag(TAG).d("loadAndMarkUploadEvents limit:$limit size:${events.size}")
 | 
					 | 
				
			||||||
//            FgEventHelper.getInstance().getFgEvent()?.let { entity ->
 | 
					 | 
				
			||||||
//                addEvent(entity)
 | 
					 | 
				
			||||||
//                events.add(entity)
 | 
					 | 
				
			||||||
//            }
 | 
					 | 
				
			||||||
//        }
 | 
					 | 
				
			||||||
        updateEventUploading(events)
 | 
					        updateEventUploading(events)
 | 
				
			||||||
        return events
 | 
					        return events
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,4 +4,8 @@ data class EventStatistic(
 | 
				
			||||||
    val eventCountAll: Int = 0,
 | 
					    val eventCountAll: Int = 0,
 | 
				
			||||||
    val eventCountDeleted: Int = 0,
 | 
					    val eventCountDeleted: Int = 0,
 | 
				
			||||||
    val eventCountUploaded: Int = 0,
 | 
					    val eventCountUploaded: Int = 0,
 | 
				
			||||||
)
 | 
					) {
 | 
				
			||||||
 | 
					    override fun toString(): String {
 | 
				
			||||||
 | 
					        return "eventCountAll=$eventCountAll, eventCountDeleted=$eventCountDeleted, eventCountUploaded=$eventCountUploaded"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -30,31 +30,31 @@ data class TransactionResult<T>(
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun <T> RoomDatabase.runInTransactionEx(
 | 
					//fun <T> RoomDatabase.runInTransactionEx(
 | 
				
			||||||
    callback: () -> TransactionResult<T>,
 | 
					//    callback: () -> TransactionResult<T>,
 | 
				
			||||||
    defVal: T?
 | 
					//    defVal: T?
 | 
				
			||||||
): Maybe<T> {
 | 
					//): Maybe<T> {
 | 
				
			||||||
    return Maybe.create { emitter ->
 | 
					//    return Maybe.create { emitter ->
 | 
				
			||||||
        val result = this.runInTransaction(callback)
 | 
					//        val result = this.runInTransaction(callback)
 | 
				
			||||||
            ?: TransactionResult(defVal, ResultBehavior.SUCCESS)
 | 
					//            ?: TransactionResult(defVal, ResultBehavior.SUCCESS)
 | 
				
			||||||
        when (result.behavior) {
 | 
					//        when (result.behavior) {
 | 
				
			||||||
            ResultBehavior.SUCCESS -> {
 | 
					//            ResultBehavior.SUCCESS -> {
 | 
				
			||||||
                val value = result.value
 | 
					//                val value = result.value
 | 
				
			||||||
                if (value != null) {
 | 
					//                if (value != null) {
 | 
				
			||||||
                    emitter.onSuccess(value)
 | 
					//                    emitter.onSuccess(value)
 | 
				
			||||||
                }
 | 
					//                }
 | 
				
			||||||
                emitter.onComplete()
 | 
					//                emitter.onComplete()
 | 
				
			||||||
            }
 | 
					//            }
 | 
				
			||||||
            ResultBehavior.ERROR -> {
 | 
					//            ResultBehavior.ERROR -> {
 | 
				
			||||||
                emitter.onError(DatabaseException("runInTransaction error!", result.cause))
 | 
					//                emitter.onError(DatabaseException("runInTransaction error!", result.cause))
 | 
				
			||||||
            }
 | 
					//            }
 | 
				
			||||||
            else -> {
 | 
					//            else -> {
 | 
				
			||||||
                emitter.onComplete()
 | 
					//                emitter.onComplete()
 | 
				
			||||||
            }
 | 
					//            }
 | 
				
			||||||
        }
 | 
					//        }
 | 
				
			||||||
    }
 | 
					//    }
 | 
				
			||||||
}
 | 
					//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun <T> RoomDatabase.runInTransactionEx(callback: () -> TransactionResult<T>): Maybe<T> {
 | 
					//fun <T> RoomDatabase.runInTransactionEx(callback: () -> TransactionResult<T>): Maybe<T> {
 | 
				
			||||||
    return runInTransactionEx(callback, null)
 | 
					//    return runInTransactionEx(callback, null)
 | 
				
			||||||
}
 | 
					//}
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package guru.core.analytics.data.local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.annotation.SuppressLint
 | 
					import android.annotation.SuppressLint
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.DnsMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PreferencesManager private constructor(
 | 
					class PreferencesManager private constructor(
 | 
				
			||||||
    context: Context,
 | 
					    context: Context,
 | 
				
			||||||
| 
						 | 
					@ -49,4 +50,15 @@ class PreferencesManager private constructor(
 | 
				
			||||||
    var uploadEventBaseUrl: String? by bind("update_event_base_url", "")
 | 
					    var uploadEventBaseUrl: String? by bind("update_event_base_url", "")
 | 
				
			||||||
    var totalDurationFgEvent: Long? by bind("total_duration_fg_event", 0L)
 | 
					    var totalDurationFgEvent: Long? by bind("total_duration_fg_event", 0L)
 | 
				
			||||||
    var hostAddressJson: String? by bind("host_address", "")
 | 
					    var hostAddressJson: String? by bind("host_address", "")
 | 
				
			||||||
 | 
					    var uploadIpAddressList: String? by bind("upload_ip_address", "")
 | 
				
			||||||
 | 
					    var useCompositeDns: Boolean? by bind("use_composite_dns", false)
 | 
				
			||||||
 | 
					    var dnsMode: Int? by bind("dns_mode", DnsMode.DEFAULT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var firebaseId: String? by bind("firebase_id", "")
 | 
				
			||||||
 | 
					    var userId: String? by bind("user_id", "")
 | 
				
			||||||
 | 
					    var adjustId: String? by bind("adjust_id", "")
 | 
				
			||||||
 | 
					    var deviceId: String? by bind("device_id", "")
 | 
				
			||||||
 | 
					    var adId: String? by bind("ad_id", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,4 +16,5 @@ internal data class AnalyticsInfo(
 | 
				
			||||||
    var mainProcess: String? = null,
 | 
					    var mainProcess: String? = null,
 | 
				
			||||||
    var isEnableCronet: Boolean? = null,
 | 
					    var isEnableCronet: Boolean? = null,
 | 
				
			||||||
    var uploadIpAddress: List<String>? = null,
 | 
					    var uploadIpAddress: List<String>? = null,
 | 
				
			||||||
 | 
					    var dnsMode: Int? = null
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package guru.core.analytics.data.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import androidx.annotation.Keep
 | 
					import androidx.annotation.Keep
 | 
				
			||||||
import com.google.gson.annotations.SerializedName
 | 
					import com.google.gson.annotations.SerializedName
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.DnsMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Keep
 | 
					@Keep
 | 
				
			||||||
data class GuruAnalyticsAuditSnapshot(
 | 
					data class GuruAnalyticsAuditSnapshot(
 | 
				
			||||||
| 
						 | 
					@ -18,12 +19,16 @@ data class GuruAnalyticsAuditSnapshot(
 | 
				
			||||||
    @SerializedName("sessionDeleted") val sessionDeleted: Int = 0,
 | 
					    @SerializedName("sessionDeleted") val sessionDeleted: Int = 0,
 | 
				
			||||||
    @SerializedName("sessionTotal") val sessionTotal: Int = 0,
 | 
					    @SerializedName("sessionTotal") val sessionTotal: Int = 0,
 | 
				
			||||||
    @SerializedName("uploadReady") val uploadReady: Boolean = false,
 | 
					    @SerializedName("uploadReady") val uploadReady: Boolean = false,
 | 
				
			||||||
 | 
					    @SerializedName("dnsMode") val dnsMode: Int = DnsMode.DEFAULT,
 | 
				
			||||||
 | 
					    @SerializedName("serverIp") var serverIp: String = ""
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object GuruAnalyticsAudit {
 | 
					object GuruAnalyticsAudit {
 | 
				
			||||||
    var initialized: Boolean = false
 | 
					    var initialized: Boolean = false
 | 
				
			||||||
    var engineInitialized: Boolean = false
 | 
					    var engineInitialized: Boolean = false
 | 
				
			||||||
    var useCronet: Boolean = false
 | 
					    var useCronet: Boolean = false
 | 
				
			||||||
 | 
					    var dnsMode: Int = DnsMode.DEFAULT
 | 
				
			||||||
 | 
					    var enabledCronet: Boolean = false
 | 
				
			||||||
    var eventDispatcherStarted: Boolean = false
 | 
					    var eventDispatcherStarted: Boolean = false
 | 
				
			||||||
    var fgHelperInitialized: Boolean = false
 | 
					    var fgHelperInitialized: Boolean = false
 | 
				
			||||||
    var connectionState: Boolean = false
 | 
					    var connectionState: Boolean = false
 | 
				
			||||||
| 
						 | 
					@ -40,10 +45,13 @@ object GuruAnalyticsAudit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var uploadReady: Boolean = false
 | 
					    var uploadReady: Boolean = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var serverIp: String = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun snapshot() = GuruAnalyticsAuditSnapshot(
 | 
					    fun snapshot() = GuruAnalyticsAuditSnapshot(
 | 
				
			||||||
        initialized = initialized,
 | 
					        initialized = initialized,
 | 
				
			||||||
        engineInitialized = engineInitialized,
 | 
					        engineInitialized = engineInitialized,
 | 
				
			||||||
        useCronet = useCronet,
 | 
					        useCronet = useCronet,
 | 
				
			||||||
 | 
					        dnsMode = dnsMode,
 | 
				
			||||||
        eventDispatcherStarted = eventDispatcherStarted,
 | 
					        eventDispatcherStarted = eventDispatcherStarted,
 | 
				
			||||||
        fgHelperInitialized = fgHelperInitialized,
 | 
					        fgHelperInitialized = fgHelperInitialized,
 | 
				
			||||||
        connectionState = connectionState,
 | 
					        connectionState = connectionState,
 | 
				
			||||||
| 
						 | 
					@ -53,7 +61,8 @@ object GuruAnalyticsAudit {
 | 
				
			||||||
        sessionUploaded = sessionUploaded,
 | 
					        sessionUploaded = sessionUploaded,
 | 
				
			||||||
        sessionDeleted = sessionDeleted,
 | 
					        sessionDeleted = sessionDeleted,
 | 
				
			||||||
        sessionTotal = sessionTotal,
 | 
					        sessionTotal = sessionTotal,
 | 
				
			||||||
        uploadReady = uploadReady
 | 
					        uploadReady = uploadReady,
 | 
				
			||||||
 | 
					        serverIp = serverIp
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,16 +2,20 @@ package guru.core.analytics.data.store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.os.Build
 | 
					import android.os.Build
 | 
				
			||||||
 | 
					import guru.core.analytics.BuildConfig
 | 
				
			||||||
import guru.core.analytics.Constants
 | 
					import guru.core.analytics.Constants
 | 
				
			||||||
import guru.core.analytics.handler.AnalyticsCode
 | 
					import guru.core.analytics.handler.AnalyticsCode
 | 
				
			||||||
import guru.core.analytics.handler.EventHandler
 | 
					import guru.core.analytics.handler.EventHandler
 | 
				
			||||||
import guru.core.analytics.utils.AndroidUtils
 | 
					import guru.core.analytics.utils.AndroidUtils
 | 
				
			||||||
import io.reactivex.subjects.BehaviorSubject
 | 
					import io.reactivex.subjects.BehaviorSubject
 | 
				
			||||||
 | 
					import timber.log.Timber
 | 
				
			||||||
import java.util.*
 | 
					import java.util.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object DeviceInfoStore {
 | 
					object DeviceInfoStore {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const val SDK_VERSION = "v1.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val deviceInfoSubject: BehaviorSubject<Map<String, Any>> =
 | 
					    private val deviceInfoSubject: BehaviorSubject<Map<String, Any>> =
 | 
				
			||||||
        BehaviorSubject.createDefault(
 | 
					        BehaviorSubject.createDefault(
 | 
				
			||||||
            hashMapOf()
 | 
					            hashMapOf()
 | 
				
			||||||
| 
						 | 
					@ -37,7 +41,9 @@ object DeviceInfoStore {
 | 
				
			||||||
        map[Constants.DeviceInfo.SCREEN_W] = AndroidUtils.getWindowWidth(context) ?: 0
 | 
					        map[Constants.DeviceInfo.SCREEN_W] = AndroidUtils.getWindowWidth(context) ?: 0
 | 
				
			||||||
        map[Constants.DeviceInfo.OS_VERSION] = Build.VERSION.RELEASE
 | 
					        map[Constants.DeviceInfo.OS_VERSION] = Build.VERSION.RELEASE
 | 
				
			||||||
        map[Constants.DeviceInfo.LANGUAGE] = Locale.getDefault().language
 | 
					        map[Constants.DeviceInfo.LANGUAGE] = Locale.getDefault().language
 | 
				
			||||||
 | 
					        map[Constants.DeviceInfo.SDK_INFO] = "${SDK_VERSION}-${BuildConfig.buildTs}"
 | 
				
			||||||
        deviceInfoSubject.onNext(map)
 | 
					        deviceInfoSubject.onNext(map)
 | 
				
			||||||
        EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_DEVICE_INFO, map)
 | 
					        EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_DEVICE_INFO, map)
 | 
				
			||||||
 | 
					        Timber.tag("DeviceInfoStore").i("DeviceInfo: $map")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,22 @@
 | 
				
			||||||
package guru.core.analytics.data.store
 | 
					package guru.core.analytics.data.store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
import guru.core.analytics.Constants
 | 
					import guru.core.analytics.Constants
 | 
				
			||||||
import guru.core.analytics.data.db.model.Event
 | 
					import guru.core.analytics.data.db.model.Event
 | 
				
			||||||
import guru.core.analytics.data.db.model.EventEntity
 | 
					import guru.core.analytics.data.db.model.EventEntity
 | 
				
			||||||
import guru.core.analytics.data.db.model.EventPriority
 | 
					import guru.core.analytics.data.db.model.EventPriority
 | 
				
			||||||
import guru.core.analytics.data.db.model.ParamValue
 | 
					import guru.core.analytics.data.db.model.ParamValue
 | 
				
			||||||
 | 
					import guru.core.analytics.data.local.PreferencesManager
 | 
				
			||||||
import guru.core.analytics.data.model.EventItem
 | 
					import guru.core.analytics.data.model.EventItem
 | 
				
			||||||
 | 
					import guru.core.analytics.data.model.GuruAnalyticsAudit
 | 
				
			||||||
 | 
					import guru.core.analytics.utils.AndroidUtils
 | 
				
			||||||
import guru.core.analytics.utils.DateTimeUtils
 | 
					import guru.core.analytics.utils.DateTimeUtils
 | 
				
			||||||
import guru.core.analytics.utils.GsonUtil
 | 
					import guru.core.analytics.utils.GsonUtil
 | 
				
			||||||
import io.reactivex.subjects.BehaviorSubject
 | 
					import io.reactivex.subjects.BehaviorSubject
 | 
				
			||||||
 | 
					import timber.log.Timber
 | 
				
			||||||
import java.util.*
 | 
					import java.util.*
 | 
				
			||||||
import java.util.concurrent.ConcurrentHashMap
 | 
					import java.util.concurrent.ConcurrentHashMap
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicBoolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object EventInfoStore {
 | 
					object EventInfoStore {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +25,135 @@ object EventInfoStore {
 | 
				
			||||||
        UUID.randomUUID().toString()
 | 
					        UUID.randomUUID().toString()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val initialized = AtomicBoolean(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var preferenceMgr: PreferencesManager? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun initialize(ctx: Context) {
 | 
				
			||||||
 | 
					        if (initialized.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
					            val appVersion = AndroidUtils.getAppVersion(ctx)
 | 
				
			||||||
 | 
					            setUserProperty("app_version", appVersion)
 | 
				
			||||||
 | 
					            DeviceInfoStore.setDeviceInfo(ctx)
 | 
				
			||||||
 | 
					            preferenceMgr = PreferencesManager.getInstance(ctx).apply {
 | 
				
			||||||
 | 
					                restoreIds(this)
 | 
				
			||||||
 | 
					                GuruAnalyticsAudit.total = this.eventCountAll ?: 0
 | 
				
			||||||
 | 
					                GuruAnalyticsAudit.uploaded = this.eventCountUploaded ?: 0
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun restoreIds(pm: PreferencesManager) {
 | 
				
			||||||
 | 
					        val firebaseId = try {
 | 
				
			||||||
 | 
					            pm.firebaseId ?: ""
 | 
				
			||||||
 | 
					        } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val userId = try {
 | 
				
			||||||
 | 
					            pm.userId ?: ""
 | 
				
			||||||
 | 
					        } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val adId = try {
 | 
				
			||||||
 | 
					            pm.adId ?: ""
 | 
				
			||||||
 | 
					        } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val adjustId = try {
 | 
				
			||||||
 | 
					            pm.adjustId ?: ""
 | 
				
			||||||
 | 
					        } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val deviceId = try {
 | 
				
			||||||
 | 
					            pm.deviceId ?: ""
 | 
				
			||||||
 | 
					        } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					            ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var restoreIdsMsg = "restoreIds: "
 | 
				
			||||||
 | 
					        if (firebaseId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.FIREBASE_ID)
 | 
				
			||||||
 | 
					            if (current != null && current != firebaseId) {
 | 
				
			||||||
 | 
					                pm.firebaseId = current
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.FIREBASE_ID}]:${firebaseId}=>${current} "
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                setIds(Constants.Ids.FIREBASE_ID, firebaseId)
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.FIREBASE_ID}]:${firebaseId} "
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.FIREBASE_ID)
 | 
				
			||||||
 | 
					            if (current != null) {
 | 
				
			||||||
 | 
					                pm.firebaseId = current
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (userId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.UID)
 | 
				
			||||||
 | 
					            if (current != null && current != userId) {
 | 
				
			||||||
 | 
					                pm.userId = current
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.UID}]:${userId}=>${current} "
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                setIds(Constants.Ids.UID, userId)
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.UID}]:${userId} "
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.UID)
 | 
				
			||||||
 | 
					            if (current != null) {
 | 
				
			||||||
 | 
					                pm.userId = current
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (adjustId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.ADJUST_ID)
 | 
				
			||||||
 | 
					            if (current != null && current != adjustId) {
 | 
				
			||||||
 | 
					                pm.adjustId = current
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.ADJUST_ID}]:${adjustId}=>${current} "
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                setIds(Constants.Ids.ADJUST_ID, adjustId)
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.ADJUST_ID}]:${adjustId} "
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.ADJUST_ID)
 | 
				
			||||||
 | 
					            if (current != null) {
 | 
				
			||||||
 | 
					                pm.adjustId = current
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (deviceId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.DEVICE_ID)
 | 
				
			||||||
 | 
					            if (current != null && current != deviceId) {
 | 
				
			||||||
 | 
					                pm.deviceId = current
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.DEVICE_ID}]:${deviceId}=>${current} "
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                setIds(Constants.Ids.DEVICE_ID, deviceId)
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.DEVICE_ID}]:${deviceId} "
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.DEVICE_ID)
 | 
				
			||||||
 | 
					            if (current != null) {
 | 
				
			||||||
 | 
					                pm.deviceId = current
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (adId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.AD_ID)
 | 
				
			||||||
 | 
					            if (current != null && current != adId) {
 | 
				
			||||||
 | 
					                pm.adId = current
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.AD_ID}]:${adId}=>${current} "
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                setIds(Constants.Ids.AD_ID, adId)
 | 
				
			||||||
 | 
					                restoreIdsMsg += " [${Constants.Ids.AD_ID}]:${adId} "
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            val current = getId(Constants.Ids.AD_ID)
 | 
				
			||||||
 | 
					            if (current != null) {
 | 
				
			||||||
 | 
					                pm.adId = current
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Timber.tag("EventInfoStore").i(restoreIdsMsg)
 | 
				
			||||||
 | 
					        Timber.tag("EventInfoStore").i(ids.toString())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val propertiesSubject: BehaviorSubject<ConcurrentHashMap<String, String>> =
 | 
					    private val propertiesSubject: BehaviorSubject<ConcurrentHashMap<String, String>> =
 | 
				
			||||||
        BehaviorSubject.createDefault(
 | 
					        BehaviorSubject.createDefault(
 | 
				
			||||||
            ConcurrentHashMap<String, String>().apply {
 | 
					            ConcurrentHashMap<String, String>().apply {
 | 
				
			||||||
| 
						 | 
					@ -75,24 +210,57 @@ object EventInfoStore {
 | 
				
			||||||
        ids = ids.plus(idName to id)
 | 
					        ids = ids.plus(idName to id)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    internal fun getId(idName: String): String? = ids[idName]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun setDeviceId(deviceId: String) {
 | 
					    fun setDeviceId(deviceId: String) {
 | 
				
			||||||
 | 
					        if (deviceId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val prev = getId(Constants.Ids.DEVICE_ID) ?: ""
 | 
				
			||||||
            setIds(Constants.Ids.DEVICE_ID, deviceId)
 | 
					            setIds(Constants.Ids.DEVICE_ID, deviceId)
 | 
				
			||||||
 | 
					            if (prev != deviceId) {
 | 
				
			||||||
 | 
					                preferenceMgr?.deviceId = deviceId
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun setUid(uid: String) {
 | 
					    fun setUid(uid: String) {
 | 
				
			||||||
 | 
					        if (uid.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val prev = getId(Constants.Ids.UID) ?: ""
 | 
				
			||||||
            setIds(Constants.Ids.UID, uid)
 | 
					            setIds(Constants.Ids.UID, uid)
 | 
				
			||||||
 | 
					            if (prev != uid) {
 | 
				
			||||||
 | 
					                preferenceMgr?.userId = uid
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun setAdjustId(adjustId: String) {
 | 
					    fun setAdjustId(adjustId: String) {
 | 
				
			||||||
 | 
					        if (adjustId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val prev = getId(Constants.Ids.ADJUST_ID) ?: ""
 | 
				
			||||||
            setIds(Constants.Ids.ADJUST_ID, adjustId)
 | 
					            setIds(Constants.Ids.ADJUST_ID, adjustId)
 | 
				
			||||||
 | 
					            if (prev != adjustId) {
 | 
				
			||||||
 | 
					                preferenceMgr?.adjustId = adjustId
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun setAdId(adId: String) {
 | 
					    fun setAdId(adId: String) {
 | 
				
			||||||
 | 
					        if (adId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val prev = getId(Constants.Ids.AD_ID) ?: ""
 | 
				
			||||||
            setIds(Constants.Ids.AD_ID, adId)
 | 
					            setIds(Constants.Ids.AD_ID, adId)
 | 
				
			||||||
 | 
					            if (prev != adId) {
 | 
				
			||||||
 | 
					                preferenceMgr?.adId = adId
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun setFirebaseId(firebaseId: String) {
 | 
					    fun setFirebaseId(firebaseId: String) {
 | 
				
			||||||
 | 
					        if (firebaseId.isNotEmpty()) {
 | 
				
			||||||
 | 
					            val prev = getId(Constants.Ids.FIREBASE_ID) ?: ""
 | 
				
			||||||
            setIds(Constants.Ids.FIREBASE_ID, firebaseId)
 | 
					            setIds(Constants.Ids.FIREBASE_ID, firebaseId)
 | 
				
			||||||
 | 
					            if (prev != firebaseId) {
 | 
				
			||||||
 | 
					                preferenceMgr?.firebaseId = firebaseId
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun createParamValue(value: Any): ParamValue {
 | 
					    private fun createParamValue(value: Any): ParamValue {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,9 +28,12 @@ enum class AnalyticsCode(val code: Int) {
 | 
				
			||||||
    ERROR_ZIP(107),              // zip 错误
 | 
					    ERROR_ZIP(107),              // zip 错误
 | 
				
			||||||
    ERROR_DNS_CACHE(108),        // zip 错误
 | 
					    ERROR_DNS_CACHE(108),        // zip 错误
 | 
				
			||||||
    ERROR_CRONET_INTERCEPTOR(109),// cronet拦截器
 | 
					    ERROR_CRONET_INTERCEPTOR(109),// cronet拦截器
 | 
				
			||||||
 | 
					    ERROR_SESSION_START_ERROR(110),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    EVENT_FIRST_OPEN(1001),       // first_open 事件
 | 
					    EVENT_FIRST_OPEN(1001),       // first_open 事件
 | 
				
			||||||
    EVENT_FG(1002),               // fg 事件
 | 
					    EVENT_FG(1002),               // fg 事件
 | 
				
			||||||
 | 
					    EVENT_LOOKUP(1003),
 | 
				
			||||||
 | 
					    EVENT_SESSION_ACTIVE(1004),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 初始化进度
 | 
					    // 初始化进度
 | 
				
			||||||
    INIT_STEP_1(100001),
 | 
					    INIT_STEP_1(100001),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,92 @@
 | 
				
			||||||
 | 
					package guru.core.analytics.impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.net.Uri
 | 
				
			||||||
 | 
					import androidx.work.ListenableWorker
 | 
				
			||||||
 | 
					import androidx.work.RxWorker
 | 
				
			||||||
 | 
					import androidx.work.WorkerParameters
 | 
				
			||||||
 | 
					import guru.core.analytics.Constants
 | 
				
			||||||
 | 
					import guru.core.analytics.GuruAnalytics
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.GuruRepository
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.ServiceLocator
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.DnsMode
 | 
				
			||||||
 | 
					import guru.core.analytics.data.db.GuruAnalyticsDatabase
 | 
				
			||||||
 | 
					import guru.core.analytics.data.db.model.EventPriority
 | 
				
			||||||
 | 
					import guru.core.analytics.data.local.PreferencesManager
 | 
				
			||||||
 | 
					import guru.core.analytics.data.model.EventItem
 | 
				
			||||||
 | 
					import guru.core.analytics.data.model.GuruAnalyticsAudit
 | 
				
			||||||
 | 
					import guru.core.analytics.data.store.DeviceInfoStore
 | 
				
			||||||
 | 
					import guru.core.analytics.data.store.EventInfoStore
 | 
				
			||||||
 | 
					import guru.core.analytics.handler.AnalyticsCode
 | 
				
			||||||
 | 
					import guru.core.analytics.handler.EventHandler
 | 
				
			||||||
 | 
					import guru.core.analytics.log.PersistentTree
 | 
				
			||||||
 | 
					import guru.core.analytics.utils.ApiParamUtils
 | 
				
			||||||
 | 
					import io.reactivex.Single
 | 
				
			||||||
 | 
					import io.reactivex.schedulers.Schedulers
 | 
				
			||||||
 | 
					import timber.log.Timber
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ActiveWorker(
 | 
				
			||||||
 | 
					    val context: Context,
 | 
				
			||||||
 | 
					    workerParams: WorkerParameters
 | 
				
			||||||
 | 
					) : RxWorker(context.applicationContext, workerParams) {
 | 
				
			||||||
 | 
					    private fun buildGuruRepository(): GuruRepository {
 | 
				
			||||||
 | 
					        val preferenceMgr = PreferencesManager.getInstance(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val dnsMode = preferenceMgr.dnsMode ?: DnsMode.DEFAULT
 | 
				
			||||||
 | 
					        ServiceLocator.setDnsMode(dnsMode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val baseUrl = preferenceMgr.uploadEventBaseUrl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (baseUrl != null) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                if (Uri.parse(baseUrl).scheme?.startsWith("http") == true) {
 | 
				
			||||||
 | 
					                    return ServiceLocator.provideGuruRepository(context, baseUri = Uri.parse(baseUrl))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					                Timber.w("[Worker] Invalid base url: $baseUrl")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val uploadIpAddress = preferenceMgr.uploadIpAddressList?.split("|")
 | 
				
			||||||
 | 
					        if (!uploadIpAddress.isNullOrEmpty()) {
 | 
				
			||||||
 | 
					            ServiceLocator.setUploadIpAddress(uploadIpAddress)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Timber.tag("ActiveWorker").i("baseUrl: $baseUrl useCompositeDns: $dnsMode uploadIpAddress:$uploadIpAddress")
 | 
				
			||||||
 | 
					        return ServiceLocator.provideGuruRepository(context)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        const val WORKER_NAME = "SessionActive"
 | 
				
			||||||
 | 
					        const val WORKER_TAG = "SessionActive"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun createWork(): Single<Result> {
 | 
				
			||||||
 | 
					        if (GuruAnalyticsImpl.timberPlanted.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
					            Timber.plant(PersistentTree(context, debug = true))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Timber.d("Active OnWork...")
 | 
				
			||||||
 | 
					        val item = EventItem(
 | 
				
			||||||
 | 
					            eventName = "session_active", itemCategory = "success", params = mapOf(
 | 
				
			||||||
 | 
					                "uploaded" to GuruAnalyticsAudit.uploaded,
 | 
				
			||||||
 | 
					                "total" to GuruAnalyticsAudit.total,
 | 
				
			||||||
 | 
					                "uid" to (EventInfoStore.getId(Constants.Ids.UID) ?: ""),
 | 
				
			||||||
 | 
					                "fid" to (EventInfoStore.getId(Constants.Ids.FIREBASE_ID) ?: ""),
 | 
				
			||||||
 | 
					                "method" to "worker",
 | 
				
			||||||
 | 
					                "server" to GuruAnalyticsAudit.serverIp
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        EventInfoStore.initialize(context)
 | 
				
			||||||
 | 
					        val event = EventInfoStore.deriveEvent(item, priority = EventPriority.HIGH)
 | 
				
			||||||
 | 
					        val param = ApiParamUtils.generateApiParam(listOf(event))
 | 
				
			||||||
 | 
					        return buildGuruRepository().uploadEvents(param).map { Result.success() }.doOnSuccess {
 | 
				
			||||||
 | 
					            Timber.i("active success! $it")
 | 
				
			||||||
 | 
					            EventEngine.logEvent(event)
 | 
				
			||||||
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_SESSION_ACTIVE)
 | 
				
			||||||
 | 
					            EventEngine.sessionActivated = true
 | 
				
			||||||
 | 
					        }.onErrorReturn {
 | 
				
			||||||
 | 
					            Timber.e("active error! $it")
 | 
				
			||||||
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_SESSION_START_ERROR, it.message)
 | 
				
			||||||
 | 
					            return@onErrorReturn Result.retry()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,19 @@
 | 
				
			||||||
package guru.core.analytics.impl
 | 
					package guru.core.analytics.impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.net.Uri
 | 
				
			||||||
import androidx.work.RxWorker
 | 
					import androidx.work.RxWorker
 | 
				
			||||||
import androidx.work.WorkerParameters
 | 
					import androidx.work.WorkerParameters
 | 
				
			||||||
 | 
					import guru.core.analytics.GuruAnalytics
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.GuruRepository
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.ServiceLocator
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.DnsMode
 | 
				
			||||||
import guru.core.analytics.data.db.GuruAnalyticsDatabase
 | 
					import guru.core.analytics.data.db.GuruAnalyticsDatabase
 | 
				
			||||||
 | 
					import guru.core.analytics.data.local.PreferencesManager
 | 
				
			||||||
import guru.core.analytics.data.store.DeviceInfoStore
 | 
					import guru.core.analytics.data.store.DeviceInfoStore
 | 
				
			||||||
 | 
					import guru.core.analytics.data.store.EventInfoStore
 | 
				
			||||||
import guru.core.analytics.log.PersistentTree
 | 
					import guru.core.analytics.log.PersistentTree
 | 
				
			||||||
 | 
					import guru.core.analytics.utils.GsonUtil
 | 
				
			||||||
import io.reactivex.Single
 | 
					import io.reactivex.Single
 | 
				
			||||||
import timber.log.Timber
 | 
					import timber.log.Timber
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,21 +22,55 @@ class AnalyticsWorker(
 | 
				
			||||||
    workerParams: WorkerParameters
 | 
					    workerParams: WorkerParameters
 | 
				
			||||||
) : RxWorker(context.applicationContext, workerParams) {
 | 
					) : RxWorker(context.applicationContext, workerParams) {
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        const val WORKER_ID = "GuruAnalytics"
 | 
					        const val WORKER_NAME = "AnalyticsUploader"
 | 
				
			||||||
        const val WORKER_TAG = "Analytics"
 | 
					        const val WORKER_TAG = "AnalyticsUploader"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun buildGuruRepository(context: Context): GuruRepository {
 | 
				
			||||||
 | 
					        val preferenceMgr = PreferencesManager.getInstance(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val dnsMode = preferenceMgr.dnsMode ?: DnsMode.DEFAULT
 | 
				
			||||||
 | 
					        ServiceLocator.setDnsMode(dnsMode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val baseUrl = preferenceMgr.uploadEventBaseUrl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Timber.tag("AnalyticsWorker").i("baseUrl: $baseUrl useCompositeDns: $dnsMode")
 | 
				
			||||||
 | 
					        if (baseUrl != null) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                if (Uri.parse(baseUrl).scheme?.startsWith("http") == true) {
 | 
				
			||||||
 | 
					                    return ServiceLocator.provideGuruRepository(context, baseUri = Uri.parse(baseUrl))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					                Timber.w("[Worker] Invalid base url: $baseUrl")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val uploadIpAddress = preferenceMgr.uploadIpAddressList?.split("|")
 | 
				
			||||||
 | 
					        if (!uploadIpAddress.isNullOrEmpty()) {
 | 
				
			||||||
 | 
					            ServiceLocator.setUploadIpAddress(uploadIpAddress)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ServiceLocator.provideGuruRepository(context)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun createWork(): Single<Result> {
 | 
					    override fun createWork(): Single<Result> {
 | 
				
			||||||
        Timber.plant(PersistentTree(context))
 | 
					        if (GuruAnalyticsImpl.timberPlanted.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
					            Timber.plant(PersistentTree(context, debug = true))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val success = GuruAnalytics.INSTANCE.forceUpload("AnalyticsWorker")
 | 
				
			||||||
 | 
					        Timber.d("OnWork..forceUpload:${success}")
 | 
				
			||||||
 | 
					        return if (success) {
 | 
				
			||||||
 | 
					            Single.just(Result.success())
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
            GuruAnalyticsDatabase.initialize(context)
 | 
					            GuruAnalyticsDatabase.initialize(context)
 | 
				
			||||||
        DeviceInfoStore.setDeviceInfo(context)
 | 
					            EventInfoStore.initialize(context)
 | 
				
			||||||
        Timber.d("OnWork..")
 | 
					            Timber.d("OnWork..initialized")
 | 
				
			||||||
        val engine = EventEngine(context)
 | 
					            val engine = EventEngine(context, guruRepository = buildGuruRepository(context))
 | 
				
			||||||
        return engine.validateEvents().doOnSuccess {
 | 
					
 | 
				
			||||||
 | 
					            engine.validateEvents().doOnSuccess {
 | 
				
			||||||
                Timber.d("validateEvents deleted:${it.first} reset:${it.second}")
 | 
					                Timber.d("validateEvents deleted:${it.first} reset:${it.second}")
 | 
				
			||||||
            }.toFlowable().flatMap { engine.uploadEvents(500) }.map { true }.toList()
 | 
					            }.toFlowable().flatMap { engine.uploadEvents(500) }.map { true }.toList()
 | 
				
			||||||
                .map { Result.success() }
 | 
					                .map { Result.success() }
 | 
				
			||||||
                .onErrorReturn { Result.success() }
 | 
					                .onErrorReturn { Result.success() }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,6 @@ internal class AppLifecycleMonitor internal constructor(context: Context) {
 | 
				
			||||||
                Timber.d("${TAG}_ON_PAUSE")
 | 
					                Timber.d("${TAG}_ON_PAUSE")
 | 
				
			||||||
                fgHelper.stop()
 | 
					                fgHelper.stop()
 | 
				
			||||||
                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.LIFECYCLE_PAUSE)
 | 
					                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.LIFECYCLE_PAUSE)
 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Lifecycle.Event.ON_STOP -> Timber.d("${TAG}_ON_STOP")
 | 
					            Lifecycle.Event.ON_STOP -> Timber.d("${TAG}_ON_STOP")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import android.net.ConnectivityManager.NetworkCallback
 | 
				
			||||||
import android.net.Network
 | 
					import android.net.Network
 | 
				
			||||||
import android.net.NetworkCapabilities
 | 
					import android.net.NetworkCapabilities
 | 
				
			||||||
import android.net.NetworkRequest
 | 
					import android.net.NetworkRequest
 | 
				
			||||||
 | 
					import android.os.Build
 | 
				
			||||||
import guru.core.analytics.handler.AnalyticsCode
 | 
					import guru.core.analytics.handler.AnalyticsCode
 | 
				
			||||||
import guru.core.analytics.handler.EventHandler
 | 
					import guru.core.analytics.handler.EventHandler
 | 
				
			||||||
import guru.core.analytics.utils.AndroidUtils
 | 
					import guru.core.analytics.utils.AndroidUtils
 | 
				
			||||||
| 
						 | 
					@ -14,48 +15,48 @@ import io.reactivex.Flowable
 | 
				
			||||||
import io.reactivex.subjects.BehaviorSubject
 | 
					import io.reactivex.subjects.BehaviorSubject
 | 
				
			||||||
import timber.log.Timber
 | 
					import timber.log.Timber
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object ConnectionStateMonitor : NetworkCallback() {
 | 
					class ConnectionStateMonitor(private val context: Context) : NetworkCallback() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private const val TAG = "ConnectionStateMonitor"
 | 
					    private val TAG = "ConnectionStateMonitor"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var networkRequest: NetworkRequest? = null
 | 
					    private var networkRequest: NetworkRequest? = null
 | 
				
			||||||
    private val connectStateSubject: BehaviorSubject<Boolean> = BehaviorSubject.createDefault(false)
 | 
					    private val networkChangedSubject: BehaviorSubject<Int> = BehaviorSubject.createDefault(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val connectStateFlowable: Flowable<Boolean>
 | 
					    private val connectivityManager by lazy {
 | 
				
			||||||
        get() = connectStateSubject.toFlowable(BackpressureStrategy.DROP)
 | 
					        return@lazy context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun bindConnectionStateChanged(context: Context) {
 | 
					    val networkChanged: Flowable<Int>
 | 
				
			||||||
        connectStateSubject.onNext(AndroidUtils.isInternetAvailable(context))
 | 
					        get() = networkChangedSubject.toFlowable(BackpressureStrategy.DROP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun bind() {
 | 
				
			||||||
        if (networkRequest == null) {
 | 
					        if (networkRequest == null) {
 | 
				
			||||||
            networkRequest = NetworkRequest.Builder()
 | 
					            networkRequest = NetworkRequest.Builder()
 | 
				
			||||||
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
 | 
					                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
 | 
				
			||||||
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
 | 
					                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
 | 
				
			||||||
                .build()
 | 
					                .build()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
 | 
					        connectivityManager.registerNetworkCallback(networkRequest!!, this)
 | 
				
			||||||
        cm?.registerNetworkCallback(networkRequest!!, this)
 | 
					        networkChanged()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun unbindConnectionStateChanged(context: Context?) {
 | 
					    fun unbind(context: Context?) {
 | 
				
			||||||
        val cm = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
 | 
					        val cm = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
 | 
				
			||||||
        cm?.unregisterNetworkCallback(this)
 | 
					        cm?.unregisterNetworkCallback(this)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun networkChanged() {
 | 
				
			||||||
 | 
					        networkChangedSubject.onNext((networkChangedSubject.value ?: 0) + 1)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onAvailable(network: Network) {
 | 
					    override fun onAvailable(network: Network) {
 | 
				
			||||||
        super.onAvailable(network)
 | 
					        super.onAvailable(network)
 | 
				
			||||||
        Timber.d("${TAG}_onAvailable")
 | 
					        networkChanged()
 | 
				
			||||||
        if (connectStateSubject.value != true) {
 | 
					 | 
				
			||||||
            connectStateSubject.onNext(true)
 | 
					 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.NETWORK_AVAILABLE)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun onLost(network: Network) {
 | 
					    override fun onLost(network: Network) {
 | 
				
			||||||
        super.onLost(network)
 | 
					        super.onLost(network)
 | 
				
			||||||
        Timber.d("${TAG}_onLost")
 | 
					        networkChanged()
 | 
				
			||||||
        if (connectStateSubject.value != false) {
 | 
					 | 
				
			||||||
            connectStateSubject.onNext(false)
 | 
					 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.NETWORK_LOST)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ class PendingEvent(
 | 
				
			||||||
    val at = SystemClock.elapsedRealtime()
 | 
					    val at = SystemClock.elapsedRealtime()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object EventDispatcher : EventDeliver {
 | 
					data object EventDispatcher : EventDeliver {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val pendingEvents = ConcurrentLinkedQueue<PendingEvent>()
 | 
					    private val pendingEvents = ConcurrentLinkedQueue<PendingEvent>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,15 @@ package guru.core.analytics.impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
 | 
					import androidx.work.BackoffPolicy
 | 
				
			||||||
 | 
					import androidx.work.Constraints
 | 
				
			||||||
 | 
					import androidx.work.ExistingPeriodicWorkPolicy
 | 
				
			||||||
 | 
					import androidx.work.ExistingWorkPolicy
 | 
				
			||||||
 | 
					import androidx.work.NetworkType
 | 
				
			||||||
 | 
					import androidx.work.OneTimeWorkRequestBuilder
 | 
				
			||||||
 | 
					import androidx.work.PeriodicWorkRequestBuilder
 | 
				
			||||||
 | 
					import androidx.work.SystemClock
 | 
				
			||||||
 | 
					import androidx.work.WorkManager
 | 
				
			||||||
import guru.core.analytics.Constants
 | 
					import guru.core.analytics.Constants
 | 
				
			||||||
import guru.core.analytics.GuruAnalytics
 | 
					import guru.core.analytics.GuruAnalytics
 | 
				
			||||||
import guru.core.analytics.data.api.GuruRepository
 | 
					import guru.core.analytics.data.api.GuruRepository
 | 
				
			||||||
| 
						 | 
					@ -18,8 +27,8 @@ import guru.core.analytics.data.store.EventInfoStore
 | 
				
			||||||
import guru.core.analytics.handler.AnalyticsCode
 | 
					import guru.core.analytics.handler.AnalyticsCode
 | 
				
			||||||
import guru.core.analytics.handler.EventHandler
 | 
					import guru.core.analytics.handler.EventHandler
 | 
				
			||||||
import guru.core.analytics.log.PersistentTree
 | 
					import guru.core.analytics.log.PersistentTree
 | 
				
			||||||
import guru.core.analytics.utils.AndroidUtils
 | 
					 | 
				
			||||||
import guru.core.analytics.utils.ApiParamUtils
 | 
					import guru.core.analytics.utils.ApiParamUtils
 | 
				
			||||||
 | 
					import guru.core.analytics.utils.Connectivity
 | 
				
			||||||
import guru.core.analytics.utils.DateTimeUtils
 | 
					import guru.core.analytics.utils.DateTimeUtils
 | 
				
			||||||
import io.reactivex.BackpressureStrategy
 | 
					import io.reactivex.BackpressureStrategy
 | 
				
			||||||
import io.reactivex.Flowable
 | 
					import io.reactivex.Flowable
 | 
				
			||||||
| 
						 | 
					@ -41,16 +50,17 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
    private val batchLimit: Int = DEFAULT_BATCH_LIMIT,
 | 
					    private val batchLimit: Int = DEFAULT_BATCH_LIMIT,
 | 
				
			||||||
    private val uploadPeriodInSeconds: Long = DEFAULT_UPLOAD_PERIOD_IN_SECONDS,
 | 
					    private val uploadPeriodInSeconds: Long = DEFAULT_UPLOAD_PERIOD_IN_SECONDS,
 | 
				
			||||||
    private val eventExpiredInDays: Int = DEFAULT_EVENT_EXPIRED_IN_DAYS,
 | 
					    private val eventExpiredInDays: Int = DEFAULT_EVENT_EXPIRED_IN_DAYS,
 | 
				
			||||||
    private val uploadEventBaseUri: Uri? = null,
 | 
					    private val guruRepository: GuruRepository
 | 
				
			||||||
) : EventDeliver {
 | 
					) : EventDeliver {
 | 
				
			||||||
    private val guruRepository: GuruRepository by lazy {
 | 
					 | 
				
			||||||
        ServiceLocator.provideGuruRepository(context.applicationContext, baseUri = uploadEventBaseUri)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val preferencesManager by lazy {
 | 
					    private val preferencesManager by lazy {
 | 
				
			||||||
        PreferencesManager.getInstance(context)
 | 
					        PreferencesManager.getInstance(context)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val connectivity: Connectivity by lazy {
 | 
				
			||||||
 | 
					        Connectivity(context)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val lifecycleMonitor = AppLifecycleMonitor(context)
 | 
					    private val lifecycleMonitor = AppLifecycleMonitor(context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
| 
						 | 
					@ -59,12 +69,17 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
        const val DEFAULT_BATCH_LIMIT = 25 // 一次上传event最多数量
 | 
					        const val DEFAULT_BATCH_LIMIT = 25 // 一次上传event最多数量
 | 
				
			||||||
        const val DEFAULT_EVENT_EXPIRED_IN_DAYS = 7
 | 
					        const val DEFAULT_EVENT_EXPIRED_IN_DAYS = 7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const val SESSION_ACTIVE_INTERVAL = 15 * 60 * 1000 // 15分钟
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private val scheduler = Schedulers.from(Executors.newSingleThreadExecutor())
 | 
					        private val scheduler = Schedulers.from(Executors.newSingleThreadExecutor())
 | 
				
			||||||
        private val dbScheduler = Schedulers.from(Executors.newSingleThreadExecutor())
 | 
					        private val dbScheduler = Schedulers.from(Executors.newSingleThreadExecutor())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private val dateTimeFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
 | 
					        private val dateTimeFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Volatile
 | 
				
			||||||
 | 
					        var sessionActivated = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fun logDebug(message: String, vararg args: Any?) {
 | 
					        fun logDebug(message: String, vararg args: Any?) {
 | 
				
			||||||
            if (GuruAnalytics.INSTANCE.isDebug()) {
 | 
					            if (GuruAnalytics.INSTANCE.isDebug()) {
 | 
				
			||||||
| 
						 | 
					@ -72,20 +87,26 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fun logInfo(message: String, vararg args: Any?) {
 | 
				
			||||||
 | 
					            if (GuruAnalytics.INSTANCE.isDebug()) {
 | 
				
			||||||
 | 
					                Timber.tag(TAG).i(message, args)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fun logEvent(entity: EventEntity) {
 | 
					        fun logEvent(entity: EventEntity) {
 | 
				
			||||||
            Timber.log(
 | 
					            Timber.log(
 | 
				
			||||||
                PersistentTree.PRIORITY_EVENT,
 | 
					                PersistentTree.PRIORITY_EVENT, "[${dateTimeFormatter.format(entity.at)}] ${entity.event} ${entity.json}"
 | 
				
			||||||
                "[${dateTimeFormatter.format(entity.at)}] ${entity.event} ${entity.json}"
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var compositeDisposable: CompositeDisposable = CompositeDisposable()
 | 
					    private var compositeDisposable: CompositeDisposable = CompositeDisposable()
 | 
				
			||||||
    private val pendingEventSubject: PublishSubject<Int> = PublishSubject.create()
 | 
					    private val pendingEventSubject: PublishSubject<Int> = PublishSubject.create()
 | 
				
			||||||
    private val forceUploadSubject: PublishSubject<Boolean> = PublishSubject.create()
 | 
					    private val forceTriggerSubject: PublishSubject<String> = PublishSubject.create()
 | 
				
			||||||
 | 
					    internal val started = AtomicBoolean(false)
 | 
				
			||||||
    private val started = AtomicBoolean(false)
 | 
					 | 
				
			||||||
    private var enableUpload = true
 | 
					    private var enableUpload = true
 | 
				
			||||||
 | 
					    private var latestValidActionTs = 0L
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun setEnableUpload(enable: Boolean) {
 | 
					    fun setEnableUpload(enable: Boolean) {
 | 
				
			||||||
        enableUpload = enable
 | 
					        enableUpload = enable
 | 
				
			||||||
| 
						 | 
					@ -96,8 +117,19 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
    fun start(startUploadDelay: Long?) {
 | 
					    fun start(startUploadDelay: Long?) {
 | 
				
			||||||
        if (started.compareAndSet(false, true)) {
 | 
					        if (started.compareAndSet(false, true)) {
 | 
				
			||||||
            prepare()
 | 
					            prepare()
 | 
				
			||||||
            ConnectionStateMonitor.bindConnectionStateChanged(context.applicationContext)
 | 
					            connectivity.bind()
 | 
				
			||||||
            lifecycleMonitor.initialize()
 | 
					            lifecycleMonitor.initialize()
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                if (connectivity.isNetworkAvailable()) {
 | 
				
			||||||
 | 
					                    logSessionActive()
 | 
				
			||||||
 | 
					                    logDebug("[1] session started!")
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    dispatchActiveWorker()
 | 
				
			||||||
 | 
					                    logDebug("dispatchActiveWorker!")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					                logDebug("session started error!")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            scheduler.scheduleDirect({
 | 
					            scheduler.scheduleDirect({
 | 
				
			||||||
                EventDispatcher.start()
 | 
					                EventDispatcher.start()
 | 
				
			||||||
                logFirstOpen()
 | 
					                logFirstOpen()
 | 
				
			||||||
| 
						 | 
					@ -112,8 +144,7 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
    private fun logFirstOpen() {
 | 
					    private fun logFirstOpen() {
 | 
				
			||||||
        if (preferencesManager.isFirstOpen == true) {
 | 
					        if (preferencesManager.isFirstOpen == true) {
 | 
				
			||||||
            GuruAnalytics.INSTANCE.logEvent(
 | 
					            GuruAnalytics.INSTANCE.logEvent(
 | 
				
			||||||
                Constants.Event.FIRST_OPEN,
 | 
					                Constants.Event.FIRST_OPEN, options = AnalyticsOptions(EventPriority.EMERGENCE)
 | 
				
			||||||
                options = AnalyticsOptions(EventPriority.EMERGENCE)
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            preferencesManager.isFirstOpen = false
 | 
					            preferencesManager.isFirstOpen = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -121,9 +152,64 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun logSessionActive() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val item = EventItem(
 | 
				
			||||||
 | 
					            eventName = "session_active", itemCategory = "success", params = mapOf(
 | 
				
			||||||
 | 
					                "uploaded" to GuruAnalyticsAudit.uploaded,
 | 
				
			||||||
 | 
					                "total" to GuruAnalyticsAudit.total,
 | 
				
			||||||
 | 
					                "uid" to (EventInfoStore.getId(Constants.Ids.UID) ?: ""),
 | 
				
			||||||
 | 
					                "fid" to (EventInfoStore.getId(Constants.Ids.FIREBASE_ID) ?: ""),
 | 
				
			||||||
 | 
					                "method" to "start",
 | 
				
			||||||
 | 
					                "server" to GuruAnalyticsAudit.serverIp
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        val event = EventInfoStore.deriveEvent(item, priority = EventPriority.HIGH)
 | 
				
			||||||
 | 
					        val param = ApiParamUtils.generateApiParam(listOf(event))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guruRepository.uploadEvents(param).map { true }.doOnSuccess {
 | 
				
			||||||
 | 
					            Timber.i("active success! $it")
 | 
				
			||||||
 | 
					            logEvent(event)
 | 
				
			||||||
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_SESSION_ACTIVE)
 | 
				
			||||||
 | 
					            sessionActivated = true
 | 
				
			||||||
 | 
					        }.onErrorReturn {
 | 
				
			||||||
 | 
					            Timber.e("active error! $it")
 | 
				
			||||||
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_SESSION_START_ERROR, it.message)
 | 
				
			||||||
 | 
					            if (!sessionActivated) {
 | 
				
			||||||
 | 
					                dispatchActiveWorker()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return@onErrorReturn false
 | 
				
			||||||
 | 
					        }.subscribeOn(Schedulers.io()).subscribe()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun dispatchActiveWorker() {
 | 
				
			||||||
 | 
					        Timber.e("dispatchActiveWorker...")
 | 
				
			||||||
 | 
					        val constraints = Constraints.Builder()
 | 
				
			||||||
 | 
					            .setRequiredNetworkType(NetworkType.UNMETERED)
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					        val request = OneTimeWorkRequestBuilder<ActiveWorker>()
 | 
				
			||||||
 | 
					            .setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES)
 | 
				
			||||||
 | 
					            .addTag(ActiveWorker.WORKER_TAG)
 | 
				
			||||||
 | 
					            .setConstraints(constraints)
 | 
				
			||||||
 | 
					            .setInitialDelay(5, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            WorkManager.getInstance(context)
 | 
				
			||||||
 | 
					                .enqueueUniqueWork(
 | 
				
			||||||
 | 
					                    ActiveWorker.WORKER_NAME,
 | 
				
			||||||
 | 
					                    ExistingWorkPolicy.KEEP,
 | 
				
			||||||
 | 
					                    request
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					        } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					            Timber.tag("EventEngine").i("dispatchActiveWorker error: $throwable")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun startWork() {
 | 
					    private fun startWork() {
 | 
				
			||||||
 | 
					        Timber.d("startWork")
 | 
				
			||||||
        pollEvents()
 | 
					        pollEvents()
 | 
				
			||||||
        forceUpload()
 | 
					        forceUpload("startWork")
 | 
				
			||||||
        Timber.d("UploadEventDaemon started!!")
 | 
					        Timber.d("UploadEventDaemon started!!")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,28 +235,20 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun pollEvents() {
 | 
					    private fun pollEvents() {
 | 
				
			||||||
        logDebug("pollEvents()!! $uploadPeriodInSeconds $batchLimit")
 | 
					        logDebug("pollEvents()!! $uploadPeriodInSeconds $batchLimit")
 | 
				
			||||||
        val flowable =
 | 
					        val periodFlowable = Flowable.interval(5, uploadPeriodInSeconds, TimeUnit.SECONDS).onBackpressureDrop()
 | 
				
			||||||
            pendingEventSubject.buffer(uploadPeriodInSeconds, TimeUnit.SECONDS, batchLimit)
 | 
					        val forceFlowable = forceTriggerSubject.toFlowable(BackpressureStrategy.DROP).map { scene ->
 | 
				
			||||||
                .toFlowable(BackpressureStrategy.DROP)
 | 
					            logDebug("force trigger: $scene")
 | 
				
			||||||
                .doOnNext {
 | 
					 | 
				
			||||||
                    logDebug("pendingEvent ${it.size}")
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        val networkFlowable = ConnectionStateMonitor.connectStateFlowable.doOnNext {
 | 
					        val networkFlowable = connectivity.networkAvailableFlowable
 | 
				
			||||||
            GuruAnalyticsAudit.connectionState = it
 | 
					 | 
				
			||||||
            logDebug("network $it")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        val forceFlowable = forceUploadSubject.toFlowable(BackpressureStrategy.DROP)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        compositeDisposable.add(Flowable.combineLatest(
 | 
					        compositeDisposable.add(Flowable.combineLatest(
 | 
				
			||||||
            Flowable.merge(flowable, forceFlowable), networkFlowable
 | 
					            periodFlowable, forceFlowable, networkFlowable,
 | 
				
			||||||
        ) { _, connected -> connected }
 | 
					        ) { _, _, available ->
 | 
				
			||||||
            .filter {
 | 
					            GuruAnalyticsAudit.connectionState = available
 | 
				
			||||||
                logDebug("pollEvent filter $it")
 | 
					            val uploadedRate = GuruAnalyticsAudit.uploaded / GuruAnalyticsAudit.total.toFloat()
 | 
				
			||||||
                return@filter it
 | 
					            val ignoreAvailable = !GuruAnalyticsAudit.uploadReady && uploadedRate < 0.6
 | 
				
			||||||
            }
 | 
					            logDebug("enableUpload:$enableUpload && (network:$available || ignoreAvailable: (${ignoreAvailable})[uploaded(${GuruAnalyticsAudit.uploaded}) / total(${GuruAnalyticsAudit.total}) = $uploadedRate])")
 | 
				
			||||||
            .filter { enableUpload }
 | 
					            return@combineLatest enableUpload && (available || ignoreAvailable)
 | 
				
			||||||
            .flatMap { uploadEvents(500) }
 | 
					        }.flatMap { uploadEvents(100) }.subscribe()
 | 
				
			||||||
            .subscribe()
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -181,8 +259,7 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
            val expiredTs = currentTs - eventExpiredInDays * DateTimeUtils.DAYS_IN_MILLIS
 | 
					            val expiredTs = currentTs - eventExpiredInDays * DateTimeUtils.DAYS_IN_MILLIS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Timber.d("validateEvents $currentTs $expiredTs $eventExpiredInDays")
 | 
					            Timber.d("validateEvents $currentTs $expiredTs $eventExpiredInDays")
 | 
				
			||||||
            val pair = GuruAnalyticsDatabase.getInstance().eventDao()
 | 
					            val pair = GuruAnalyticsDatabase.getInstance().eventDao().deleteExpiredEvents(expiredTs)
 | 
				
			||||||
                .deleteExpiredEvents(expiredTs)
 | 
					 | 
				
			||||||
            // 记录删除的事件数量
 | 
					            // 记录删除的事件数量
 | 
				
			||||||
            if (pair.first > 0) {
 | 
					            if (pair.first > 0) {
 | 
				
			||||||
                val eventCountDeleted = preferencesManager.eventCountDeleted ?: 0
 | 
					                val eventCountDeleted = preferencesManager.eventCountDeleted ?: 0
 | 
				
			||||||
| 
						 | 
					@ -192,15 +269,15 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
                GuruAnalyticsAudit.sessionDeleted += pair.first
 | 
					                GuruAnalyticsAudit.sessionDeleted += pair.first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                val extMap = mapOf(
 | 
					                val extMap = mapOf(
 | 
				
			||||||
                    "expiredCount" to pair.first,
 | 
					                    "expiredCount" to pair.first, "allDeleteCount" to preferencesManager.eventCountDeleted
 | 
				
			||||||
                    "allDeleteCount" to preferencesManager.eventCountDeleted
 | 
					 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.DELETE_EXPIRED, extMap)
 | 
					                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.DELETE_EXPIRED, extMap)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            emitter.onSuccess(pair)
 | 
					            emitter.onSuccess(pair)
 | 
				
			||||||
        }.subscribeOn(dbScheduler)
 | 
					        }.subscribeOn(dbScheduler).onErrorReturn {
 | 
				
			||||||
            .onErrorReturn {
 | 
					            EventHandler.INSTANCE.notifyEventHandler(
 | 
				
			||||||
                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_DELETE_EXPIRED, it.message)
 | 
					                AnalyticsCode.ERROR_DELETE_EXPIRED, it.message
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            Pair(-1, -1)
 | 
					            Pair(-1, -1)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -209,18 +286,15 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
        val eventDao = GuruAnalyticsDatabase.getInstance().eventDao()
 | 
					        val eventDao = GuruAnalyticsDatabase.getInstance().eventDao()
 | 
				
			||||||
        GuruAnalyticsAudit.uploadReady = true
 | 
					        GuruAnalyticsAudit.uploadReady = true
 | 
				
			||||||
        logDebug("uploadEvents: $count")
 | 
					        logDebug("uploadEvents: $count")
 | 
				
			||||||
        return Flowable.just(count).map { eventDao.loadAndMarkUploadEvents(it) }
 | 
					        return Flowable.just(count).map { eventDao.loadAndMarkUploadEvents(it) }.onErrorReturn {
 | 
				
			||||||
            .onErrorReturn {
 | 
					            try {
 | 
				
			||||||
                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_LOAD_MARK, it.message)
 | 
					 | 
				
			||||||
                eventDao.loadAndMarkUploadEvents(batchLimit)
 | 
					                eventDao.loadAndMarkUploadEvents(batchLimit)
 | 
				
			||||||
 | 
					            } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_LOAD_MARK, it.message)
 | 
				
			||||||
 | 
					                emptyList()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            .filter { it.isNotEmpty() }
 | 
					        }.filter { it.isNotEmpty() }.subscribeOn(dbScheduler).observeOn(scheduler).concatMap { splitEntities(it) }
 | 
				
			||||||
            .subscribeOn(dbScheduler)
 | 
					            .flatMapSingle { uploadEventsInternal(it) }.filter { it.isNotEmpty() }.doOnNext {
 | 
				
			||||||
            .observeOn(scheduler)
 | 
					 | 
				
			||||||
            .concatMap { splitEntities(it) }
 | 
					 | 
				
			||||||
            .flatMapSingle { uploadEventsInternal(it) }
 | 
					 | 
				
			||||||
            .filter { it.isNotEmpty() }
 | 
					 | 
				
			||||||
            .doOnNext {
 | 
					 | 
				
			||||||
                eventDao.deleteEvents(it)
 | 
					                eventDao.deleteEvents(it)
 | 
				
			||||||
                if (GuruAnalytics.INSTANCE.isDebug()) {
 | 
					                if (GuruAnalytics.INSTANCE.isDebug()) {
 | 
				
			||||||
                    for (entity in it) {
 | 
					                    for (entity in it) {
 | 
				
			||||||
| 
						 | 
					@ -230,22 +304,11 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun uploadEventsInternal(
 | 
					    private fun uploadEventsInternal(entities: List<EventEntity>): Single<List<EventEntity>> {
 | 
				
			||||||
        entities: List<EventEntity>,
 | 
					 | 
				
			||||||
        withoutDelay: Boolean = false
 | 
					 | 
				
			||||||
    ): Single<List<EventEntity>> {
 | 
					 | 
				
			||||||
        val param = ApiParamUtils.generateApiParam(entities)
 | 
					        val param = ApiParamUtils.generateApiParam(entities)
 | 
				
			||||||
        return guruRepository.uploadEvents(param).map { true }
 | 
					        return guruRepository.uploadEvents(param).map { true }.observeOn(dbScheduler).doOnError {
 | 
				
			||||||
            .observeOn(dbScheduler)
 | 
					
 | 
				
			||||||
            .doOnError {
 | 
					        }.doOnSuccess {
 | 
				
			||||||
                if (withoutDelay) {
 | 
					 | 
				
			||||||
                    GuruAnalyticsDatabase.getInstance().eventDao().addEvents(entities)
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    GuruAnalyticsDatabase.getInstance().eventDao().updateEventDefault(entities)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_FAIL, it.message)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            .doOnSuccess {
 | 
					 | 
				
			||||||
            // 记录上传成功的数量
 | 
					            // 记录上传成功的数量
 | 
				
			||||||
            val eventCountUploaded = preferencesManager.eventCountUploaded ?: 0
 | 
					            val eventCountUploaded = preferencesManager.eventCountUploaded ?: 0
 | 
				
			||||||
            val uploaded = eventCountUploaded + entities.size
 | 
					            val uploaded = eventCountUploaded + entities.size
 | 
				
			||||||
| 
						 | 
					@ -258,59 +321,49 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
                "eventNames" to entities.joinToString(",") { it.event },
 | 
					                "eventNames" to entities.joinToString(",") { it.event },
 | 
				
			||||||
                "allUploadedCount" to preferencesManager.eventCountUploaded,
 | 
					                "allUploadedCount" to preferencesManager.eventCountUploaded,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            logDebug("uploadEvents success: $extMap")
 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_SUCCESS, extMap)
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_SUCCESS, extMap)
 | 
				
			||||||
 | 
					            latestValidActionTs = android.os.SystemClock.elapsedRealtime()
 | 
				
			||||||
 | 
					        }.onErrorReturn {
 | 
				
			||||||
 | 
					            GuruAnalyticsDatabase.getInstance().eventDao().updateEventDefault(entities)
 | 
				
			||||||
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_FAIL, it.message)
 | 
				
			||||||
 | 
					            val elapsed = android.os.SystemClock.elapsedRealtime()
 | 
				
			||||||
 | 
					            val uploadedRate = GuruAnalyticsAudit.sessionUploaded / GuruAnalyticsAudit.sessionTotal
 | 
				
			||||||
 | 
					            val isActiveNetworkMetered = connectivity.isActiveNetworkMetered
 | 
				
			||||||
 | 
					            val exceededValidActionGap = elapsed - latestValidActionTs > SESSION_ACTIVE_INTERVAL
 | 
				
			||||||
 | 
					            logInfo("uploadEvent error $it sessionActivated:$sessionActivated uploadedRate:$uploadedRate isActiveNetworkMetered:$isActiveNetworkMetered gap:${(elapsed - latestValidActionTs) / 1000}s")
 | 
				
			||||||
 | 
					            /// 如果没有激活,并且当前是计费网络,并且距离上次上传成功时间超过15分钟,激活worker
 | 
				
			||||||
 | 
					            /// 因为这个 worker 会在非计费网络下尝试执行
 | 
				
			||||||
 | 
					            if (!sessionActivated && uploadedRate < 0.6 && isActiveNetworkMetered && exceededValidActionGap) {
 | 
				
			||||||
 | 
					                logInfo("Metered Network Active! But upload event failed rate > 0.4! So dispatch ActiveWorker")
 | 
				
			||||||
 | 
					                dispatchActiveWorker()
 | 
				
			||||||
 | 
					                latestValidActionTs = elapsed
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            .onErrorReturn { false }
 | 
					            false
 | 
				
			||||||
            .map { if (it) entities else emptyList() }
 | 
					        }.map { if (it) entities else emptyList() }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun logEventWithoutDelay(
 | 
					 | 
				
			||||||
        eventName: String,
 | 
					 | 
				
			||||||
        itemCategory: String? = null,
 | 
					 | 
				
			||||||
        itemName: String? = null,
 | 
					 | 
				
			||||||
        value: Int? = null,
 | 
					 | 
				
			||||||
        parameters: Map<String, Any>? = null
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        val item = EventItem(
 | 
					 | 
				
			||||||
            eventName = eventName,
 | 
					 | 
				
			||||||
            itemCategory = itemCategory,
 | 
					 | 
				
			||||||
            itemName = itemName,
 | 
					 | 
				
			||||||
            value = value,
 | 
					 | 
				
			||||||
            params = parameters
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        val event = EventInfoStore.deriveEvent(item)
 | 
					 | 
				
			||||||
        compositeDisposable.add(
 | 
					 | 
				
			||||||
            Flowable.just(mutableListOf(event))
 | 
					 | 
				
			||||||
                .flatMap {
 | 
					 | 
				
			||||||
                    return@flatMap if (!AndroidUtils.isInternetAvailable(context)
 | 
					 | 
				
			||||||
                    ) {
 | 
					 | 
				
			||||||
                        GuruAnalyticsDatabase.getInstance().eventDao().addEvents(it)
 | 
					 | 
				
			||||||
                        Flowable.empty()
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        Flowable.just(it)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                .flatMapSingle { uploadEventsInternal(it, withoutDelay = true) }
 | 
					 | 
				
			||||||
                .subscribe {}
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
 | 
					    override fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
 | 
				
			||||||
        pendingEventSubject.onNext(1)
 | 
					
 | 
				
			||||||
        // 记录收到的事件数量
 | 
					        // 记录收到的事件数量
 | 
				
			||||||
        increaseEventCount()
 | 
					        val delivered = increaseEventCount()
 | 
				
			||||||
 | 
					        pendingEventSubject.onNext(1)
 | 
				
			||||||
        if (options.priority == EventPriority.EMERGENCE) {
 | 
					        if (options.priority == EventPriority.EMERGENCE) {
 | 
				
			||||||
            forceUpload()
 | 
					            logInfo("EMERGENCE: ${item.eventName} forceUpload")
 | 
				
			||||||
 | 
					            forceUpload("EMERGENCE")
 | 
				
			||||||
 | 
					        } else if (delivered and 0x1F == 0x1F) {
 | 
				
			||||||
 | 
					            logInfo("Already delivered $delivered events!! forceUpload")
 | 
				
			||||||
 | 
					            forceUpload("DELIVERED")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun increaseEventCount() {
 | 
					    private fun increaseEventCount(): Int {
 | 
				
			||||||
        val eventCountAll = preferencesManager.eventCountAll ?: 0
 | 
					        val eventCountAll = preferencesManager.eventCountAll ?: 0
 | 
				
			||||||
        val total = eventCountAll + 1
 | 
					        val total = eventCountAll + 1
 | 
				
			||||||
        preferencesManager.eventCountAll = total
 | 
					        preferencesManager.eventCountAll = total
 | 
				
			||||||
        GuruAnalyticsAudit.total = total
 | 
					        GuruAnalyticsAudit.total = total
 | 
				
			||||||
        GuruAnalyticsAudit.sessionTotal ++
 | 
					        GuruAnalyticsAudit.sessionTotal++
 | 
				
			||||||
 | 
					        return total
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun deliverProperty(name: String, value: String) {
 | 
					    override fun deliverProperty(name: String, value: String) {
 | 
				
			||||||
| 
						 | 
					@ -321,8 +374,8 @@ internal class EventEngine internal constructor(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun forceUpload() {
 | 
					    internal fun forceUpload(scene: String = "unknown") {
 | 
				
			||||||
        forceUploadSubject.onNext(true)
 | 
					        forceTriggerSubject.onNext(scene)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getEventsStatics(): EventStatistic {
 | 
					    fun getEventsStatics(): EventStatistic {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,12 +2,15 @@ package guru.core.analytics.impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
 | 
					import android.transition.Scene
 | 
				
			||||||
import androidx.annotation.RequiresPermission
 | 
					import androidx.annotation.RequiresPermission
 | 
				
			||||||
import androidx.work.*
 | 
					import androidx.work.*
 | 
				
			||||||
import guru.core.analytics.Constants
 | 
					import guru.core.analytics.Constants
 | 
				
			||||||
import guru.core.analytics.GuruAnalytics
 | 
					import guru.core.analytics.GuruAnalytics
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.AnalyticsApiHost
 | 
				
			||||||
import guru.core.analytics.data.api.ServiceLocator
 | 
					import guru.core.analytics.data.api.ServiceLocator
 | 
				
			||||||
import guru.core.analytics.data.api.cronet.CronetHelper
 | 
					import guru.core.analytics.data.api.cronet.CronetHelper
 | 
				
			||||||
 | 
					import guru.core.analytics.data.api.dns.DnsMode
 | 
				
			||||||
import guru.core.analytics.data.db.GuruAnalyticsDatabase
 | 
					import guru.core.analytics.data.db.GuruAnalyticsDatabase
 | 
				
			||||||
import guru.core.analytics.data.db.model.EventStatistic
 | 
					import guru.core.analytics.data.db.model.EventStatistic
 | 
				
			||||||
import guru.core.analytics.data.local.PreferencesManager
 | 
					import guru.core.analytics.data.local.PreferencesManager
 | 
				
			||||||
| 
						 | 
					@ -35,26 +38,28 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        private val reservedEventNames = setOf("test")
 | 
					        private val reservedEventNames = setOf("test")
 | 
				
			||||||
        private val internalVersion = "0.2.1.0"
 | 
					        private val internalVersion = "0.2.1.0"
 | 
				
			||||||
 | 
					        internal val timberPlanted = AtomicBoolean(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val initialized = AtomicBoolean(false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private var engine: EventEngine? = null
 | 
					    private var engine: EventEngine? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var lifecycleMonitor: AppLifecycleMonitor? = null
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private var debugMode = false
 | 
					    private var debugMode = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val deliverExecutor = Executors.newSingleThreadExecutor()
 | 
					    private val deliverExecutor = Executors.newSingleThreadExecutor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val delivers = CopyOnWriteArrayList<EventDeliver>()
 | 
					    private val delivers = CopyOnWriteArrayList<EventDeliver>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val initialized = AtomicBoolean(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun isDebug(): Boolean = debugMode
 | 
					    override fun isDebug(): Boolean = debugMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun setDebug(debug: Boolean) {
 | 
					    override fun setDebug(debug: Boolean) {
 | 
				
			||||||
        debugMode = debug
 | 
					        debugMode = debug
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun isInitialized() = initialized.get()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String) {
 | 
					    override fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String) {
 | 
				
			||||||
        if (updateEventBaseUrl.isEmpty()) {
 | 
					        if (updateEventBaseUrl.isEmpty()) {
 | 
				
			||||||
            throw IllegalArgumentException("updateEventBaseUrl:${updateEventBaseUrl} is empty")
 | 
					            throw IllegalArgumentException("updateEventBaseUrl:${updateEventBaseUrl} is empty")
 | 
				
			||||||
| 
						 | 
					@ -82,53 +87,70 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
 | 
				
			||||||
        mainProcess: String?,
 | 
					        mainProcess: String?,
 | 
				
			||||||
        isEnableCronet: Boolean?,
 | 
					        isEnableCronet: Boolean?,
 | 
				
			||||||
        uploadIpAddress: List<String>?,
 | 
					        uploadIpAddress: List<String>?,
 | 
				
			||||||
 | 
					        dnsMode: Int?
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        if (initialized.compareAndSet(false, true)) {
 | 
					        if (initialized.compareAndSet(false, true)) {
 | 
				
			||||||
            delivers.add(EventDispatcher)
 | 
					 | 
				
			||||||
            eventHandlerCallback?.let { EventHandler.INSTANCE.addEventHandler(it) }
 | 
					 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            val debugApp = SystemProperties.read("debug.guru.analytics.app")
 | 
					            val debugApp = SystemProperties.read("debug.guru.analytics.app")
 | 
				
			||||||
            val forceDebug = debugApp == context.packageName
 | 
					            val forceDebug = debugApp == context.packageName
 | 
				
			||||||
            if (forceDebug || persistableLog) {
 | 
					            if (forceDebug || persistableLog) {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    if (timberPlanted.compareAndSet(false, true)) {
 | 
				
			||||||
                        Timber.plant(PersistentTree(context, debug = debug))
 | 
					                        Timber.plant(PersistentTree(context, debug = debug))
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                } catch (_: Throwable) {
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_1)
 | 
				
			||||||
 | 
					            EventInfoStore.initialize(context)
 | 
				
			||||||
 | 
					            delivers.add(EventDispatcher)
 | 
				
			||||||
 | 
					            eventHandlerCallback?.let { EventHandler.INSTANCE.addEventHandler(it) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_2)
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val debugUrl = SystemProperties.read("debug.guru.analytics.url")
 | 
					            val debugUrl = SystemProperties.read("debug.guru.analytics.url")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            debugMode = forceDebug || debug
 | 
					            debugMode = forceDebug || debug
 | 
				
			||||||
            Timber.d("[$internalVersion]initialize batchLimit:$batchLimit uploadPeriodInSecond:$uploadPeriodInSeconds startUploadDelayInSecond:$startUploadDelayInSecond eventExpiredInDays:$eventExpiredInDays debug:$debugMode debugUrl:$debugUrl")
 | 
					            Timber.d("[$internalVersion]initialize batchLimit:$batchLimit uploadPeriodInSecond:$uploadPeriodInSeconds startUploadDelayInSecond:$startUploadDelayInSecond eventExpiredInDays:$eventExpiredInDays debug:$debugMode debugUrl:$debugUrl uploadIpAddress:$uploadIpAddress dnsMode:$dnsMode")
 | 
				
			||||||
            GuruAnalyticsDatabase.initialize(context.applicationContext)
 | 
					            GuruAnalyticsDatabase.initialize(context.applicationContext)
 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_3)
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val baseUrl = if (forceDebug && debugUrl.isNotEmpty()) debugUrl else uploadEventBaseUrl
 | 
					            val baseUrl = if (forceDebug && debugUrl.isNotEmpty()) debugUrl else (uploadEventBaseUrl ?: AnalyticsApiHost.BASE_URL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            DeviceInfoStore.setDeviceInfo(context)
 | 
					            // TODO: 此处存在安全隐患,后续需重构
 | 
				
			||||||
            ServiceLocator.setDebug(debug)
 | 
					            ServiceLocator.setDebug(debug)
 | 
				
			||||||
            ServiceLocator.addHeaderParam("X-APP-ID", xAppId)
 | 
					            ServiceLocator.addHeaderParam("X-APP-ID", xAppId)
 | 
				
			||||||
            ServiceLocator.addHeaderParam("X-DEVICE-INFO", xDeviceInfo)
 | 
					            ServiceLocator.addHeaderParam("X-DEVICE-INFO", xDeviceInfo)
 | 
				
			||||||
            ServiceLocator.setUploadIpAddress(uploadIpAddress)
 | 
					            ServiceLocator.setUploadIpAddress(uploadIpAddress)
 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_4)
 | 
					            ServiceLocator.setDnsMode(dnsMode ?: 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            CronetHelper.init(context, isEnableCronet) {
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_4)
 | 
				
			||||||
 | 
					            if (isEnableCronet == true) {
 | 
				
			||||||
 | 
					                GuruAnalyticsAudit.enabledCronet = true
 | 
				
			||||||
 | 
					                ServiceLocator.setCronet(true)
 | 
				
			||||||
 | 
					                CronetHelper.init(context, true) {
 | 
				
			||||||
                    if (it) {
 | 
					                    if (it) {
 | 
				
			||||||
                        setUserProperty(Constants.Properties.GURU_ANM, Constants.AnmState.CRONET)
 | 
					                        setUserProperty(Constants.Properties.GURU_ANM, Constants.AnmState.CRONET)
 | 
				
			||||||
                        GuruAnalyticsAudit.useCronet = true
 | 
					                        GuruAnalyticsAudit.useCronet = true
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_5)
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var uploadEventBaseUri: Uri? = null
 | 
					            var uploadEventBaseUri: Uri? = null
 | 
				
			||||||
 | 
					            var hostname: String? = ""
 | 
				
			||||||
            if (!baseUrl.isNullOrBlank()) {
 | 
					            if (!baseUrl.isNullOrBlank()) {
 | 
				
			||||||
                uploadEventBaseUri =
 | 
					                uploadEventBaseUri =
 | 
				
			||||||
                    kotlin.runCatching { Uri.parse(baseUrl) }.getOrNull()
 | 
					                    kotlin.runCatching { Uri.parse(baseUrl) }.getOrNull()
 | 
				
			||||||
                if (uploadEventBaseUri?.scheme?.startsWith("http") != true) {
 | 
					                if (uploadEventBaseUri?.scheme?.startsWith("http") != true) {
 | 
				
			||||||
                    throw IllegalArgumentException("initialize updateEventBaseUrl:${baseUrl} incorrect format")
 | 
					                    throw IllegalArgumentException("initialize updateEventBaseUrl:${baseUrl} incorrect format")
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                hostname = uploadEventBaseUri.host
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_6)
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ServiceLocator.preloadDns(hostname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            engine = EventEngine(
 | 
					            engine = EventEngine(
 | 
				
			||||||
                context,
 | 
					                context,
 | 
				
			||||||
                batchLimit = batchLimit ?: EventEngine.DEFAULT_BATCH_LIMIT,
 | 
					                batchLimit = batchLimit ?: EventEngine.DEFAULT_BATCH_LIMIT,
 | 
				
			||||||
| 
						 | 
					@ -137,7 +159,7 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
 | 
				
			||||||
                eventExpiredInDays = EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS.coerceAtLeast(
 | 
					                eventExpiredInDays = EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS.coerceAtLeast(
 | 
				
			||||||
                    eventExpiredInDays ?: EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS
 | 
					                    eventExpiredInDays ?: EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                uploadEventBaseUri = uploadEventBaseUri
 | 
					                guruRepository = ServiceLocator.provideGuruRepository(context, baseUri = uploadEventBaseUri) /// 此处需要配合 ServiceLocator的重构进行修改
 | 
				
			||||||
            ).apply {
 | 
					            ).apply {
 | 
				
			||||||
                delivers.add(this)
 | 
					                delivers.add(this)
 | 
				
			||||||
                start(startUploadDelayInSecond)
 | 
					                start(startUploadDelayInSecond)
 | 
				
			||||||
| 
						 | 
					@ -147,7 +169,11 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
 | 
				
			||||||
                val process = AndroidUtils.getProcessName(context)
 | 
					                val process = AndroidUtils.getProcessName(context)
 | 
				
			||||||
                Timber.d("initialize ${mainProcess == process} currentProcess:$process mainProcess:$mainProcess")
 | 
					                Timber.d("initialize ${mainProcess == process} currentProcess:$process mainProcess:$mainProcess")
 | 
				
			||||||
                if (mainProcess.isNullOrBlank() || mainProcess == process) {
 | 
					                if (mainProcess.isNullOrBlank() || mainProcess == process) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
                        initAnalyticsPeriodic(context)
 | 
					                        initAnalyticsPeriodic(context)
 | 
				
			||||||
 | 
					                    } catch (throwable: Throwable) {
 | 
				
			||||||
 | 
					                        Timber.d("init worker error!")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    if (!process.isNullOrBlank()) {
 | 
					                    if (!process.isNullOrBlank()) {
 | 
				
			||||||
                        val params = mutableMapOf<String, Any>()
 | 
					                        val params = mutableMapOf<String, Any>()
 | 
				
			||||||
| 
						 | 
					@ -173,6 +199,10 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
 | 
				
			||||||
            if (!uploadEventBaseUrl.isNullOrEmpty()) {
 | 
					            if (!uploadEventBaseUrl.isNullOrEmpty()) {
 | 
				
			||||||
                PreferencesManager.getInstance(context).uploadEventBaseUrl = baseUrl
 | 
					                PreferencesManager.getInstance(context).uploadEventBaseUrl = baseUrl
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            PreferencesManager.getInstance(context).dnsMode = dnsMode ?: DnsMode.DEFAULT
 | 
				
			||||||
 | 
					            if (uploadIpAddress != null) {
 | 
				
			||||||
 | 
					                PreferencesManager.getInstance(context).uploadIpAddressList = uploadIpAddress.joinToString("|")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_9)
 | 
					            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_9)
 | 
				
			||||||
            GuruAnalyticsAudit.initialized = true
 | 
					            GuruAnalyticsAudit.initialized = true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -180,28 +210,35 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun initAnalyticsPeriodic(context: Context) {
 | 
					    private fun initAnalyticsPeriodic(context: Context) {
 | 
				
			||||||
        val constraints = Constraints.Builder()
 | 
					        val constraints = Constraints.Builder()
 | 
				
			||||||
            .setRequiredNetworkType(NetworkType.CONNECTED)
 | 
					            .setRequiredNetworkType(NetworkType.UNMETERED)
 | 
				
			||||||
            .build()
 | 
					            .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val request = PeriodicWorkRequestBuilder<AnalyticsWorker>(
 | 
					        val request = PeriodicWorkRequestBuilder<AnalyticsWorker>(
 | 
				
			||||||
            6, TimeUnit.HOURS,
 | 
					            40, TimeUnit.MINUTES,
 | 
				
			||||||
            15, TimeUnit.MINUTES
 | 
					            15, TimeUnit.MINUTES
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
            .setBackoffCriteria(BackoffPolicy.LINEAR, 15, TimeUnit.MINUTES)
 | 
					            .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)
 | 
				
			||||||
            .addTag(AnalyticsWorker.WORKER_TAG)
 | 
					            .addTag(AnalyticsWorker.WORKER_TAG)
 | 
				
			||||||
            .setConstraints(constraints)
 | 
					            .setConstraints(constraints)
 | 
				
			||||||
            .build()
 | 
					            .build()
 | 
				
			||||||
        Timber.d("initAnalyticsPeriodic")
 | 
					
 | 
				
			||||||
        WorkManager.getInstance(context)
 | 
					        WorkManager.getInstance(context)
 | 
				
			||||||
            .enqueueUniquePeriodicWork(
 | 
					            .enqueueUniquePeriodicWork(
 | 
				
			||||||
                AnalyticsWorker.WORKER_ID,
 | 
					                AnalyticsWorker.WORKER_NAME,
 | 
				
			||||||
                ExistingPeriodicWorkPolicy.REPLACE, request
 | 
					                ExistingPeriodicWorkPolicy.KEEP, request
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//        WorkManager.getInstance(context)
 | 
				
			||||||
 | 
					//            .cancelAllWorkByTag(
 | 
				
			||||||
 | 
					//                "AnalyticsWorker",
 | 
				
			||||||
 | 
					//            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val extMap = mapOf(
 | 
					        val extMap = mapOf(
 | 
				
			||||||
            "repeatInterval" to "6h",
 | 
					            "repeatInterval" to "40m",
 | 
				
			||||||
            "flexTimeInterval" to "15m",
 | 
					            "flexTimeInterval" to "15m",
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        Timber.d("initAnalyticsPeriodic completed")
 | 
				
			||||||
        EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.PERIODIC_WORK_ENQUEUE, extMap)
 | 
					        EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.PERIODIC_WORK_ENQUEUE, extMap)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -326,6 +363,24 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
 | 
				
			||||||
        return GsonUtil.gson.toJson(snapshot)
 | 
					        return GsonUtil.gson.toJson(snapshot)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun clearStatistic(context: Context) {
 | 
				
			||||||
 | 
					        PreferencesManager.getInstance(context).apply {
 | 
				
			||||||
 | 
					            eventCountAll = 0
 | 
				
			||||||
 | 
					            eventCountDeleted = 0
 | 
				
			||||||
 | 
					            eventCountUploaded = 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun forceUpload(scene: String): Boolean {
 | 
				
			||||||
 | 
					        return engine?.let {
 | 
				
			||||||
 | 
					            if (it.started.get()) {
 | 
				
			||||||
 | 
					                it.forceUpload(scene)
 | 
				
			||||||
 | 
					                return@let true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return@let false
 | 
				
			||||||
 | 
					        } ?: false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
 | 
					    private fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
 | 
				
			||||||
        Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!")
 | 
					        Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!")
 | 
				
			||||||
        deliverExecutor.execute {
 | 
					        deliverExecutor.execute {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,218 @@
 | 
				
			||||||
 | 
					package guru.core.analytics.utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.BroadcastReceiver
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.Intent
 | 
				
			||||||
 | 
					import android.content.IntentFilter
 | 
				
			||||||
 | 
					import android.net.ConnectivityManager
 | 
				
			||||||
 | 
					import android.net.ConnectivityManager.NetworkCallback
 | 
				
			||||||
 | 
					import android.net.Network
 | 
				
			||||||
 | 
					import android.net.NetworkCapabilities
 | 
				
			||||||
 | 
					import android.os.Build
 | 
				
			||||||
 | 
					import android.os.Handler
 | 
				
			||||||
 | 
					import android.os.Looper
 | 
				
			||||||
 | 
					import io.reactivex.BackpressureStrategy
 | 
				
			||||||
 | 
					import io.reactivex.Flowable
 | 
				
			||||||
 | 
					import io.reactivex.subjects.BehaviorSubject
 | 
				
			||||||
 | 
					import timber.log.Timber
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicBoolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Connectivity(private val context: Context) : BroadcastReceiver() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        const val CONNECTIVITY_NONE: String = "none"
 | 
				
			||||||
 | 
					        const val CONNECTIVITY_WIFI: String = "wifi"
 | 
				
			||||||
 | 
					        const val CONNECTIVITY_MOBILE: String = "mobile"
 | 
				
			||||||
 | 
					        const val CONNECTIVITY_ETHERNET: String = "ethernet"
 | 
				
			||||||
 | 
					        const val CONNECTIVITY_BLUETOOTH: String = "bluetooth"
 | 
				
			||||||
 | 
					        const val CONNECTIVITY_VPN: String = "vpn"
 | 
				
			||||||
 | 
					        const val CONNECTIVITY_OTHER: String = "other"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const val CONNECTIVITY_ACTION: String = "android.net.conn.CONNECTIVITY_CHANGE"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val connectivityManager by lazy {
 | 
				
			||||||
 | 
					        return@lazy context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val mainHandler by lazy {
 | 
				
			||||||
 | 
					        Handler(Looper.getMainLooper())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val networkTypesSubject: BehaviorSubject<List<String>> = BehaviorSubject.createDefault(emptyList())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val networkAvailableFlowable: Flowable<Boolean>
 | 
				
			||||||
 | 
					        get() = networkTypesSubject.toFlowable(BackpressureStrategy.DROP).map {
 | 
				
			||||||
 | 
					            return@map isNetworkAvailableByTypes(it)
 | 
				
			||||||
 | 
					        }.throttleLast(5, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val bound = AtomicBoolean(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private var networkCallback: NetworkCallback? = null
 | 
				
			||||||
 | 
					    private fun sendCurrentNetworkTypesDelay() {
 | 
				
			||||||
 | 
					        val runnable = Runnable { sendNetworkTypes(getNetworkTypes()) }
 | 
				
			||||||
 | 
					        // Emit events on main thread
 | 
				
			||||||
 | 
					        // 500 milliseconds to avoid race conditions
 | 
				
			||||||
 | 
					        mainHandler.postDelayed(runnable, 500)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun sendNetworkTypes(types: List<String>) {
 | 
				
			||||||
 | 
					        networkTypesSubject.onNext(types)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getNetworkTypes(): List<String> {
 | 
				
			||||||
 | 
					        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
				
			||||||
 | 
					            val network = connectivityManager.activeNetwork
 | 
				
			||||||
 | 
					            return getCapabilitiesFromNetwork(network)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // For legacy versions, return a single type as before or adapt similarly if multiple types
 | 
				
			||||||
 | 
					            // need to be supported
 | 
				
			||||||
 | 
					            return getNetworkTypesLegacy()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun isNetworkAvailable(): Boolean {
 | 
				
			||||||
 | 
					        val types = getNetworkTypes()
 | 
				
			||||||
 | 
					        return isNetworkAvailableByTypes(types)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val isActiveNetworkMetered: Boolean
 | 
				
			||||||
 | 
					        get() = connectivityManager.isActiveNetworkMetered
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun isNetworkAvailableByTypes(types: List<String>): Boolean {
 | 
				
			||||||
 | 
					        return types.contains(CONNECTIVITY_WIFI) || types.contains(CONNECTIVITY_MOBILE) || types.contains(CONNECTIVITY_ETHERNET)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getCapabilitiesFromNetwork(network: Network?): List<String> {
 | 
				
			||||||
 | 
					        val capabilities = connectivityManager.getNetworkCapabilities(network)
 | 
				
			||||||
 | 
					        return getCapabilitiesList(capabilities)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getCapabilitiesList(capabilities: NetworkCapabilities?): List<String> {
 | 
				
			||||||
 | 
					        val types: MutableList<String> = ArrayList()
 | 
				
			||||||
 | 
					        if (capabilities == null
 | 
				
			||||||
 | 
					            || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_NONE)
 | 
				
			||||||
 | 
					            return types
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
 | 
				
			||||||
 | 
					            || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_WIFI)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_ETHERNET)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_VPN)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_MOBILE)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_BLUETOOTH)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (types.isEmpty()
 | 
				
			||||||
 | 
					            && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_OTHER)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (types.isEmpty()) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_NONE)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return types
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getNetworkTypesLegacy(): List<String> {
 | 
				
			||||||
 | 
					        // handle type for Android versions less than Android 6
 | 
				
			||||||
 | 
					        val info = connectivityManager.activeNetworkInfo
 | 
				
			||||||
 | 
					        val types: MutableList<String> = java.util.ArrayList()
 | 
				
			||||||
 | 
					        if (info == null || !info.isConnected) {
 | 
				
			||||||
 | 
					            types.add(CONNECTIVITY_NONE)
 | 
				
			||||||
 | 
					            return types
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val type = info.type
 | 
				
			||||||
 | 
					        when (type) {
 | 
				
			||||||
 | 
					            ConnectivityManager.TYPE_BLUETOOTH -> types.add(Connectivity.CONNECTIVITY_BLUETOOTH)
 | 
				
			||||||
 | 
					            ConnectivityManager.TYPE_ETHERNET -> types.add(Connectivity.CONNECTIVITY_ETHERNET)
 | 
				
			||||||
 | 
					            ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_WIMAX -> types.add(Connectivity.CONNECTIVITY_WIFI)
 | 
				
			||||||
 | 
					            ConnectivityManager.TYPE_VPN -> types.add(Connectivity.CONNECTIVITY_VPN)
 | 
				
			||||||
 | 
					            ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_MOBILE_DUN, ConnectivityManager.TYPE_MOBILE_HIPRI -> types.add(Connectivity.CONNECTIVITY_MOBILE)
 | 
				
			||||||
 | 
					            else -> types.add(Connectivity.CONNECTIVITY_OTHER)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return types
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun bind() {
 | 
				
			||||||
 | 
					        if (!bound.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
					            Timber.d("Connectivity Already bind!")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 | 
				
			||||||
 | 
					            networkCallback = object : NetworkCallback() {
 | 
				
			||||||
 | 
					                override fun onAvailable(network: Network) {
 | 
				
			||||||
 | 
					                    // onAvailable is called when the phone switches to a new network
 | 
				
			||||||
 | 
					                    // e.g. the phone was offline and gets wifi connection
 | 
				
			||||||
 | 
					                    // or the phone was on wifi and now switches to mobile.
 | 
				
			||||||
 | 
					                    // The plugin sends the current capability connection to the users.
 | 
				
			||||||
 | 
					                    val types = getCapabilitiesFromNetwork(network)
 | 
				
			||||||
 | 
					                    sendNetworkTypes(types)
 | 
				
			||||||
 | 
					                    Timber.tag("Connectivity").d("onAvailable:${types}")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                override fun onCapabilitiesChanged(
 | 
				
			||||||
 | 
					                    network: Network, networkCapabilities: NetworkCapabilities
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    // This callback is called multiple times after a call to onAvailable
 | 
				
			||||||
 | 
					                    // this also causes multiple callbacks to the Flutter layer.
 | 
				
			||||||
 | 
					                    val types = getCapabilitiesList(networkCapabilities)
 | 
				
			||||||
 | 
					                    sendNetworkTypes(types)
 | 
				
			||||||
 | 
					                    Timber.tag("Connectivity").d("onCapabilitiesChanged:${types}")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                override fun onLost(network: Network) {
 | 
				
			||||||
 | 
					                    // This callback is called when a capability is lost.
 | 
				
			||||||
 | 
					                    //
 | 
				
			||||||
 | 
					                    // The provided Network object contains information about the
 | 
				
			||||||
 | 
					                    // network capability that has been lost, so we cannot use it.
 | 
				
			||||||
 | 
					                    //
 | 
				
			||||||
 | 
					                    // Instead, post the current network but with a delay long enough
 | 
				
			||||||
 | 
					                    // that we avoid a race condition.
 | 
				
			||||||
 | 
					                    sendCurrentNetworkTypesDelay()
 | 
				
			||||||
 | 
					                    Timber.tag("Connectivity").d("onLost")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }.apply {
 | 
				
			||||||
 | 
					                connectivityManager.registerDefaultNetworkCallback(this)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            context.registerReceiver(this, IntentFilter(CONNECTIVITY_ACTION))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Need to emit first event with connectivity types without waiting for first change in system
 | 
				
			||||||
 | 
					        // that might happen much later
 | 
				
			||||||
 | 
					        sendNetworkTypes(getNetworkTypes())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun unbind() {
 | 
				
			||||||
 | 
					        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 | 
				
			||||||
 | 
					            networkCallback = networkCallback?.let {
 | 
				
			||||||
 | 
					                connectivityManager.unregisterNetworkCallback(it)
 | 
				
			||||||
 | 
					                null
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                context.unregisterReceiver(this)
 | 
				
			||||||
 | 
					            } catch (e: Exception) {
 | 
				
			||||||
 | 
					                // listen never called, ignore the error
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun onReceive(context: Context?, intent: Intent?) {
 | 
				
			||||||
 | 
					        sendNetworkTypes(getNetworkTypes())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue