Compare commits
	
		
			3 Commits 
		
	
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						d5ec200617 | |
| 
							
							
								
								 | 
						3803d53afe | |
| 
							
							
								
								 | 
						5927987ff5 | 
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
.gradle
 | 
			
		||||
/captures
 | 
			
		||||
/local.properties
 | 
			
		||||
/.idea/workspace.xml
 | 
			
		||||
.DS_Store
 | 
			
		||||
/build
 | 
			
		||||
.idea/
 | 
			
		||||
*iml
 | 
			
		||||
*.iml
 | 
			
		||||
*/build
 | 
			
		||||
/lib
 | 
			
		||||
wh.properties
 | 
			
		||||
/atom
 | 
			
		||||
*.txt
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
/build
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    id 'com.android.library'
 | 
			
		||||
    id 'org.jetbrains.kotlin.android'
 | 
			
		||||
    id 'maven-publish'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: 'maven-publish.gradle'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileSdk 33
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        minSdk 21
 | 
			
		||||
        targetSdk 33
 | 
			
		||||
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
        consumerProguardFiles "consumer-rules.pro"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            minifyEnabled false
 | 
			
		||||
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = '1.8'
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
 | 
			
		||||
    implementation 'androidx.core:core-ktx:1.7.0'
 | 
			
		||||
    testImplementation 'junit:junit:4.13.2'
 | 
			
		||||
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
 | 
			
		||||
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
 | 
			
		||||
 | 
			
		||||
    implementation 'com.google.android.ump:user-messaging-platform:2.1.0'
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
publishing {    // Repositories *to* which Gradle can publish artifacts
 | 
			
		||||
    repositories { RepositoryHandler handler ->
 | 
			
		||||
        handler.mavenLocal()
 | 
			
		||||
        maven {
 | 
			
		||||
            url "$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    publications { PublicationContainer publication ->
 | 
			
		||||
 | 
			
		||||
        // Creates a Maven publication called "myPublication".
 | 
			
		||||
        maven(MavenPublication) {
 | 
			
		||||
            groupId 'guru.core.consent'
 | 
			
		||||
            artifactId 'GuruConsent'
 | 
			
		||||
            version '1.1.0'             // Your package version
 | 
			
		||||
//                artifact publishArtifact    //Example: *./target/myJavaClasses.jar*
 | 
			
		||||
//            artifact "build/outputs/aar/aar-test-release.aar"//aar包的目录
 | 
			
		||||
            afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) } // 方式一:生成aar包
 | 
			
		||||
 | 
			
		||||
            //带上依赖 ,否则会报错
 | 
			
		||||
            pom.withXml {
 | 
			
		||||
                def dependenciesNode = asNode().appendNode('dependencies')
 | 
			
		||||
 | 
			
		||||
                def scopes = []
 | 
			
		||||
                if (configurations.hasProperty("api")) {
 | 
			
		||||
                    scopes.add(configurations.api)
 | 
			
		||||
                }
 | 
			
		||||
                if (configurations.hasProperty("implementation")) {
 | 
			
		||||
                    scopes.add(configurations.implementation)
 | 
			
		||||
                }
 | 
			
		||||
                if (configurations.hasProperty("debugImplementation")) {
 | 
			
		||||
                    scopes.add(configurations.debugImplementation)
 | 
			
		||||
                }
 | 
			
		||||
                if (configurations.hasProperty("releaseImplementation")) {
 | 
			
		||||
                    scopes.add(configurations.releaseImplementation)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
//                    if (project.ext.targetType != "jar") {
 | 
			
		||||
//                        scopes.add(configurations.provided)
 | 
			
		||||
//                    }
 | 
			
		||||
 | 
			
		||||
                scopes.each { scope ->
 | 
			
		||||
                    scope.allDependencies.each {
 | 
			
		||||
                        if (it instanceof ModuleDependency) {
 | 
			
		||||
                            boolean isTransitive = ((ModuleDependency) it).transitive
 | 
			
		||||
                            if (!isTransitive) {
 | 
			
		||||
                                println "<<<< not transitive dependency: [${it.group}, ${it.name}, ${it.version}]"
 | 
			
		||||
                                return
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (it.group == "${project.rootProject.name}.libs" || it.version == 'unspecified') {
 | 
			
		||||
                            return
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (it.group && it.name && it.version) {
 | 
			
		||||
                            def dependencyNode = dependenciesNode.appendNode('dependency')
 | 
			
		||||
                            dependencyNode.appendNode('groupId', it.group)
 | 
			
		||||
                            dependencyNode.appendNode('artifactId', it.name)
 | 
			
		||||
                            dependencyNode.appendNode('version', it.version)
 | 
			
		||||
                            dependencyNode.appendNode('scope', scope.name)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
# Add project specific ProGuard rules here.
 | 
			
		||||
# You can control the set of applied configuration files using the
 | 
			
		||||
# proguardFiles setting in build.gradle.
 | 
			
		||||
#
 | 
			
		||||
# For more details, see
 | 
			
		||||
#   http://developer.android.com/guide/developing/tools/proguard.html
 | 
			
		||||
 | 
			
		||||
# If your project uses WebView with JS, uncomment the following
 | 
			
		||||
# and specify the fully qualified class name to the JavaScript interface
 | 
			
		||||
# class:
 | 
			
		||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
 | 
			
		||||
#   public *;
 | 
			
		||||
#}
 | 
			
		||||
 | 
			
		||||
# Uncomment this to preserve the line number information for
 | 
			
		||||
# debugging stack traces.
 | 
			
		||||
#-keepattributes SourceFile,LineNumberTable
 | 
			
		||||
 | 
			
		||||
# If you keep the line number information, uncomment this to
 | 
			
		||||
# hide the original source file name.
 | 
			
		||||
#-renamesourcefileattribute SourceFile
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
package guru.core.consent
 | 
			
		||||
 | 
			
		||||
import androidx.test.platform.app.InstrumentationRegistry
 | 
			
		||||
import androidx.test.ext.junit.runners.AndroidJUnit4
 | 
			
		||||
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
import org.junit.runner.RunWith
 | 
			
		||||
 | 
			
		||||
import org.junit.Assert.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Instrumented test, which will execute on an Android device.
 | 
			
		||||
 *
 | 
			
		||||
 * See [testing documentation](http://d.android.com/tools/testing).
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(AndroidJUnit4::class)
 | 
			
		||||
class ExampleInstrumentedTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun useAppContext() {
 | 
			
		||||
        // Context of the app under test.
 | 
			
		||||
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
 | 
			
		||||
        assertEquals("guru.core.consent.test", appContext.packageName)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    package="guru.core.consent">
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,85 @@
 | 
			
		|||
package guru.core.consent.gdpr
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.IntDef
 | 
			
		||||
import com.google.android.ump.ConsentDebugSettings
 | 
			
		||||
import com.google.android.ump.ConsentInformation
 | 
			
		||||
import com.google.android.ump.FormError
 | 
			
		||||
 | 
			
		||||
@IntDef(
 | 
			
		||||
    AdsRes.LAYOUT,
 | 
			
		||||
    AdsRes.HEADLINE,
 | 
			
		||||
    AdsRes.BODY,
 | 
			
		||||
    AdsRes.CALL_TO_ACTION,
 | 
			
		||||
    AdsRes.APP_ICON,
 | 
			
		||||
    AdsRes.ADVERTISER,
 | 
			
		||||
    AdsRes.MEDIA,
 | 
			
		||||
    AdsRes.AD_CHOICES,
 | 
			
		||||
    AdsRes.PRICE,
 | 
			
		||||
    AdsRes.STORE,
 | 
			
		||||
    AdsRes.STAR_RATING
 | 
			
		||||
)
 | 
			
		||||
@Retention(AnnotationRetention.BINARY)
 | 
			
		||||
@Target(
 | 
			
		||||
    AnnotationTarget.FUNCTION,
 | 
			
		||||
    AnnotationTarget.VALUE_PARAMETER,
 | 
			
		||||
    AnnotationTarget.FIELD,
 | 
			
		||||
    AnnotationTarget.LOCAL_VARIABLE,
 | 
			
		||||
    AnnotationTarget.CLASS
 | 
			
		||||
)
 | 
			
		||||
annotation class AdsRes {
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val LAYOUT = 0
 | 
			
		||||
        const val HEADLINE = 1
 | 
			
		||||
        const val BODY = 2
 | 
			
		||||
        const val CALL_TO_ACTION = 3
 | 
			
		||||
        const val APP_ICON = 4
 | 
			
		||||
        const val ADVERTISER = 5
 | 
			
		||||
        const val MEDIA = 6
 | 
			
		||||
        const val AD_CHOICES = 7
 | 
			
		||||
        const val PRICE = 8
 | 
			
		||||
        const val STORE = 9
 | 
			
		||||
        const val STAR_RATING = 10
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@IntDef(ConsentDebugGeography.DISABLED, ConsentDebugGeography.EEA, ConsentDebugGeography.NOT_EEA)
 | 
			
		||||
@Retention(AnnotationRetention.SOURCE)
 | 
			
		||||
annotation class ConsentDebugGeography {
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val DISABLED = ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_DISABLED
 | 
			
		||||
        const val EEA = ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA
 | 
			
		||||
        const val NOT_EEA = ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_NOT_EEA
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@IntDef(
 | 
			
		||||
    ConsentStatus.NOT_AVAILABLE, ConsentStatus.UNKNOWN,
 | 
			
		||||
    ConsentStatus.NOT_REQUIRED, ConsentStatus.REQUIRED, ConsentStatus.OBTAINED
 | 
			
		||||
)
 | 
			
		||||
@Retention(AnnotationRetention.SOURCE)
 | 
			
		||||
annotation class ConsentStatus {
 | 
			
		||||
    companion object {
 | 
			
		||||
        // Consent Form Not Available
 | 
			
		||||
        const val NOT_AVAILABLE = -100
 | 
			
		||||
 | 
			
		||||
        // Unknown consent status.
 | 
			
		||||
        const val UNKNOWN = ConsentInformation.ConsentStatus.UNKNOWN
 | 
			
		||||
 | 
			
		||||
        // User consent required but not yet obtained.
 | 
			
		||||
        const val NOT_REQUIRED = ConsentInformation.ConsentStatus.NOT_REQUIRED
 | 
			
		||||
 | 
			
		||||
        // User consent not required. For example, the user is not in the EEA or the UK.
 | 
			
		||||
        const val REQUIRED = ConsentInformation.ConsentStatus.REQUIRED
 | 
			
		||||
 | 
			
		||||
        // User consent obtained. Personalization not defined.
 | 
			
		||||
        const val OBTAINED = ConsentInformation.ConsentStatus.OBTAINED
 | 
			
		||||
 | 
			
		||||
        fun toName(@ConsentStatus status: Int) = when (status) {
 | 
			
		||||
            UNKNOWN -> "UNKNOWN"
 | 
			
		||||
            NOT_REQUIRED -> "NOT_REQUIRED"
 | 
			
		||||
            OBTAINED -> "OBTAINED"
 | 
			
		||||
            REQUIRED -> "REQUIRED"
 | 
			
		||||
            else -> "NOT_AVAILABLE"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
package guru.core.consent.gdpr
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import androidx.annotation.Keep
 | 
			
		||||
import com.google.android.ump.ConsentForm
 | 
			
		||||
import com.google.android.ump.ConsentInformation
 | 
			
		||||
import com.google.android.ump.UserMessagingPlatform
 | 
			
		||||
 | 
			
		||||
@Keep
 | 
			
		||||
class ConsentManager(private val activity: Activity) {
 | 
			
		||||
    private val consentInformation: ConsentInformation =
 | 
			
		||||
        UserMessagingPlatform.getConsentInformation(activity)
 | 
			
		||||
 | 
			
		||||
    /** Interface definition for a callback to be invoked when consent gathering is complete. */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /** Helper variable to determine if the app can request ads. */
 | 
			
		||||
    val canRequestAds: Boolean
 | 
			
		||||
        get() = consentInformation.canRequestAds()
 | 
			
		||||
 | 
			
		||||
    /** Helper variable to determine if the privacy options form is required. */
 | 
			
		||||
    val isPrivacyOptionsRequired: Boolean
 | 
			
		||||
        get() =
 | 
			
		||||
            consentInformation.privacyOptionsRequirementStatus ==
 | 
			
		||||
                    ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method to call the UMP SDK methods to request consent information and load/show a
 | 
			
		||||
     * consent form if necessary.
 | 
			
		||||
     */
 | 
			
		||||
    fun gather(request: ConsentRequest) {
 | 
			
		||||
 | 
			
		||||
        // Requesting an update to consent information should be called on every app launch.
 | 
			
		||||
        consentInformation.requestConsentInfoUpdate(
 | 
			
		||||
            activity,
 | 
			
		||||
            request.params,
 | 
			
		||||
            {
 | 
			
		||||
                UserMessagingPlatform.loadAndShowConsentFormIfRequired(activity) { formError ->
 | 
			
		||||
                    // Consent has been gathered.
 | 
			
		||||
                    request.consentGatheringCompleteListener?.consentGatheringComplete(formError)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            { requestConsentError ->
 | 
			
		||||
                request.consentGatheringCompleteListener?.consentGatheringComplete(
 | 
			
		||||
                    requestConsentError
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Helper method to call the UMP SDK method to show the privacy options form. */
 | 
			
		||||
    fun showPrivacyOptionsForm(
 | 
			
		||||
        activity: Activity,
 | 
			
		||||
        onConsentFormDismissedListener: ConsentForm.OnConsentFormDismissedListener
 | 
			
		||||
    ) {
 | 
			
		||||
        UserMessagingPlatform.showPrivacyOptionsForm(activity, onConsentFormDismissedListener)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
package guru.core.consent.gdpr
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import androidx.annotation.Keep
 | 
			
		||||
import com.google.android.ump.ConsentDebugSettings
 | 
			
		||||
import com.google.android.ump.ConsentRequestParameters
 | 
			
		||||
import com.google.android.ump.FormError
 | 
			
		||||
 | 
			
		||||
@Keep
 | 
			
		||||
class ConsentRequest(
 | 
			
		||||
    val activity: Activity,
 | 
			
		||||
    val params: ConsentRequestParameters,
 | 
			
		||||
    val listener: Listener?,
 | 
			
		||||
    val consentGatheringCompleteListener: OnConsentGatheringCompleteListener?
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    interface Listener {
 | 
			
		||||
        fun onConsentResult(@ConsentStatus status: Int)
 | 
			
		||||
 | 
			
		||||
        fun onConsentImpression()
 | 
			
		||||
 | 
			
		||||
        fun onConsentLoadFailure()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun interface OnConsentGatheringCompleteListener {
 | 
			
		||||
        fun consentGatheringComplete(error: FormError?)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Keep
 | 
			
		||||
    data class Builder(val activity: Activity) {
 | 
			
		||||
        private var tagForUnderAgeOfConsent: Boolean = false
 | 
			
		||||
        private var testDeviceIds: HashSet<String> = HashSet()
 | 
			
		||||
        private var consentListener: Listener? = null
 | 
			
		||||
        private var consentGatheringCompleteListener: OnConsentGatheringCompleteListener? = null
 | 
			
		||||
 | 
			
		||||
        @ConsentDebugGeography
 | 
			
		||||
        var debugGeography: Int? = null
 | 
			
		||||
 | 
			
		||||
        fun debugGeography(@ConsentDebugGeography geography: Int?) = apply {
 | 
			
		||||
            debugGeography = geography
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun tagForUnderAgeOfConsent(enabled: Boolean) = apply {
 | 
			
		||||
            tagForUnderAgeOfConsent = enabled
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun addDeviceIds(ids: Set<String>) = apply {
 | 
			
		||||
            testDeviceIds.addAll(ids)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun withListener(listener: Listener) = apply {
 | 
			
		||||
            consentListener = listener
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun withConsentGatheringCompleteListener(listener: OnConsentGatheringCompleteListener) =
 | 
			
		||||
            apply {
 | 
			
		||||
                consentGatheringCompleteListener = listener
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        fun build(): ConsentRequest {
 | 
			
		||||
            val builder = ConsentRequestParameters.Builder()
 | 
			
		||||
            if (debugGeography != null || testDeviceIds.isNotEmpty()) {
 | 
			
		||||
                builder.setConsentDebugSettings(
 | 
			
		||||
                    ConsentDebugSettings.Builder(activity).let {
 | 
			
		||||
                        debugGeography?.apply {
 | 
			
		||||
                            it.setDebugGeography(this)
 | 
			
		||||
                        }
 | 
			
		||||
                        for (id in testDeviceIds) {
 | 
			
		||||
                            it.addTestDeviceHashedId(id)
 | 
			
		||||
                        }
 | 
			
		||||
                        it.build()
 | 
			
		||||
                    })
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            return ConsentRequest(
 | 
			
		||||
                activity,
 | 
			
		||||
                builder.build(),
 | 
			
		||||
                consentListener,
 | 
			
		||||
                consentGatheringCompleteListener
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,101 @@
 | 
			
		|||
package guru.core.consent.gdpr
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import androidx.annotation.Keep
 | 
			
		||||
import com.google.android.ump.ConsentForm
 | 
			
		||||
import com.google.android.ump.ConsentInformation
 | 
			
		||||
import com.google.android.ump.UserMessagingPlatform
 | 
			
		||||
 | 
			
		||||
@Deprecated("Use ConsentManager instead")
 | 
			
		||||
@Keep
 | 
			
		||||
object GdprHelper {
 | 
			
		||||
 | 
			
		||||
    var consentForm: ConsentForm? = null
 | 
			
		||||
 | 
			
		||||
    fun request(request: ConsentRequest) {
 | 
			
		||||
        val consentInformation = UserMessagingPlatform.getConsentInformation(request.activity)
 | 
			
		||||
        consentInformation.requestConsentInfoUpdate(
 | 
			
		||||
                request.activity,
 | 
			
		||||
                request.params,
 | 
			
		||||
                {
 | 
			
		||||
                    // The consent information state was updated.
 | 
			
		||||
                    // You are now ready to check if a form is available.
 | 
			
		||||
                    if (consentInformation.isConsentFormAvailable) {
 | 
			
		||||
                        loadForm(request)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        request.listener?.onConsentResult(ConsentStatus.NOT_AVAILABLE)
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    // Handle the error.
 | 
			
		||||
//                    Timber.w("GdprHelper request error! ${it.message}")
 | 
			
		||||
                    request.listener?.onConsentLoadFailure()
 | 
			
		||||
                })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun loadForm(request: ConsentRequest) {
 | 
			
		||||
        UserMessagingPlatform.loadConsentForm(
 | 
			
		||||
                request.activity,
 | 
			
		||||
                { consentForm ->
 | 
			
		||||
                    run {
 | 
			
		||||
                        GdprHelper.consentForm = consentForm
 | 
			
		||||
                        val consentInformation = UserMessagingPlatform.getConsentInformation(request.activity)
 | 
			
		||||
                        if (consentInformation.consentStatus == ConsentInformation.ConsentStatus.REQUIRED) {
 | 
			
		||||
                            request.listener?.onConsentImpression()
 | 
			
		||||
                            consentForm.show(request.activity) { // Handle dismissal by reloading form.
 | 
			
		||||
                                checkForm(request)
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            request.listener?.onConsentResult(consentInformation.consentStatus)
 | 
			
		||||
                            grantConsent(request.activity)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    // Handle the error
 | 
			
		||||
//                    Timber.w("GdprHelper loadForm error! ${it.message}")
 | 
			
		||||
                    request.listener?.onConsentLoadFailure()
 | 
			
		||||
                }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun checkForm(request: ConsentRequest) {
 | 
			
		||||
        UserMessagingPlatform.loadConsentForm(
 | 
			
		||||
                request.activity,
 | 
			
		||||
                { consentForm ->
 | 
			
		||||
                    run {
 | 
			
		||||
                        GdprHelper.consentForm = consentForm
 | 
			
		||||
                        val consentInformation = UserMessagingPlatform.getConsentInformation(request.activity)
 | 
			
		||||
                        request.listener?.onConsentResult(consentInformation.consentStatus)
 | 
			
		||||
                        grantConsent(request.activity)
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    // Handle the error
 | 
			
		||||
//                    Timber.w("GdprHelper loadForm error! ${it.message}")
 | 
			
		||||
                    request.listener?.onConsentLoadFailure()
 | 
			
		||||
                }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun grantConsent(context: Context) {
 | 
			
		||||
        // Under the Google EU User Consent Policy, you must ensure that certain disclosures are given to,
 | 
			
		||||
        // and consents obtained from, users in the European Economic Area (EEA) regarding the use of device
 | 
			
		||||
        // identifiers and personal data. This policy reflects the requirements of the EU ePrivacy Directive
 | 
			
		||||
        // and the General Data Protection Regulation (GDPR). When seeking consent, you must identify each ad
 | 
			
		||||
        // network in your mediation chain that may collect, receive, or use personal data and provide information
 | 
			
		||||
        // about each network's use. Google currently is unable to pass the user's consent choice to such
 | 
			
		||||
        // networks automatically.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun revokeConsent() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun reset(activity: Activity) {
 | 
			
		||||
        val consentInformation = UserMessagingPlatform.getConsentInformation(activity)
 | 
			
		||||
        consentInformation.reset()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
package guru.core.consent
 | 
			
		||||
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
 | 
			
		||||
import org.junit.Assert.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Example local unit test, which will execute on the development machine (host).
 | 
			
		||||
 *
 | 
			
		||||
 * See [testing documentation](http://d.android.com/tools/testing).
 | 
			
		||||
 */
 | 
			
		||||
class ExampleUnitTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun addition_isCorrect() {
 | 
			
		||||
        assertEquals(4, 2 + 2)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
/build
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    id 'com.android.application'
 | 
			
		||||
    id 'org.jetbrains.kotlin.android'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileSdk 32
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        applicationId "com.example.gurugdprexample"
 | 
			
		||||
        minSdk 21
 | 
			
		||||
        targetSdk 31
 | 
			
		||||
        versionCode 1
 | 
			
		||||
        versionName "1.0"
 | 
			
		||||
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            minifyEnabled false
 | 
			
		||||
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = '1.8'
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
 | 
			
		||||
    implementation 'androidx.core:core-ktx:1.5.10'
 | 
			
		||||
    implementation 'androidx.appcompat:appcompat:1.5.1'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.7.0'
 | 
			
		||||
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
 | 
			
		||||
    testImplementation 'junit:junit:4.13.2'
 | 
			
		||||
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
 | 
			
		||||
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
 | 
			
		||||
 | 
			
		||||
//    implementation 'guru.core.consent:GuruConsent:1.0.0'
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
# Add project specific ProGuard rules here.
 | 
			
		||||
# You can control the set of applied configuration files using the
 | 
			
		||||
# proguardFiles setting in build.gradle.
 | 
			
		||||
#
 | 
			
		||||
# For more details, see
 | 
			
		||||
#   http://developer.android.com/guide/developing/tools/proguard.html
 | 
			
		||||
 | 
			
		||||
# If your project uses WebView with JS, uncomment the following
 | 
			
		||||
# and specify the fully qualified class name to the JavaScript interface
 | 
			
		||||
# class:
 | 
			
		||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
 | 
			
		||||
#   public *;
 | 
			
		||||
#}
 | 
			
		||||
 | 
			
		||||
# Uncomment this to preserve the line number information for
 | 
			
		||||
# debugging stack traces.
 | 
			
		||||
#-keepattributes SourceFile,LineNumberTable
 | 
			
		||||
 | 
			
		||||
# If you keep the line number information, uncomment this to
 | 
			
		||||
# hide the original source file name.
 | 
			
		||||
#-renamesourcefileattribute SourceFile
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
package com.example.gurugdprexample
 | 
			
		||||
 | 
			
		||||
import androidx.test.platform.app.InstrumentationRegistry
 | 
			
		||||
import androidx.test.ext.junit.runners.AndroidJUnit4
 | 
			
		||||
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
import org.junit.runner.RunWith
 | 
			
		||||
 | 
			
		||||
import org.junit.Assert.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Instrumented test, which will execute on an Android device.
 | 
			
		||||
 *
 | 
			
		||||
 * See [testing documentation](http://d.android.com/tools/testing).
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(AndroidJUnit4::class)
 | 
			
		||||
class ExampleInstrumentedTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun useAppContext() {
 | 
			
		||||
        // Context of the app under test.
 | 
			
		||||
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
 | 
			
		||||
        assertEquals("com.example.gurugdprexample", appContext.packageName)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    package="com.example.gurugdprexample">
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:allowBackup="true"
 | 
			
		||||
        android:dataExtractionRules="@xml/data_extraction_rules"
 | 
			
		||||
        android:fullBackupContent="@xml/backup_rules"
 | 
			
		||||
        android:icon="@mipmap/ic_launcher"
 | 
			
		||||
        android:label="@string/app_name"
 | 
			
		||||
        android:roundIcon="@mipmap/ic_launcher_round"
 | 
			
		||||
        android:supportsRtl="true"
 | 
			
		||||
        android:theme="@style/Theme.GuruGdprExample"
 | 
			
		||||
        tools:targetApi="31">
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".MainActivity"
 | 
			
		||||
            android:exported="true">
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.MAIN" />
 | 
			
		||||
 | 
			
		||||
                <category android:name="android.intent.category.LAUNCHER" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
package com.example.gurugdprexample
 | 
			
		||||
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
 | 
			
		||||
class MainActivity : AppCompatActivity() {
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        setContentView(R.layout.activity_main)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:aapt="http://schemas.android.com/aapt"
 | 
			
		||||
    android:width="108dp"
 | 
			
		||||
    android:height="108dp"
 | 
			
		||||
    android:viewportWidth="108"
 | 
			
		||||
    android:viewportHeight="108">
 | 
			
		||||
    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
 | 
			
		||||
        <aapt:attr name="android:fillColor">
 | 
			
		||||
            <gradient
 | 
			
		||||
                android:endX="85.84757"
 | 
			
		||||
                android:endY="92.4963"
 | 
			
		||||
                android:startX="42.9492"
 | 
			
		||||
                android:startY="49.59793"
 | 
			
		||||
                android:type="linear">
 | 
			
		||||
                <item
 | 
			
		||||
                    android:color="#44000000"
 | 
			
		||||
                    android:offset="0.0" />
 | 
			
		||||
                <item
 | 
			
		||||
                    android:color="#00000000"
 | 
			
		||||
                    android:offset="1.0" />
 | 
			
		||||
            </gradient>
 | 
			
		||||
        </aapt:attr>
 | 
			
		||||
    </path>
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#FFFFFF"
 | 
			
		||||
        android:fillType="nonZero"
 | 
			
		||||
        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
 | 
			
		||||
        android:strokeWidth="1"
 | 
			
		||||
        android:strokeColor="#00000000" />
 | 
			
		||||
</vector>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,170 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="108dp"
 | 
			
		||||
    android:height="108dp"
 | 
			
		||||
    android:viewportWidth="108"
 | 
			
		||||
    android:viewportHeight="108">
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#3DDC84"
 | 
			
		||||
        android:pathData="M0,0h108v108h-108z" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M9,0L9,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,0L19,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M29,0L29,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M39,0L39,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M49,0L49,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M59,0L59,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M69,0L69,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M79,0L79,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M89,0L89,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M99,0L99,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,9L108,9"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,19L108,19"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,29L108,29"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,39L108,39"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,49L108,49"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,59L108,59"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,69L108,69"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,79L108,79"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,89L108,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,99L108,99"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,29L89,29"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,39L89,39"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,49L89,49"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,59L89,59"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,69L89,69"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,79L89,79"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M29,19L29,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M39,19L39,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M49,19L49,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M59,19L59,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M69,19L69,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M79,19L79,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
</vector>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    tools:context=".MainActivity">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:text="Hello World!"
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent" />
 | 
			
		||||
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <background android:drawable="@drawable/ic_launcher_background" />
 | 
			
		||||
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
 | 
			
		||||
</adaptive-icon>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <background android:drawable="@drawable/ic_launcher_background" />
 | 
			
		||||
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
 | 
			
		||||
</adaptive-icon>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
| 
		 After Width: | Height: | Size: 2.8 KiB  | 
| 
		 After Width: | Height: | Size: 982 B  | 
| 
		 After Width: | Height: | Size: 1.7 KiB  | 
| 
		 After Width: | Height: | Size: 1.9 KiB  | 
| 
		 After Width: | Height: | Size: 3.8 KiB  | 
| 
		 After Width: | Height: | Size: 2.8 KiB  | 
| 
		 After Width: | Height: | Size: 5.8 KiB  | 
| 
		 After Width: | Height: | Size: 3.8 KiB  | 
| 
		 After Width: | Height: | Size: 7.6 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
<resources xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
    <!-- Base application theme. -->
 | 
			
		||||
    <style name="Theme.GuruGdprExample" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
 | 
			
		||||
        <!-- Primary brand color. -->
 | 
			
		||||
        <item name="colorPrimary">@color/purple_200</item>
 | 
			
		||||
        <item name="colorPrimaryVariant">@color/purple_700</item>
 | 
			
		||||
        <item name="colorOnPrimary">@color/black</item>
 | 
			
		||||
        <!-- Secondary brand color. -->
 | 
			
		||||
        <item name="colorSecondary">@color/teal_200</item>
 | 
			
		||||
        <item name="colorSecondaryVariant">@color/teal_200</item>
 | 
			
		||||
        <item name="colorOnSecondary">@color/black</item>
 | 
			
		||||
        <!-- Status bar color. -->
 | 
			
		||||
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
 | 
			
		||||
        <!-- Customize your theme here. -->
 | 
			
		||||
    </style>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <color name="purple_200">#FFBB86FC</color>
 | 
			
		||||
    <color name="purple_500">#FF6200EE</color>
 | 
			
		||||
    <color name="purple_700">#FF3700B3</color>
 | 
			
		||||
    <color name="teal_200">#FF03DAC5</color>
 | 
			
		||||
    <color name="teal_700">#FF018786</color>
 | 
			
		||||
    <color name="black">#FF000000</color>
 | 
			
		||||
    <color name="white">#FFFFFFFF</color>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
<resources>
 | 
			
		||||
    <string name="app_name">GuruGdprExample</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
<resources xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
    <!-- Base application theme. -->
 | 
			
		||||
    <style name="Theme.GuruGdprExample" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
 | 
			
		||||
        <!-- Primary brand color. -->
 | 
			
		||||
        <item name="colorPrimary">@color/purple_500</item>
 | 
			
		||||
        <item name="colorPrimaryVariant">@color/purple_700</item>
 | 
			
		||||
        <item name="colorOnPrimary">@color/white</item>
 | 
			
		||||
        <!-- Secondary brand color. -->
 | 
			
		||||
        <item name="colorSecondary">@color/teal_200</item>
 | 
			
		||||
        <item name="colorSecondaryVariant">@color/teal_700</item>
 | 
			
		||||
        <item name="colorOnSecondary">@color/black</item>
 | 
			
		||||
        <!-- Status bar color. -->
 | 
			
		||||
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
 | 
			
		||||
        <!-- Customize your theme here. -->
 | 
			
		||||
    </style>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?><!--
 | 
			
		||||
   Sample backup rules file; uncomment and customize as necessary.
 | 
			
		||||
   See https://developer.android.com/guide/topics/data/autobackup
 | 
			
		||||
   for details.
 | 
			
		||||
   Note: This file is ignored for devices older that API 31
 | 
			
		||||
   See https://developer.android.com/about/versions/12/backup-restore
 | 
			
		||||
-->
 | 
			
		||||
<full-backup-content>
 | 
			
		||||
    <!--
 | 
			
		||||
   <include domain="sharedpref" path="."/>
 | 
			
		||||
   <exclude domain="sharedpref" path="device.xml"/>
 | 
			
		||||
-->
 | 
			
		||||
</full-backup-content>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?><!--
 | 
			
		||||
   Sample data extraction rules file; uncomment and customize as necessary.
 | 
			
		||||
   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
 | 
			
		||||
   for details.
 | 
			
		||||
-->
 | 
			
		||||
<data-extraction-rules>
 | 
			
		||||
    <cloud-backup>
 | 
			
		||||
        <!-- TODO: Use <include> and <exclude> to control what is backed up.
 | 
			
		||||
        <include .../>
 | 
			
		||||
        <exclude .../>
 | 
			
		||||
        -->
 | 
			
		||||
    </cloud-backup>
 | 
			
		||||
    <!--
 | 
			
		||||
    <device-transfer>
 | 
			
		||||
        <include .../>
 | 
			
		||||
        <exclude .../>
 | 
			
		||||
    </device-transfer>
 | 
			
		||||
    -->
 | 
			
		||||
</data-extraction-rules>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
package com.example.gurugdprexample
 | 
			
		||||
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
 | 
			
		||||
import org.junit.Assert.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Example local unit test, which will execute on the development machine (host).
 | 
			
		||||
 *
 | 
			
		||||
 * See [testing documentation](http://d.android.com/tools/testing).
 | 
			
		||||
 */
 | 
			
		||||
class ExampleUnitTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun addition_isCorrect() {
 | 
			
		||||
        assertEquals(4, 2 + 2)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'com.android.application' version '7.2.1' apply false
 | 
			
		||||
    id 'com.android.library' version '7.2.1' apply false
 | 
			
		||||
    id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
task clean(type: Delete) {
 | 
			
		||||
    delete rootProject.buildDir
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
# Project-wide Gradle settings.
 | 
			
		||||
# IDE (e.g. Android Studio) users:
 | 
			
		||||
# Gradle settings configured through the IDE *will override*
 | 
			
		||||
# any settings specified in this file.
 | 
			
		||||
# For more details on how to configure your build environment visit
 | 
			
		||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
 | 
			
		||||
# Specifies the JVM arguments used for the daemon process.
 | 
			
		||||
# The setting is particularly useful for tweaking memory settings.
 | 
			
		||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
 | 
			
		||||
# When configured, Gradle will run in incubating parallel mode.
 | 
			
		||||
# This option should only be used with decoupled projects. More details, visit
 | 
			
		||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
 | 
			
		||||
# org.gradle.parallel=true
 | 
			
		||||
# AndroidX package structure to make it clearer which packages are bundled with the
 | 
			
		||||
# Android operating system, and which are packaged with your app"s APK
 | 
			
		||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
 | 
			
		||||
android.useAndroidX=true
 | 
			
		||||
# Kotlin code style for this project: "official" or "obsolete":
 | 
			
		||||
kotlin.code.style=official
 | 
			
		||||
# Enables namespacing of each library's R class so that its R class includes only the
 | 
			
		||||
# resources declared in the library itself and none from the library's dependencies,
 | 
			
		||||
# thereby reducing the size of the R class for that library
 | 
			
		||||
android.nonTransitiveRClass=true
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
#Fri Nov 11 12:05:16 CST 2022
 | 
			
		||||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,185 @@
 | 
			
		|||
#!/usr/bin/env sh
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2015 the original author or authors.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#      https://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
##
 | 
			
		||||
##  Gradle start up script for UN*X
 | 
			
		||||
##
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
# Attempt to set APP_HOME
 | 
			
		||||
# Resolve links: $0 may be a link
 | 
			
		||||
PRG="$0"
 | 
			
		||||
# Need this for relative symlinks.
 | 
			
		||||
while [ -h "$PRG" ] ; do
 | 
			
		||||
    ls=`ls -ld "$PRG"`
 | 
			
		||||
    link=`expr "$ls" : '.*-> \(.*\)$'`
 | 
			
		||||
    if expr "$link" : '/.*' > /dev/null; then
 | 
			
		||||
        PRG="$link"
 | 
			
		||||
    else
 | 
			
		||||
        PRG=`dirname "$PRG"`"/$link"
 | 
			
		||||
    fi
 | 
			
		||||
done
 | 
			
		||||
SAVED="`pwd`"
 | 
			
		||||
cd "`dirname \"$PRG\"`/" >/dev/null
 | 
			
		||||
APP_HOME="`pwd -P`"
 | 
			
		||||
cd "$SAVED" >/dev/null
 | 
			
		||||
 | 
			
		||||
APP_NAME="Gradle"
 | 
			
		||||
APP_BASE_NAME=`basename "$0"`
 | 
			
		||||
 | 
			
		||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 | 
			
		||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 | 
			
		||||
 | 
			
		||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
 | 
			
		||||
MAX_FD="maximum"
 | 
			
		||||
 | 
			
		||||
warn () {
 | 
			
		||||
    echo "$*"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
die () {
 | 
			
		||||
    echo
 | 
			
		||||
    echo "$*"
 | 
			
		||||
    echo
 | 
			
		||||
    exit 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# OS specific support (must be 'true' or 'false').
 | 
			
		||||
cygwin=false
 | 
			
		||||
msys=false
 | 
			
		||||
darwin=false
 | 
			
		||||
nonstop=false
 | 
			
		||||
case "`uname`" in
 | 
			
		||||
  CYGWIN* )
 | 
			
		||||
    cygwin=true
 | 
			
		||||
    ;;
 | 
			
		||||
  Darwin* )
 | 
			
		||||
    darwin=true
 | 
			
		||||
    ;;
 | 
			
		||||
  MINGW* )
 | 
			
		||||
    msys=true
 | 
			
		||||
    ;;
 | 
			
		||||
  NONSTOP* )
 | 
			
		||||
    nonstop=true
 | 
			
		||||
    ;;
 | 
			
		||||
esac
 | 
			
		||||
 | 
			
		||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Determine the Java command to use to start the JVM.
 | 
			
		||||
if [ -n "$JAVA_HOME" ] ; then
 | 
			
		||||
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
 | 
			
		||||
        # IBM's JDK on AIX uses strange locations for the executables
 | 
			
		||||
        JAVACMD="$JAVA_HOME/jre/sh/java"
 | 
			
		||||
    else
 | 
			
		||||
        JAVACMD="$JAVA_HOME/bin/java"
 | 
			
		||||
    fi
 | 
			
		||||
    if [ ! -x "$JAVACMD" ] ; then
 | 
			
		||||
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
 | 
			
		||||
 | 
			
		||||
Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
location of your Java installation."
 | 
			
		||||
    fi
 | 
			
		||||
else
 | 
			
		||||
    JAVACMD="java"
 | 
			
		||||
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
 | 
			
		||||
Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
location of your Java installation."
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Increase the maximum file descriptors if we can.
 | 
			
		||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
 | 
			
		||||
    MAX_FD_LIMIT=`ulimit -H -n`
 | 
			
		||||
    if [ $? -eq 0 ] ; then
 | 
			
		||||
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
 | 
			
		||||
            MAX_FD="$MAX_FD_LIMIT"
 | 
			
		||||
        fi
 | 
			
		||||
        ulimit -n $MAX_FD
 | 
			
		||||
        if [ $? -ne 0 ] ; then
 | 
			
		||||
            warn "Could not set maximum file descriptor limit: $MAX_FD"
 | 
			
		||||
        fi
 | 
			
		||||
    else
 | 
			
		||||
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# For Darwin, add options to specify how the application appears in the dock
 | 
			
		||||
if $darwin; then
 | 
			
		||||
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# For Cygwin or MSYS, switch paths to Windows format before running java
 | 
			
		||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
 | 
			
		||||
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
 | 
			
		||||
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
 | 
			
		||||
 | 
			
		||||
    JAVACMD=`cygpath --unix "$JAVACMD"`
 | 
			
		||||
 | 
			
		||||
    # We build the pattern for arguments to be converted via cygpath
 | 
			
		||||
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
 | 
			
		||||
    SEP=""
 | 
			
		||||
    for dir in $ROOTDIRSRAW ; do
 | 
			
		||||
        ROOTDIRS="$ROOTDIRS$SEP$dir"
 | 
			
		||||
        SEP="|"
 | 
			
		||||
    done
 | 
			
		||||
    OURCYGPATTERN="(^($ROOTDIRS))"
 | 
			
		||||
    # Add a user-defined pattern to the cygpath arguments
 | 
			
		||||
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
 | 
			
		||||
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
 | 
			
		||||
    fi
 | 
			
		||||
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
 | 
			
		||||
    i=0
 | 
			
		||||
    for arg in "$@" ; do
 | 
			
		||||
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
 | 
			
		||||
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
 | 
			
		||||
 | 
			
		||||
        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
 | 
			
		||||
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
 | 
			
		||||
        else
 | 
			
		||||
            eval `echo args$i`="\"$arg\""
 | 
			
		||||
        fi
 | 
			
		||||
        i=`expr $i + 1`
 | 
			
		||||
    done
 | 
			
		||||
    case $i in
 | 
			
		||||
        0) set -- ;;
 | 
			
		||||
        1) set -- "$args0" ;;
 | 
			
		||||
        2) set -- "$args0" "$args1" ;;
 | 
			
		||||
        3) set -- "$args0" "$args1" "$args2" ;;
 | 
			
		||||
        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
 | 
			
		||||
        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
 | 
			
		||||
        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
 | 
			
		||||
        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
 | 
			
		||||
        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
 | 
			
		||||
        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
 | 
			
		||||
    esac
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Escape application args
 | 
			
		||||
save () {
 | 
			
		||||
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
 | 
			
		||||
    echo " "
 | 
			
		||||
}
 | 
			
		||||
APP_ARGS=`save "$@"`
 | 
			
		||||
 | 
			
		||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
 | 
			
		||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
 | 
			
		||||
 | 
			
		||||
exec "$JAVACMD" "$@"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
@rem
 | 
			
		||||
@rem Copyright 2015 the original author or authors.
 | 
			
		||||
@rem
 | 
			
		||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
@rem you may not use this file except in compliance with the License.
 | 
			
		||||
@rem You may obtain a copy of the License at
 | 
			
		||||
@rem
 | 
			
		||||
@rem      https://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
@rem
 | 
			
		||||
@rem Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
@rem See the License for the specific language governing permissions and
 | 
			
		||||
@rem limitations under the License.
 | 
			
		||||
@rem
 | 
			
		||||
 | 
			
		||||
@if "%DEBUG%" == "" @echo off
 | 
			
		||||
@rem ##########################################################################
 | 
			
		||||
@rem
 | 
			
		||||
@rem  Gradle startup script for Windows
 | 
			
		||||
@rem
 | 
			
		||||
@rem ##########################################################################
 | 
			
		||||
 | 
			
		||||
@rem Set local scope for the variables with windows NT shell
 | 
			
		||||
if "%OS%"=="Windows_NT" setlocal
 | 
			
		||||
 | 
			
		||||
set DIRNAME=%~dp0
 | 
			
		||||
if "%DIRNAME%" == "" set DIRNAME=.
 | 
			
		||||
set APP_BASE_NAME=%~n0
 | 
			
		||||
set APP_HOME=%DIRNAME%
 | 
			
		||||
 | 
			
		||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
 | 
			
		||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
 | 
			
		||||
 | 
			
		||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 | 
			
		||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
 | 
			
		||||
 | 
			
		||||
@rem Find java.exe
 | 
			
		||||
if defined JAVA_HOME goto findJavaFromJavaHome
 | 
			
		||||
 | 
			
		||||
set JAVA_EXE=java.exe
 | 
			
		||||
%JAVA_EXE% -version >NUL 2>&1
 | 
			
		||||
if "%ERRORLEVEL%" == "0" goto execute
 | 
			
		||||
 | 
			
		||||
echo.
 | 
			
		||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
echo.
 | 
			
		||||
echo Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
echo location of your Java installation.
 | 
			
		||||
 | 
			
		||||
goto fail
 | 
			
		||||
 | 
			
		||||
:findJavaFromJavaHome
 | 
			
		||||
set JAVA_HOME=%JAVA_HOME:"=%
 | 
			
		||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 | 
			
		||||
 | 
			
		||||
if exist "%JAVA_EXE%" goto execute
 | 
			
		||||
 | 
			
		||||
echo.
 | 
			
		||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
 | 
			
		||||
echo.
 | 
			
		||||
echo Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
echo location of your Java installation.
 | 
			
		||||
 | 
			
		||||
goto fail
 | 
			
		||||
 | 
			
		||||
:execute
 | 
			
		||||
@rem Setup the command line
 | 
			
		||||
 | 
			
		||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@rem Execute Gradle
 | 
			
		||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
 | 
			
		||||
 | 
			
		||||
:end
 | 
			
		||||
@rem End local scope for the variables with windows NT shell
 | 
			
		||||
if "%ERRORLEVEL%"=="0" goto mainEnd
 | 
			
		||||
 | 
			
		||||
:fail
 | 
			
		||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
 | 
			
		||||
rem the _cmd.exe /c_ return code!
 | 
			
		||||
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
 | 
			
		||||
exit /b 1
 | 
			
		||||
 | 
			
		||||
:mainEnd
 | 
			
		||||
if "%OS%"=="Windows_NT" endlocal
 | 
			
		||||
 | 
			
		||||
:omega
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
pluginManagement {
 | 
			
		||||
    repositories {
 | 
			
		||||
        gradlePluginPortal()
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        mavenLocal()
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
dependencyResolutionManagement {
 | 
			
		||||
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
 | 
			
		||||
    repositories {
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        mavenLocal()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
rootProject.name = "GuruGdprExample"
 | 
			
		||||
include ':app'
 | 
			
		||||
include ':GuruConsent'
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
.gradle
 | 
			
		||||
/captures
 | 
			
		||||
/local.properties
 | 
			
		||||
/.idea/workspace.xml
 | 
			
		||||
.DS_Store
 | 
			
		||||
/build
 | 
			
		||||
.idea/
 | 
			
		||||
*iml
 | 
			
		||||
*.iml
 | 
			
		||||
*/build
 | 
			
		||||
/lib
 | 
			
		||||
wh.properties
 | 
			
		||||
/atom
 | 
			
		||||
*.txt
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,174 @@
 | 
			
		|||
## v1.0.3
 | 
			
		||||
 | 
			
		||||
##### BugFix
 | 
			
		||||
- bugfix snapshotAnalyticsAudit uploaded > total
 | 
			
		||||
 | 
			
		||||
## v1.0.2
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
- init step callback
 | 
			
		||||
- snapshotAnalyticsAudit()
 | 
			
		||||
 | 
			
		||||
##### BugFix
 | 
			
		||||
- bugfix http exception
 | 
			
		||||
 | 
			
		||||
## v1.0.1
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
- init add uploadIpAddress
 | 
			
		||||
- CronetHelper init try catch
 | 
			
		||||
 | 
			
		||||
## v1.0.0
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
- CustomDns add cache
 | 
			
		||||
- init add isEnableCronet
 | 
			
		||||
- UserProperty guru_anm cronet/default
 | 
			
		||||
- PendingEvent 初始化成功前添加的埋点时间戳校正
 | 
			
		||||
- initialize uploadEventBaseUrl fromat check
 | 
			
		||||
 | 
			
		||||
## v0.3.2
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
- add fun peakUserProperties
 | 
			
		||||
- add fun getUserProperties
 | 
			
		||||
- add fun setEnableUpload
 | 
			
		||||
 | 
			
		||||
## v0.3.1
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
- modify FG event code logic
 | 
			
		||||
 | 
			
		||||
## v0.3.0
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
- modify event handle and add dns error event
 | 
			
		||||
- init remove isCallbackEventHandler / add setEventHandlerCallback
 | 
			
		||||
- EventHandler add ERROR_ZIP(107) callback
 | 
			
		||||
- EventHandler ERROR_API(101)/ERROR_CACHE_CONTROL(103) add error message
 | 
			
		||||
- model Event/ParamValue add @Keep
 | 
			
		||||
 | 
			
		||||
## v0.2.11
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
-  init add setMainProcess(isNotBlack check main process)
 | 
			
		||||
-  add error_process logevent
 | 
			
		||||
 | 
			
		||||
## v0.2.10
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
-  fg event logic adjustment
 | 
			
		||||
-  isInitPeriodicWork default false
 | 
			
		||||
-  add remove user properties function
 | 
			
		||||
 | 
			
		||||
## v0.2.9
 | 
			
		||||
 | 
			
		||||
##### BugFix
 | 
			
		||||
- GuruAnalyticsImpl delivers replace with CopyOnWriteArrayList
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
-  add app start first fg event
 | 
			
		||||
 | 
			
		||||
## v0.2.8
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
 | 
			
		||||
- 删除androidx.appcompat:appcompat库
 | 
			
		||||
 | 
			
		||||
## v0.2.7
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
 | 
			
		||||
- 修复fg埋点偶现未能清空本地累计时间的问题
 | 
			
		||||
 | 
			
		||||
## v0.2.6
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
 | 
			
		||||
- Header中增加X-APP-ID / X-DEVICE-INFO, 初始化时增加对应设置方法
 | 
			
		||||
  event结构体中新增eventId, 采用uuid4算法(小写)
 | 
			
		||||
 | 
			
		||||
## v0.2.5
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
 | 
			
		||||
- 前台时长打点(fg)获取方法调整, 修改为每间隔30秒(时长可调)生成一个fg打点
 | 
			
		||||
  初始化增加setFgEventPeriodInSeconds()方法,设置fg埋点间隔上报时间,默认30秒
 | 
			
		||||
 | 
			
		||||
## v0.2.4
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
 | 
			
		||||
- 支持配置上报打点api的baseUrl.
 | 
			
		||||
  通过builder初始化时增加方法setUploadEventBaseUrl(), 初始化完成后也可以通过调用setUploadEventBaseUrl()实现功能
 | 
			
		||||
 | 
			
		||||
## v0.2.3
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
 | 
			
		||||
- 优化统计时长机制,使用System.currentTimeMillis()替换成SystemClock.elapsedRealtime()
 | 
			
		||||
- 添加各个节点hook机制
 | 
			
		||||
- 针对unity项目添加worker规避机制
 | 
			
		||||
 | 
			
		||||
## v0.2.2
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
 | 
			
		||||
- 优化上传机制,添加打开后强制上传机制
 | 
			
		||||
 | 
			
		||||
##### BugFix
 | 
			
		||||
 | 
			
		||||
- 修复SCREEN_H上报错误问题
 | 
			
		||||
## v0.2.1
 | 
			
		||||
 | 
			
		||||
- 修复Timber.DebugTree的问题
 | 
			
		||||
 | 
			
		||||
## v0.2.0
 | 
			
		||||
 | 
			
		||||
##### Feature
 | 
			
		||||
 | 
			
		||||
- 在初始化延时期间,所有产生的事件都将等待初始化完成后才会真正分发
 | 
			
		||||
- 添加本地日志收集机制,方便追溯问题
 | 
			
		||||
 | 
			
		||||
##### BugFix
 | 
			
		||||
 | 
			
		||||
- 修复后台上报时缺失设备信息的问题
 | 
			
		||||
## v0.1.1
 | 
			
		||||
 | 
			
		||||
##### BugFix
 | 
			
		||||
 | 
			
		||||
- 修复Worker后台上报时崩溃问题
 | 
			
		||||
 | 
			
		||||
## v0.1.0
 | 
			
		||||
 | 
			
		||||
- 间隔`uploadPeriod`秒后打包上传`batchLimit`个数据
 | 
			
		||||
 | 
			
		||||
- 添加Worker处理未发送事件(`6小时`)
 | 
			
		||||
 | 
			
		||||
- 支持延时启动上传的逻辑,初始化后延时`startUploadDelay`秒后启动上传逻辑
 | 
			
		||||
 | 
			
		||||
- 支持事件优先级
 | 
			
		||||
 | 
			
		||||
    | ***NAME*** | ***PRIORITY*** |
 | 
			
		||||
    | :--------  | :--------: |
 | 
			
		||||
    | `EMERGENCE` | 0 |
 | 
			
		||||
    | `HIGH` | 5 |
 | 
			
		||||
    | `DEFAULT` | 10 |
 | 
			
		||||
    | `LOW` | 15 |
 | 
			
		||||
 | 
			
		||||
- 首次初始化时自动上报`FirstOpen`点,并将该点以`EMERGENCE`优先级发送
 | 
			
		||||
 | 
			
		||||
- 支持App生命周期自打点`fg`,并根据前后台时间计算`duration`
 | 
			
		||||
 | 
			
		||||
- 提供下列接口
 | 
			
		||||
 | 
			
		||||
    | ***Method*** | ***Description*** |
 | 
			
		||||
    | :--------  | :--------: |
 | 
			
		||||
    | `logEvent` | 所有event点都是通过该函数完成 |
 | 
			
		||||
    | `setUserProperty` | 设置用户属性 |
 | 
			
		||||
    | `setScreen` | 设置当前屏幕 |
 | 
			
		||||
    | `setDeviceId` | 设置设备ID |
 | 
			
		||||
    | `setUid` | 设置用户ID |
 | 
			
		||||
    | `setAdjustId` | 设置AdjustId |
 | 
			
		||||
    | `setAdId` | 设置Google Ad Id |
 | 
			
		||||
    | `setFirebaseId` | 设置Firebase Id |
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
/build
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    id 'com.android.application'
 | 
			
		||||
    id 'org.jetbrains.kotlin.android'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileSdk 34
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        applicationId "com.example.guruanalytics"
 | 
			
		||||
        minSdk 23
 | 
			
		||||
        targetSdk 33
 | 
			
		||||
        versionCode 1
 | 
			
		||||
        versionName "1.1.0"
 | 
			
		||||
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            minifyEnabled false
 | 
			
		||||
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = '1.8'
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
 | 
			
		||||
    implementation 'androidx.core:core-ktx:1.5.0'
 | 
			
		||||
    implementation 'androidx.appcompat:appcompat:1.4.2'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.4.0'
 | 
			
		||||
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
 | 
			
		||||
    testImplementation 'junit:junit:4.13.2'
 | 
			
		||||
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
 | 
			
		||||
    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 'guru.core.analytics:guru_analytics:0.1.0'
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
# Add project specific ProGuard rules here.
 | 
			
		||||
# You can control the set of applied configuration files using the
 | 
			
		||||
# proguardFiles setting in build.gradle.
 | 
			
		||||
#
 | 
			
		||||
# For more details, see
 | 
			
		||||
#   http://developer.android.com/guide/developing/tools/proguard.html
 | 
			
		||||
 | 
			
		||||
# If your project uses WebView with JS, uncomment the following
 | 
			
		||||
# and specify the fully qualified class name to the JavaScript interface
 | 
			
		||||
# class:
 | 
			
		||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
 | 
			
		||||
#   public *;
 | 
			
		||||
#}
 | 
			
		||||
 | 
			
		||||
# Uncomment this to preserve the line number information for
 | 
			
		||||
# debugging stack traces.
 | 
			
		||||
#-keepattributes SourceFile,LineNumberTable
 | 
			
		||||
 | 
			
		||||
# If you keep the line number information, uncomment this to
 | 
			
		||||
# hide the original source file name.
 | 
			
		||||
#-renamesourcefileattribute SourceFile
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
package com.example.guruanalytics
 | 
			
		||||
 | 
			
		||||
import androidx.test.platform.app.InstrumentationRegistry
 | 
			
		||||
import androidx.test.ext.junit.runners.AndroidJUnit4
 | 
			
		||||
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
import org.junit.runner.RunWith
 | 
			
		||||
 | 
			
		||||
import org.junit.Assert.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Instrumented test, which will execute on an Android device.
 | 
			
		||||
 *
 | 
			
		||||
 * See [testing documentation](http://d.android.com/tools/testing).
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(AndroidJUnit4::class)
 | 
			
		||||
class ExampleInstrumentedTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun useAppContext() {
 | 
			
		||||
        // Context of the app under test.
 | 
			
		||||
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
 | 
			
		||||
        assertEquals("com.example.guruanalytics", appContext.packageName)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    package="com.example.guruanalytics">
 | 
			
		||||
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET" />
 | 
			
		||||
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:allowBackup="true"
 | 
			
		||||
        android:dataExtractionRules="@xml/data_extraction_rules"
 | 
			
		||||
        android:fullBackupContent="@xml/backup_rules"
 | 
			
		||||
        android:icon="@mipmap/ic_launcher"
 | 
			
		||||
        android:label="@string/app_name"
 | 
			
		||||
        android:roundIcon="@mipmap/ic_launcher_round"
 | 
			
		||||
        android:supportsRtl="true"
 | 
			
		||||
        android:theme="@style/Theme.GuruAnalytics"
 | 
			
		||||
        tools:targetApi="31">
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".MainActivity"
 | 
			
		||||
            android:exported="true">
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.MAIN" />
 | 
			
		||||
 | 
			
		||||
                <category android:name="android.intent.category.LAUNCHER" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".TestProcessActivity"
 | 
			
		||||
            android:process=":unity" />
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,310 @@
 | 
			
		|||
package com.example.guruanalytics
 | 
			
		||||
 | 
			
		||||
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.RemoteException
 | 
			
		||||
import android.util.Log
 | 
			
		||||
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.data.api.dns.DnsMode
 | 
			
		||||
import guru.core.analytics.data.db.model.EventPriority
 | 
			
		||||
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() {
 | 
			
		||||
 | 
			
		||||
    private var enableUpload = true
 | 
			
		||||
 | 
			
		||||
    @RequiresApi(Build.VERSION_CODES.N)
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        setContentView(R.layout.activity_main)
 | 
			
		||||
 | 
			
		||||
//        GuruAnalytics.INSTANCE.initialize(
 | 
			
		||||
//            this,
 | 
			
		||||
//            batchLimit = 25,
 | 
			
		||||
//            startUploadDelayInSecond = 3L,
 | 
			
		||||
//            debug = 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)
 | 
			
		||||
                .setBatchLimit(25)
 | 
			
		||||
                .setUploadPeriodInSeconds(60)
 | 
			
		||||
                .setStartUploadDelayInSecond(3)
 | 
			
		||||
                .setEventExpiredInDays(7)
 | 
			
		||||
                .isPersistableLog(true)
 | 
			
		||||
                .setEventHandlerCallback(eventHandler)
 | 
			
		||||
                .isInitPeriodicWork(true)
 | 
			
		||||
                .isDebug(BuildConfig.DEBUG)
 | 
			
		||||
//            .setUploadEventBaseUrl("https://www.baidu.com")
 | 
			
		||||
//            .setFgEventPeriodInSeconds(60L)
 | 
			
		||||
                .setXAppId("test_x_app_id")
 | 
			
		||||
                .setXDeviceInfo("test_x_device_info")
 | 
			
		||||
                .setMainProcess("com.example.guruanalytics")
 | 
			
		||||
//            .isEnableCronet(true)
 | 
			
		||||
                .setUploadEventBaseUrl("https://collect4.fungame.cloud")
 | 
			
		||||
                .setDnsMode(DnsMode.COMPOSITE)
 | 
			
		||||
                .build()
 | 
			
		||||
 | 
			
		||||
            Log.w("GuruAnalytics", "GuruAnalytics.INSTANCE: completed")
 | 
			
		||||
        }
 | 
			
		||||
        findViewById<TextView>(R.id.tvLocalLog).setOnClickListener {
 | 
			
		||||
            val startTime = System.currentTimeMillis()
 | 
			
		||||
//            GuruAnalytics.INSTANCE.zipLogs(this)
 | 
			
		||||
//            Log.i("get_local_log", "${System.currentTimeMillis() - startTime}")
 | 
			
		||||
//            val request = OneTimeWorkRequestBuilder<AnalyticsWorker>(
 | 
			
		||||
//            ).build()
 | 
			
		||||
////                .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(
 | 
			
		||||
                "Worker",
 | 
			
		||||
                "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 {
 | 
			
		||||
            val map = mutableMapOf<String, Any>()
 | 
			
		||||
            map["percent"] = 0.4
 | 
			
		||||
            map["level"] = 2
 | 
			
		||||
            map["from"] = "game"
 | 
			
		||||
            GuruAnalytics.INSTANCE.logEvent(
 | 
			
		||||
                "test_event_emergence", "game", "main", 10, map, options = AnalyticsOptions(
 | 
			
		||||
                    EventPriority.EMERGENCE
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        findViewById<TextView>(R.id.tvBaseUrl).setOnClickListener {
 | 
			
		||||
//            GuruAnalytics.INSTANCE.setUploadEventBaseUrl(this, "https://www.castbox.fm/")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val tvEnable = findViewById<TextView>(R.id.tvEnable)
 | 
			
		||||
        tvEnable?.setOnClickListener {
 | 
			
		||||
            val enable = !enableUpload
 | 
			
		||||
            GuruAnalytics.INSTANCE.setEnableUpload(enable)
 | 
			
		||||
            enableUpload = enable
 | 
			
		||||
            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.setAdId("AD_ID_01")
 | 
			
		||||
        GuruAnalytics.INSTANCE.setFirebaseId("FIREBASE_ID")
 | 
			
		||||
        GuruAnalytics.INSTANCE.setUid("MBK-YYYYY")
 | 
			
		||||
        GuruAnalytics.INSTANCE.setUserProperty("uid", "110051")
 | 
			
		||||
        GuruAnalytics.INSTANCE.setUserProperty("age", "12")
 | 
			
		||||
        GuruAnalytics.INSTANCE.setUserProperty("sex", "male")
 | 
			
		||||
        Log.d("Test", "setAdId")
 | 
			
		||||
 | 
			
		||||
        val properties = GuruAnalytics.INSTANCE.peakUserProperties()
 | 
			
		||||
        Log.i("peakUserProperties", "properties:$properties")
 | 
			
		||||
 | 
			
		||||
        GuruAnalytics.INSTANCE.getUserProperties {
 | 
			
		||||
            Log.i("getUserProperties", "properties:$it")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val eventHandler: (Int, String?) -> Unit = { code, ext ->
 | 
			
		||||
        Log.i("eventHandler", "$code${if (ext.isNullOrBlank()) "" else "    $ext"}")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
        GuruAnalytics.INSTANCE.removeEventHandler(eventHandler)
 | 
			
		||||
        super.onDestroy()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
package com.example.guruanalytics
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import guru.core.analytics.GuruAnalytics
 | 
			
		||||
 | 
			
		||||
class TestProcessActivity : AppCompatActivity() {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun startActivity(context: Context) {
 | 
			
		||||
            val intent = Intent(context, TestProcessActivity::class.java)
 | 
			
		||||
            context.startActivity(intent)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        setContentView(R.layout.activity_test_process)
 | 
			
		||||
 | 
			
		||||
        GuruAnalytics.Builder(this)
 | 
			
		||||
            .setBatchLimit(25)
 | 
			
		||||
            .setUploadPeriodInSeconds(60)
 | 
			
		||||
            .setStartUploadDelayInSecond(3)
 | 
			
		||||
            .setEventExpiredInDays(7)
 | 
			
		||||
            .isPersistableLog(true)
 | 
			
		||||
            .isInitPeriodicWork(true)
 | 
			
		||||
            .isDebug(BuildConfig.DEBUG)
 | 
			
		||||
            .setXAppId("test_x_app_id")
 | 
			
		||||
            .setXDeviceInfo("test_x_device_info")
 | 
			
		||||
            .setMainProcess("com.example.guruanalytics")
 | 
			
		||||
 | 
			
		||||
            .setUploadIpAddress(listOf("13.248.248.135", "3.33.195.44"))
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        findViewById<TextView>(R.id.tvFinish).setOnClickListener {
 | 
			
		||||
            finish()
 | 
			
		||||
        }
 | 
			
		||||
        findViewById<TextView>(R.id.tvLogEventProcess).setOnClickListener {
 | 
			
		||||
            val map = mutableMapOf<String, Any>()
 | 
			
		||||
            map["percent"] = 0.4
 | 
			
		||||
            map["level"] = 2
 | 
			
		||||
            map["from"] = "game"
 | 
			
		||||
            GuruAnalytics.INSTANCE.logEvent("test_event", "game", "main", 10, map)
 | 
			
		||||
            Log.e("initialize","tvLogEventProcess")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:aapt="http://schemas.android.com/aapt"
 | 
			
		||||
    android:width="108dp"
 | 
			
		||||
    android:height="108dp"
 | 
			
		||||
    android:viewportWidth="108"
 | 
			
		||||
    android:viewportHeight="108">
 | 
			
		||||
    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
 | 
			
		||||
        <aapt:attr name="android:fillColor">
 | 
			
		||||
            <gradient
 | 
			
		||||
                android:endX="85.84757"
 | 
			
		||||
                android:endY="92.4963"
 | 
			
		||||
                android:startX="42.9492"
 | 
			
		||||
                android:startY="49.59793"
 | 
			
		||||
                android:type="linear">
 | 
			
		||||
                <item
 | 
			
		||||
                    android:color="#44000000"
 | 
			
		||||
                    android:offset="0.0" />
 | 
			
		||||
                <item
 | 
			
		||||
                    android:color="#00000000"
 | 
			
		||||
                    android:offset="1.0" />
 | 
			
		||||
            </gradient>
 | 
			
		||||
        </aapt:attr>
 | 
			
		||||
    </path>
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#FFFFFF"
 | 
			
		||||
        android:fillType="nonZero"
 | 
			
		||||
        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
 | 
			
		||||
        android:strokeWidth="1"
 | 
			
		||||
        android:strokeColor="#00000000" />
 | 
			
		||||
</vector>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,170 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="108dp"
 | 
			
		||||
    android:height="108dp"
 | 
			
		||||
    android:viewportWidth="108"
 | 
			
		||||
    android:viewportHeight="108">
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#3DDC84"
 | 
			
		||||
        android:pathData="M0,0h108v108h-108z" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M9,0L9,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,0L19,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M29,0L29,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M39,0L39,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M49,0L49,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M59,0L59,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M69,0L69,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M79,0L79,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M89,0L89,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M99,0L99,108"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,9L108,9"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,19L108,19"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,29L108,29"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,39L108,39"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,49L108,49"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,59L108,59"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,69L108,69"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,79L108,79"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,89L108,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,99L108,99"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,29L89,29"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,39L89,39"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,49L89,49"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,59L89,59"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,69L89,69"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,79L89,79"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M29,19L29,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M39,19L39,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M49,19L49,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M59,19L59,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M69,19L69,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M79,19L79,89"
 | 
			
		||||
        android:strokeWidth="0.8"
 | 
			
		||||
        android:strokeColor="#33FFFFFF" />
 | 
			
		||||
</vector>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    android:orientation="vertical"
 | 
			
		||||
    tools:context=".MainActivity">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvSetProperty"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="50dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="SET PROPERTY" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvLogEvent"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="30dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="LOG EVENT" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvEventEmergence"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="30dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="Event_EMERGENCE" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvOpenTestProcessActivity"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="30dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="Open TestProcessActivity" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvLocalLog"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="30dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="get local log file" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvEventStatistic"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="30dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="Event Statistic" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvBaseUrl"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="30dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="Update BaseUrl" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvEnable"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="30dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="Enable Upload" />
 | 
			
		||||
 | 
			
		||||
    <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>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    tools:context=".MainActivity">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvTop"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:layout_marginTop="100dp"
 | 
			
		||||
        android:text="Unity Process Activity"
 | 
			
		||||
        app:layout_constraintBottom_toTopOf="@+id/tvFinish"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        app:layout_constraintVertical_chainStyle="packed" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvFinish"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginTop="20dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="FINISH"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toBottomOf="@+id/tvTop" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvLogEventProcess"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginTop="20dp"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:text="LOG"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toBottomOf="@+id/tvFinish" />
 | 
			
		||||
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <background android:drawable="@drawable/ic_launcher_background" />
 | 
			
		||||
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
 | 
			
		||||
</adaptive-icon>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <background android:drawable="@drawable/ic_launcher_background" />
 | 
			
		||||
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
 | 
			
		||||
</adaptive-icon>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
| 
		 After Width: | Height: | Size: 2.8 KiB  | 
| 
		 After Width: | Height: | Size: 982 B  | 
| 
		 After Width: | Height: | Size: 1.7 KiB  | 
| 
		 After Width: | Height: | Size: 1.9 KiB  | 
| 
		 After Width: | Height: | Size: 3.8 KiB  | 
| 
		 After Width: | Height: | Size: 2.8 KiB  | 
| 
		 After Width: | Height: | Size: 5.8 KiB  | 
| 
		 After Width: | Height: | Size: 3.8 KiB  | 
| 
		 After Width: | Height: | Size: 7.6 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
<resources xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
    <!-- Base application theme. -->
 | 
			
		||||
    <style name="Theme.GuruAnalytics" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
 | 
			
		||||
        <!-- Primary brand color. -->
 | 
			
		||||
        <item name="colorPrimary">@color/purple_200</item>
 | 
			
		||||
        <item name="colorPrimaryVariant">@color/purple_700</item>
 | 
			
		||||
        <item name="colorOnPrimary">@color/black</item>
 | 
			
		||||
        <!-- Secondary brand color. -->
 | 
			
		||||
        <item name="colorSecondary">@color/teal_200</item>
 | 
			
		||||
        <item name="colorSecondaryVariant">@color/teal_200</item>
 | 
			
		||||
        <item name="colorOnSecondary">@color/black</item>
 | 
			
		||||
        <!-- Status bar color. -->
 | 
			
		||||
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
 | 
			
		||||
        <!-- Customize your theme here. -->
 | 
			
		||||
    </style>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <color name="purple_200">#FFBB86FC</color>
 | 
			
		||||
    <color name="purple_500">#FF6200EE</color>
 | 
			
		||||
    <color name="purple_700">#FF3700B3</color>
 | 
			
		||||
    <color name="teal_200">#FF03DAC5</color>
 | 
			
		||||
    <color name="teal_700">#FF018786</color>
 | 
			
		||||
    <color name="black">#FF000000</color>
 | 
			
		||||
    <color name="white">#FFFFFFFF</color>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
<resources>
 | 
			
		||||
    <string name="app_name">GuruAnalytics</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
<resources xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
    <!-- Base application theme. -->
 | 
			
		||||
    <style name="Theme.GuruAnalytics" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
 | 
			
		||||
        <!-- Primary brand color. -->
 | 
			
		||||
        <item name="colorPrimary">@color/purple_500</item>
 | 
			
		||||
        <item name="colorPrimaryVariant">@color/purple_700</item>
 | 
			
		||||
        <item name="colorOnPrimary">@color/white</item>
 | 
			
		||||
        <!-- Secondary brand color. -->
 | 
			
		||||
        <item name="colorSecondary">@color/teal_200</item>
 | 
			
		||||
        <item name="colorSecondaryVariant">@color/teal_700</item>
 | 
			
		||||
        <item name="colorOnSecondary">@color/black</item>
 | 
			
		||||
        <!-- Status bar color. -->
 | 
			
		||||
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
 | 
			
		||||
        <!-- Customize your theme here. -->
 | 
			
		||||
    </style>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?><!--
 | 
			
		||||
   Sample backup rules file; uncomment and customize as necessary.
 | 
			
		||||
   See https://developer.android.com/guide/topics/data/autobackup
 | 
			
		||||
   for details.
 | 
			
		||||
   Note: This file is ignored for devices older that API 31
 | 
			
		||||
   See https://developer.android.com/about/versions/12/backup-restore
 | 
			
		||||
-->
 | 
			
		||||
<full-backup-content>
 | 
			
		||||
    <!--
 | 
			
		||||
   <include domain="sharedpref" path="."/>
 | 
			
		||||
   <exclude domain="sharedpref" path="device.xml"/>
 | 
			
		||||
-->
 | 
			
		||||
</full-backup-content>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?><!--
 | 
			
		||||
   Sample data extraction rules file; uncomment and customize as necessary.
 | 
			
		||||
   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
 | 
			
		||||
   for details.
 | 
			
		||||
-->
 | 
			
		||||
<data-extraction-rules>
 | 
			
		||||
    <cloud-backup>
 | 
			
		||||
        <!-- TODO: Use <include> and <exclude> to control what is backed up.
 | 
			
		||||
        <include .../>
 | 
			
		||||
        <exclude .../>
 | 
			
		||||
        -->
 | 
			
		||||
    </cloud-backup>
 | 
			
		||||
    <!--
 | 
			
		||||
    <device-transfer>
 | 
			
		||||
        <include .../>
 | 
			
		||||
        <exclude .../>
 | 
			
		||||
    </device-transfer>
 | 
			
		||||
    -->
 | 
			
		||||
</data-extraction-rules>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
package com.example.guruanalytics
 | 
			
		||||
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
 | 
			
		||||
import org.junit.Assert.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Example local unit test, which will execute on the development machine (host).
 | 
			
		||||
 *
 | 
			
		||||
 * See [testing documentation](http://d.android.com/tools/testing).
 | 
			
		||||
 */
 | 
			
		||||
class ExampleUnitTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun addition_isCorrect() {
 | 
			
		||||
        assertEquals(4, 2 + 2)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
buildscript {
 | 
			
		||||
    ext.kotlin_version = '1.9.0'
 | 
			
		||||
 | 
			
		||||
    repositories {
 | 
			
		||||
//        maven { url 'http://localhost:8081/repository/maven-public/' }
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        mavenLocal()
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dependencies {
 | 
			
		||||
        classpath "com.android.tools.build:gradle:7.1.2"
 | 
			
		||||
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
allprojects {
 | 
			
		||||
    repositories {
 | 
			
		||||
//        maven { url 'http://localhost:8081/repository/maven-public/' }
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        google()
 | 
			
		||||
        mavenLocal()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
# Project-wide Gradle settings.
 | 
			
		||||
# IDE (e.g. Android Studio) users:
 | 
			
		||||
# Gradle settings configured through the IDE *will override*
 | 
			
		||||
# any settings specified in this file.
 | 
			
		||||
# For more details on how to configure your build environment visit
 | 
			
		||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
 | 
			
		||||
# Specifies the JVM arguments used for the daemon process.
 | 
			
		||||
# The setting is particularly useful for tweaking memory settings.
 | 
			
		||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
 | 
			
		||||
# When configured, Gradle will run in incubating parallel mode.
 | 
			
		||||
# This option should only be used with decoupled projects. More details, visit
 | 
			
		||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
 | 
			
		||||
# org.gradle.parallel=true
 | 
			
		||||
# AndroidX package structure to make it clearer which packages are bundled with the
 | 
			
		||||
# Android operating system, and which are packaged with your app"s APK
 | 
			
		||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
 | 
			
		||||
android.useAndroidX=true
 | 
			
		||||
# Kotlin code style for this project: "official" or "obsolete":
 | 
			
		||||
kotlin.code.style=official
 | 
			
		||||
# Enables namespacing of each library's R class so that its R class includes only the
 | 
			
		||||
# resources declared in the library itself and none from the library's dependencies,
 | 
			
		||||
# thereby reducing the size of the R class for that library
 | 
			
		||||
android.nonTransitiveRClass=true
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,234 @@
 | 
			
		|||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Copyright © 2015-2021 the original authors.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#      https://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
#
 | 
			
		||||
#   Gradle start up script for POSIX generated by Gradle.
 | 
			
		||||
#
 | 
			
		||||
#   Important for running:
 | 
			
		||||
#
 | 
			
		||||
#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
 | 
			
		||||
#       noncompliant, but you have some other compliant shell such as ksh or
 | 
			
		||||
#       bash, then to run this script, type that shell name before the whole
 | 
			
		||||
#       command line, like:
 | 
			
		||||
#
 | 
			
		||||
#           ksh Gradle
 | 
			
		||||
#
 | 
			
		||||
#       Busybox and similar reduced shells will NOT work, because this script
 | 
			
		||||
#       requires all of these POSIX shell features:
 | 
			
		||||
#         * functions;
 | 
			
		||||
#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
 | 
			
		||||
#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
 | 
			
		||||
#         * compound commands having a testable exit status, especially «case»;
 | 
			
		||||
#         * various built-in commands including «command», «set», and «ulimit».
 | 
			
		||||
#
 | 
			
		||||
#   Important for patching:
 | 
			
		||||
#
 | 
			
		||||
#   (2) This script targets any POSIX shell, so it avoids extensions provided
 | 
			
		||||
#       by Bash, Ksh, etc; in particular arrays are avoided.
 | 
			
		||||
#
 | 
			
		||||
#       The "traditional" practice of packing multiple parameters into a
 | 
			
		||||
#       space-separated string is a well documented source of bugs and security
 | 
			
		||||
#       problems, so this is (mostly) avoided, by progressively accumulating
 | 
			
		||||
#       options in "$@", and eventually passing that to Java.
 | 
			
		||||
#
 | 
			
		||||
#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
 | 
			
		||||
#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
 | 
			
		||||
#       see the in-line comments for details.
 | 
			
		||||
#
 | 
			
		||||
#       There are tweaks for specific operating systems such as AIX, CygWin,
 | 
			
		||||
#       Darwin, MinGW, and NonStop.
 | 
			
		||||
#
 | 
			
		||||
#   (3) This script is generated from the Groovy template
 | 
			
		||||
#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 | 
			
		||||
#       within the Gradle project.
 | 
			
		||||
#
 | 
			
		||||
#       You can find Gradle at https://github.com/gradle/gradle/.
 | 
			
		||||
#
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
# Attempt to set APP_HOME
 | 
			
		||||
 | 
			
		||||
# Resolve links: $0 may be a link
 | 
			
		||||
app_path=$0
 | 
			
		||||
 | 
			
		||||
# Need this for daisy-chained symlinks.
 | 
			
		||||
while
 | 
			
		||||
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
 | 
			
		||||
    [ -h "$app_path" ]
 | 
			
		||||
do
 | 
			
		||||
    ls=$( ls -ld "$app_path" )
 | 
			
		||||
    link=${ls#*' -> '}
 | 
			
		||||
    case $link in             #(
 | 
			
		||||
      /*)   app_path=$link ;; #(
 | 
			
		||||
      *)    app_path=$APP_HOME$link ;;
 | 
			
		||||
    esac
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
 | 
			
		||||
 | 
			
		||||
APP_NAME="Gradle"
 | 
			
		||||
APP_BASE_NAME=${0##*/}
 | 
			
		||||
 | 
			
		||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 | 
			
		||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 | 
			
		||||
 | 
			
		||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
 | 
			
		||||
MAX_FD=maximum
 | 
			
		||||
 | 
			
		||||
warn () {
 | 
			
		||||
    echo "$*"
 | 
			
		||||
} >&2
 | 
			
		||||
 | 
			
		||||
die () {
 | 
			
		||||
    echo
 | 
			
		||||
    echo "$*"
 | 
			
		||||
    echo
 | 
			
		||||
    exit 1
 | 
			
		||||
} >&2
 | 
			
		||||
 | 
			
		||||
# OS specific support (must be 'true' or 'false').
 | 
			
		||||
cygwin=false
 | 
			
		||||
msys=false
 | 
			
		||||
darwin=false
 | 
			
		||||
nonstop=false
 | 
			
		||||
case "$( uname )" in                #(
 | 
			
		||||
  CYGWIN* )         cygwin=true  ;; #(
 | 
			
		||||
  Darwin* )         darwin=true  ;; #(
 | 
			
		||||
  MSYS* | MINGW* )  msys=true    ;; #(
 | 
			
		||||
  NONSTOP* )        nonstop=true ;;
 | 
			
		||||
esac
 | 
			
		||||
 | 
			
		||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Determine the Java command to use to start the JVM.
 | 
			
		||||
if [ -n "$JAVA_HOME" ] ; then
 | 
			
		||||
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
 | 
			
		||||
        # IBM's JDK on AIX uses strange locations for the executables
 | 
			
		||||
        JAVACMD=$JAVA_HOME/jre/sh/java
 | 
			
		||||
    else
 | 
			
		||||
        JAVACMD=$JAVA_HOME/bin/java
 | 
			
		||||
    fi
 | 
			
		||||
    if [ ! -x "$JAVACMD" ] ; then
 | 
			
		||||
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
 | 
			
		||||
 | 
			
		||||
Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
location of your Java installation."
 | 
			
		||||
    fi
 | 
			
		||||
else
 | 
			
		||||
    JAVACMD=java
 | 
			
		||||
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
 | 
			
		||||
Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
location of your Java installation."
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Increase the maximum file descriptors if we can.
 | 
			
		||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
 | 
			
		||||
    case $MAX_FD in #(
 | 
			
		||||
      max*)
 | 
			
		||||
        MAX_FD=$( ulimit -H -n ) ||
 | 
			
		||||
            warn "Could not query maximum file descriptor limit"
 | 
			
		||||
    esac
 | 
			
		||||
    case $MAX_FD in  #(
 | 
			
		||||
      '' | soft) :;; #(
 | 
			
		||||
      *)
 | 
			
		||||
        ulimit -n "$MAX_FD" ||
 | 
			
		||||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
 | 
			
		||||
    esac
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Collect all arguments for the java command, stacking in reverse order:
 | 
			
		||||
#   * args from the command line
 | 
			
		||||
#   * the main class name
 | 
			
		||||
#   * -classpath
 | 
			
		||||
#   * -D...appname settings
 | 
			
		||||
#   * --module-path (only if needed)
 | 
			
		||||
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
 | 
			
		||||
 | 
			
		||||
# For Cygwin or MSYS, switch paths to Windows format before running java
 | 
			
		||||
if "$cygwin" || "$msys" ; then
 | 
			
		||||
    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
 | 
			
		||||
    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
 | 
			
		||||
 | 
			
		||||
    JAVACMD=$( cygpath --unix "$JAVACMD" )
 | 
			
		||||
 | 
			
		||||
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
 | 
			
		||||
    for arg do
 | 
			
		||||
        if
 | 
			
		||||
            case $arg in                                #(
 | 
			
		||||
              -*)   false ;;                            # don't mess with options #(
 | 
			
		||||
              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
 | 
			
		||||
                    [ -e "$t" ] ;;                      #(
 | 
			
		||||
              *)    false ;;
 | 
			
		||||
            esac
 | 
			
		||||
        then
 | 
			
		||||
            arg=$( cygpath --path --ignore --mixed "$arg" )
 | 
			
		||||
        fi
 | 
			
		||||
        # Roll the args list around exactly as many times as the number of
 | 
			
		||||
        # args, so each arg winds up back in the position where it started, but
 | 
			
		||||
        # possibly modified.
 | 
			
		||||
        #
 | 
			
		||||
        # NB: a `for` loop captures its iteration list before it begins, so
 | 
			
		||||
        # changing the positional parameters here affects neither the number of
 | 
			
		||||
        # iterations, nor the values presented in `arg`.
 | 
			
		||||
        shift                   # remove old arg
 | 
			
		||||
        set -- "$@" "$arg"      # push replacement arg
 | 
			
		||||
    done
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Collect all arguments for the java command;
 | 
			
		||||
#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
 | 
			
		||||
#     shell script including quotes and variable substitutions, so put them in
 | 
			
		||||
#     double quotes to make sure that they get re-expanded; and
 | 
			
		||||
#   * put everything else in single quotes, so that it's not re-expanded.
 | 
			
		||||
 | 
			
		||||
set -- \
 | 
			
		||||
        "-Dorg.gradle.appname=$APP_BASE_NAME" \
 | 
			
		||||
        -classpath "$CLASSPATH" \
 | 
			
		||||
        org.gradle.wrapper.GradleWrapperMain \
 | 
			
		||||
        "$@"
 | 
			
		||||
 | 
			
		||||
# Use "xargs" to parse quoted args.
 | 
			
		||||
#
 | 
			
		||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
 | 
			
		||||
#
 | 
			
		||||
# In Bash we could simply go:
 | 
			
		||||
#
 | 
			
		||||
#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
 | 
			
		||||
#   set -- "${ARGS[@]}" "$@"
 | 
			
		||||
#
 | 
			
		||||
# but POSIX shell has neither arrays nor command substitution, so instead we
 | 
			
		||||
# post-process each arg (as a line of input to sed) to backslash-escape any
 | 
			
		||||
# character that might be a shell metacharacter, then use eval to reverse
 | 
			
		||||
# that process (while maintaining the separation between arguments), and wrap
 | 
			
		||||
# the whole thing up as a single "set" statement.
 | 
			
		||||
#
 | 
			
		||||
# This will of course break if any of these variables contains a newline or
 | 
			
		||||
# an unmatched quote.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
eval "set -- $(
 | 
			
		||||
        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
 | 
			
		||||
        xargs -n1 |
 | 
			
		||||
        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
 | 
			
		||||
        tr '\n' ' '
 | 
			
		||||
    )" '"$@"'
 | 
			
		||||
 | 
			
		||||
exec "$JAVACMD" "$@"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
@rem
 | 
			
		||||
@rem Copyright 2015 the original author or authors.
 | 
			
		||||
@rem
 | 
			
		||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
@rem you may not use this file except in compliance with the License.
 | 
			
		||||
@rem You may obtain a copy of the License at
 | 
			
		||||
@rem
 | 
			
		||||
@rem      https://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
@rem
 | 
			
		||||
@rem Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
@rem See the License for the specific language governing permissions and
 | 
			
		||||
@rem limitations under the License.
 | 
			
		||||
@rem
 | 
			
		||||
 | 
			
		||||
@if "%DEBUG%" == "" @echo off
 | 
			
		||||
@rem ##########################################################################
 | 
			
		||||
@rem
 | 
			
		||||
@rem  Gradle startup script for Windows
 | 
			
		||||
@rem
 | 
			
		||||
@rem ##########################################################################
 | 
			
		||||
 | 
			
		||||
@rem Set local scope for the variables with windows NT shell
 | 
			
		||||
if "%OS%"=="Windows_NT" setlocal
 | 
			
		||||
 | 
			
		||||
set DIRNAME=%~dp0
 | 
			
		||||
if "%DIRNAME%" == "" set DIRNAME=.
 | 
			
		||||
set APP_BASE_NAME=%~n0
 | 
			
		||||
set APP_HOME=%DIRNAME%
 | 
			
		||||
 | 
			
		||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
 | 
			
		||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
 | 
			
		||||
 | 
			
		||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 | 
			
		||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
 | 
			
		||||
 | 
			
		||||
@rem Find java.exe
 | 
			
		||||
if defined JAVA_HOME goto findJavaFromJavaHome
 | 
			
		||||
 | 
			
		||||
set JAVA_EXE=java.exe
 | 
			
		||||
%JAVA_EXE% -version >NUL 2>&1
 | 
			
		||||
if "%ERRORLEVEL%" == "0" goto execute
 | 
			
		||||
 | 
			
		||||
echo.
 | 
			
		||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
echo.
 | 
			
		||||
echo Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
echo location of your Java installation.
 | 
			
		||||
 | 
			
		||||
goto fail
 | 
			
		||||
 | 
			
		||||
:findJavaFromJavaHome
 | 
			
		||||
set JAVA_HOME=%JAVA_HOME:"=%
 | 
			
		||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 | 
			
		||||
 | 
			
		||||
if exist "%JAVA_EXE%" goto execute
 | 
			
		||||
 | 
			
		||||
echo.
 | 
			
		||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
 | 
			
		||||
echo.
 | 
			
		||||
echo Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
echo location of your Java installation.
 | 
			
		||||
 | 
			
		||||
goto fail
 | 
			
		||||
 | 
			
		||||
:execute
 | 
			
		||||
@rem Setup the command line
 | 
			
		||||
 | 
			
		||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@rem Execute Gradle
 | 
			
		||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
 | 
			
		||||
 | 
			
		||||
:end
 | 
			
		||||
@rem End local scope for the variables with windows NT shell
 | 
			
		||||
if "%ERRORLEVEL%"=="0" goto mainEnd
 | 
			
		||||
 | 
			
		||||
:fail
 | 
			
		||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
 | 
			
		||||
rem the _cmd.exe /c_ return code!
 | 
			
		||||
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
 | 
			
		||||
exit /b 1
 | 
			
		||||
 | 
			
		||||
:mainEnd
 | 
			
		||||
if "%OS%"=="Windows_NT" endlocal
 | 
			
		||||
 | 
			
		||||
:omega
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
/build
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
import java.text.SimpleDateFormat
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'com.android.library'
 | 
			
		||||
    id 'org.jetbrains.kotlin.android'
 | 
			
		||||
    id 'kotlin-kapt'
 | 
			
		||||
    id 'maven-publish'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: 'dependencies.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 {
 | 
			
		||||
    compileSdk android.compileSdk
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
//        applicationId "guru.core.analytics"
 | 
			
		||||
        minSdk android.minSdk
 | 
			
		||||
        targetSdk android.targetSdk
 | 
			
		||||
        versionCode 1
 | 
			
		||||
        versionName "1.0"
 | 
			
		||||
 | 
			
		||||
        buildConfigField "String", "buildTs", buildTs()
 | 
			
		||||
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            minifyEnabled false
 | 
			
		||||
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = '1.8'
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api fileTree(dir: 'libs', include: ['*.jar'])
 | 
			
		||||
 | 
			
		||||
    testImplementation 'junit:junit:4.13.2'
 | 
			
		||||
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
 | 
			
		||||
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
 | 
			
		||||
 | 
			
		||||
    implementation basicDependencies
 | 
			
		||||
 | 
			
		||||
    kapt kaptDependencies
 | 
			
		||||
 | 
			
		||||
    implementation roomDependencies
 | 
			
		||||
 | 
			
		||||
    implementation retrofitDependencies
 | 
			
		||||
 | 
			
		||||
    implementation okhttpDependencies
 | 
			
		||||
 | 
			
		||||
    implementation processDependencies
 | 
			
		||||
 | 
			
		||||
    implementation workerDependencies
 | 
			
		||||
 | 
			
		||||
    implementation cronetDependencies
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
ext {
 | 
			
		||||
    compiler = [
 | 
			
		||||
            java  : JavaVersion.VERSION_17,
 | 
			
		||||
            kotlin: '1.9.0'
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    android = [
 | 
			
		||||
            buildTools: '30.0.3',
 | 
			
		||||
            minSdk    : 21,
 | 
			
		||||
            targetSdk : 33,
 | 
			
		||||
            compileSdk: 34
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    androidXCoreVersion = '1.7.0'
 | 
			
		||||
    timberVersion = '4.7.1'
 | 
			
		||||
    roomVersion = '2.6.1'
 | 
			
		||||
    gsonVersion = '2.8.5'
 | 
			
		||||
 | 
			
		||||
    retrofitVersion = '2.7.1'
 | 
			
		||||
    okhttpVersion = '4.12.0'
 | 
			
		||||
    preferenceVersion = '1.2.0'
 | 
			
		||||
    processVersion = '2.4.0'
 | 
			
		||||
    workVersion = '2.9.0'
 | 
			
		||||
    cronetOkhttpVersion = '0.1.0'
 | 
			
		||||
    playServicesCronetVersion = '18.0.1'
 | 
			
		||||
 | 
			
		||||
    kaptDependencies = [
 | 
			
		||||
            "androidx.room:room-compiler:$roomVersion",
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    basicDependencies = [
 | 
			
		||||
            "androidx.core:core:$androidXCoreVersion",
 | 
			
		||||
            "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${compiler.kotlin}",
 | 
			
		||||
            "com.jakewharton.timber:timber:$timberVersion",
 | 
			
		||||
            "com.google.code.gson:gson:$gsonVersion",
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    roomDependencies = [
 | 
			
		||||
            "androidx.room:room-runtime:$roomVersion",
 | 
			
		||||
            "androidx.room:room-rxjava2:$roomVersion"
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    retrofitDependencies = [
 | 
			
		||||
            "com.squareup.retrofit2:retrofit:$retrofitVersion",
 | 
			
		||||
            "com.squareup.retrofit2:converter-gson:$retrofitVersion",
 | 
			
		||||
            "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    okhttpDependencies = [
 | 
			
		||||
            "com.squareup.okhttp3:okhttp:$okhttpVersion",
 | 
			
		||||
            "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion"
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    workerDependencies = [
 | 
			
		||||
            "androidx.work:work-runtime:$workVersion",
 | 
			
		||||
            "androidx.work:work-runtime-ktx:$workVersion",
 | 
			
		||||
            "androidx.work:work-rxjava2:$workVersion"
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    processDependencies = [
 | 
			
		||||
            "androidx.lifecycle:lifecycle-process:$processVersion"
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    cronetDependencies = [
 | 
			
		||||
            "com.google.net.cronet:cronet-okhttp:$cronetOkhttpVersion",
 | 
			
		||||
            "com.google.android.gms:play-services-cronet:$playServicesCronetVersion"
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
publishing {    // Repositories *to* which Gradle can publish artifacts
 | 
			
		||||
    repositories { RepositoryHandler handler ->
 | 
			
		||||
        handler.mavenLocal()
 | 
			
		||||
        maven {
 | 
			
		||||
            url "$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
 | 
			
		||||
        }
 | 
			
		||||
//        maven {
 | 
			
		||||
//            url "http://localhost:8081/repository/maven-releases/"
 | 
			
		||||
//            credentials {
 | 
			
		||||
//                username "admin"
 | 
			
		||||
//                password ""
 | 
			
		||||
//            }
 | 
			
		||||
//        }
 | 
			
		||||
    }
 | 
			
		||||
    publications { PublicationContainer publication ->
 | 
			
		||||
 | 
			
		||||
        // Creates a Maven publication called "myPublication".
 | 
			
		||||
        maven(MavenPublication) {
 | 
			
		||||
            groupId 'guru.core.analytics'
 | 
			
		||||
            artifactId 'guru_analytics'
 | 
			
		||||
            version '1.1.0'             // Your package version
 | 
			
		||||
//                artifact publishArtifact    //Example: *./target/myJavaClasses.jar*
 | 
			
		||||
//            artifact "build/outputs/aar/aar-test-release.aar"//aar包的目录
 | 
			
		||||
            afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
 | 
			
		||||
 | 
			
		||||
            //带上依赖 ,否则会报错
 | 
			
		||||
            pom.withXml {
 | 
			
		||||
                def dependenciesNode = asNode().appendNode('dependencies')
 | 
			
		||||
 | 
			
		||||
                def scopes = []
 | 
			
		||||
                if (configurations.hasProperty("api")) {
 | 
			
		||||
                    scopes.add(configurations.api)
 | 
			
		||||
                }
 | 
			
		||||
                if (configurations.hasProperty("implementation")) {
 | 
			
		||||
                    scopes.add(configurations.implementation)
 | 
			
		||||
                }
 | 
			
		||||
                if (configurations.hasProperty("debugImplementation")) {
 | 
			
		||||
                    scopes.add(configurations.debugImplementation)
 | 
			
		||||
                }
 | 
			
		||||
                if (configurations.hasProperty("releaseImplementation")) {
 | 
			
		||||
                    scopes.add(configurations.releaseImplementation)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
//                    if (project.ext.targetType != "jar") {
 | 
			
		||||
//                        scopes.add(configurations.provided)
 | 
			
		||||
//                    }
 | 
			
		||||
 | 
			
		||||
                scopes.each { scope ->
 | 
			
		||||
                    scope.allDependencies.each {
 | 
			
		||||
                        if (it instanceof ModuleDependency) {
 | 
			
		||||
                            boolean isTransitive = ((ModuleDependency) it).transitive
 | 
			
		||||
                            if (!isTransitive) {
 | 
			
		||||
                                println "<<<< not transitive dependency: [${it.group}, ${it.name}, ${it.version}]"
 | 
			
		||||
                                return
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (it.group == "${project.rootProject.name}.libs" || it.version == 'unspecified') {
 | 
			
		||||
                            return
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (it.group && it.name && it.version) {
 | 
			
		||||
                            def dependencyNode = dependenciesNode.appendNode('dependency')
 | 
			
		||||
                            dependencyNode.appendNode('groupId', it.group)
 | 
			
		||||
                            dependencyNode.appendNode('artifactId', it.name)
 | 
			
		||||
                            dependencyNode.appendNode('version', it.version)
 | 
			
		||||
                            dependencyNode.appendNode('scope', scope.name)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
# Add project specific ProGuard rules here.
 | 
			
		||||
# You can control the set of applied configuration files using the
 | 
			
		||||
# proguardFiles setting in build.gradle.
 | 
			
		||||
#
 | 
			
		||||
# For more details, see
 | 
			
		||||
#   http://developer.android.com/guide/developing/tools/proguard.html
 | 
			
		||||
 | 
			
		||||
# If your project uses WebView with JS, uncomment the following
 | 
			
		||||
# and specify the fully qualified class name to the JavaScript interface
 | 
			
		||||
# class:
 | 
			
		||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
 | 
			
		||||
#   public *;
 | 
			
		||||
#}
 | 
			
		||||
 | 
			
		||||
# Uncomment this to preserve the line number information for
 | 
			
		||||
# debugging stack traces.
 | 
			
		||||
#-keepattributes SourceFile,LineNumberTable
 | 
			
		||||
 | 
			
		||||
# If you keep the line number information, uncomment this to
 | 
			
		||||
# hide the original source file name.
 | 
			
		||||
#-renamesourcefileattribute SourceFile
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
package guru.data
 | 
			
		||||
 | 
			
		||||
import androidx.test.ext.junit.runners.AndroidJUnit4
 | 
			
		||||
import androidx.test.platform.app.InstrumentationRegistry
 | 
			
		||||
import org.junit.Assert.assertEquals
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
import org.junit.runner.RunWith
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Instrumented test, which will execute on an Android device.
 | 
			
		||||
 *
 | 
			
		||||
 * See [testing documentation](http://d.android.com/tools/testing).
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(AndroidJUnit4::class)
 | 
			
		||||
class ExampleInstrumentedTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun useAppContext() {
 | 
			
		||||
        // Context of the app under test.
 | 
			
		||||
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
 | 
			
		||||
        assertEquals("guru.data", appContext.packageName)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    package="guru.core.analytics">
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET" />
 | 
			
		||||
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
package guru.core.analytics
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
object Constants {
 | 
			
		||||
 | 
			
		||||
    const val VERSION = 10
 | 
			
		||||
 | 
			
		||||
    object Event {
 | 
			
		||||
        const val VERSION = "version"
 | 
			
		||||
        const val TIMESTAMP = "timestamp"
 | 
			
		||||
 | 
			
		||||
        const val EVENT = "event"
 | 
			
		||||
        const val PARAM = "param"
 | 
			
		||||
        const val SCREEN = "screen_name"
 | 
			
		||||
        const val ITEM_CATEGORY = "item_category"
 | 
			
		||||
        const val ITEM_NAME = "item_name"
 | 
			
		||||
        const val VALUE = "value"
 | 
			
		||||
        const val FG = "fg"
 | 
			
		||||
        const val DURATION = "duration"
 | 
			
		||||
        const val FIRST_OPEN = "first_open"
 | 
			
		||||
        const val ERROR_PROCESS = "error_process"
 | 
			
		||||
        const val PROCESS = "process"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object Ids {
 | 
			
		||||
        const val DEVICE_ID = "deviceId"
 | 
			
		||||
        const val UID = "uid"
 | 
			
		||||
        const val ADJUST_ID = "adjustId"
 | 
			
		||||
        const val AD_ID = "adId"
 | 
			
		||||
        const val FIREBASE_ID = "firebaseId"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object DeviceInfo {
 | 
			
		||||
        const val APP_ID = "appId"
 | 
			
		||||
        const val PLATFORM = "platform"
 | 
			
		||||
        const val COUNTRY = "country"
 | 
			
		||||
        const val VERSION = "version"
 | 
			
		||||
        const val TZ_OFFSET = "tzOffset"
 | 
			
		||||
        const val DEVICE_TYPE = "deviceType"
 | 
			
		||||
        const val BRAND = "brand"
 | 
			
		||||
        const val MODEL = "model"
 | 
			
		||||
        const val SCREEN_H = "screenH"
 | 
			
		||||
        const val SCREEN_W = "screenW"
 | 
			
		||||
        const val OS_VERSION = "osVersion"
 | 
			
		||||
        const val LANGUAGE = "language"
 | 
			
		||||
        const val SDK_INFO = "sdkInfo"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object Properties {
 | 
			
		||||
        const val FIRST_OPEN_TIME = "first_open_time"
 | 
			
		||||
        const val B_LEVEL = "b_level"
 | 
			
		||||
        const val B_PLAY = "b_play"
 | 
			
		||||
        const val GRADE = "grade"
 | 
			
		||||
        const val IS_IAP_USER = "is_iap_user"
 | 
			
		||||
        const val IAP_COIN = "iap_coin"
 | 
			
		||||
        const val NONIAP_COIN = "noniap_coin"
 | 
			
		||||
        const val COIN = "coin"
 | 
			
		||||
        const val EXP = "exp"
 | 
			
		||||
        const val HP = "hp"
 | 
			
		||||
        const val GURU_ANM = "guru_anm"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object AnmState {
 | 
			
		||||
        const val CRONET = "cronet"
 | 
			
		||||
        const val DEFAULT = "default"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object DeviceType {
 | 
			
		||||
        const val TABLET = "tablet"
 | 
			
		||||
        const val MOBILE = "mobile"
 | 
			
		||||
        const val PC = "pc"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object Platform {
 | 
			
		||||
        const val ANDROID = "ANDROID"
 | 
			
		||||
        const val IOS = "IOS"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,171 @@
 | 
			
		|||
package guru.core.analytics
 | 
			
		||||
 | 
			
		||||
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.model.AnalyticsInfo
 | 
			
		||||
import guru.core.analytics.data.model.AnalyticsOptions
 | 
			
		||||
import guru.core.analytics.impl.GuruAnalyticsImpl
 | 
			
		||||
import java.io.File
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by Haoyi on 2022-11-05.
 | 
			
		||||
 */
 | 
			
		||||
abstract class GuruAnalytics {
 | 
			
		||||
 | 
			
		||||
    protected abstract fun initialize(
 | 
			
		||||
        context: Context,
 | 
			
		||||
        batchLimit: Int? = null,
 | 
			
		||||
        uploadPeriodInSeconds: Long? = null,
 | 
			
		||||
        startUploadDelayInSecond: Long? = 0L,
 | 
			
		||||
        eventExpiredInDays: Int? = 7,
 | 
			
		||||
        debug: Boolean = false,
 | 
			
		||||
        persistableLog: Boolean = true,
 | 
			
		||||
        eventHandlerCallback: ((Int, String?) -> Unit)? = null,
 | 
			
		||||
        isInitPeriodicWork: Boolean = false,
 | 
			
		||||
        uploadEventBaseUrl: String? = null,
 | 
			
		||||
        fgEventPeriodInSeconds: Long? = null,
 | 
			
		||||
        xAppId: String? = null,
 | 
			
		||||
        xDeviceInfo: String? = null,
 | 
			
		||||
        mainProcess: String? = null,
 | 
			
		||||
        isEnableCronet: Boolean? = null,
 | 
			
		||||
        uploadIpAddress: List<String>? = null,
 | 
			
		||||
        dnsMode: Int? = null
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    abstract fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun isDebug(): Boolean
 | 
			
		||||
 | 
			
		||||
    abstract fun setDebug(debug: Boolean)
 | 
			
		||||
 | 
			
		||||
    abstract fun setDeviceId(deviceId: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun setUid(uid: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun setAdjustId(adjustId: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun setAdId(adId: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun setFirebaseId(firebaseId: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun setScreen(screenName: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun zipLogs(context: Context): File?
 | 
			
		||||
 | 
			
		||||
    abstract fun logEvent(
 | 
			
		||||
        eventName: String,
 | 
			
		||||
        itemCategory: String? = null,
 | 
			
		||||
        itemName: String? = null,
 | 
			
		||||
        value: Number? = null,
 | 
			
		||||
        parameters: Map<String, Any>? = null,
 | 
			
		||||
        options: AnalyticsOptions = AnalyticsOptions()
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    abstract fun setUserProperty(key: String, value: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun getEventsStatics(): EventStatistic
 | 
			
		||||
 | 
			
		||||
    abstract fun addEventHandler(listener: ((Int, String?) -> Unit))
 | 
			
		||||
 | 
			
		||||
    abstract fun removeEventHandler(listener: ((Int, String?) -> Unit))
 | 
			
		||||
 | 
			
		||||
    abstract fun removeUserProperty(key: String)
 | 
			
		||||
 | 
			
		||||
    abstract fun removeUserProperties(keys: Set<String>)
 | 
			
		||||
 | 
			
		||||
    abstract fun getUserProperties(callback: (Map<String, String>) -> Unit)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * setUserProperty后立即获取, 可能无法获取到最新设置的值
 | 
			
		||||
     */
 | 
			
		||||
    abstract fun peakUserProperties(): Map<String, String>
 | 
			
		||||
 | 
			
		||||
    abstract fun setEnableUpload(enable: Boolean)
 | 
			
		||||
 | 
			
		||||
    abstract fun snapshotAnalyticsAudit(): String
 | 
			
		||||
 | 
			
		||||
    abstract fun clearStatistic(context: Context)
 | 
			
		||||
 | 
			
		||||
    abstract fun forceUpload(scene: String = "unknown"): Boolean
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val INSTANCE: GuruAnalytics by lazy() {
 | 
			
		||||
            GuruAnalyticsImpl()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class Builder(val context: Context) {
 | 
			
		||||
        private val analyticsInfo = AnalyticsInfo()
 | 
			
		||||
 | 
			
		||||
        fun setBatchLimit(batchLimit: Int?) = apply { analyticsInfo.batchLimit = batchLimit }
 | 
			
		||||
 | 
			
		||||
        fun setUploadPeriodInSeconds(uploadPeriodInSeconds: Long?) =
 | 
			
		||||
            apply { analyticsInfo.uploadPeriodInSeconds = uploadPeriodInSeconds }
 | 
			
		||||
 | 
			
		||||
        fun setStartUploadDelayInSecond(startUploadDelayInSecond: Long?) =
 | 
			
		||||
            apply { analyticsInfo.startUploadDelayInSecond = startUploadDelayInSecond }
 | 
			
		||||
 | 
			
		||||
        fun setEventExpiredInDays(eventExpiredInDays: Int?) =
 | 
			
		||||
            apply { analyticsInfo.eventExpiredInDays = eventExpiredInDays }
 | 
			
		||||
 | 
			
		||||
        fun isPersistableLog(persistableLog: Boolean) =
 | 
			
		||||
            apply { analyticsInfo.persistableLog = persistableLog }
 | 
			
		||||
 | 
			
		||||
        fun setEventHandlerCallback(eventHandlerCallback: ((Int, String?) -> Unit)) =
 | 
			
		||||
            apply { analyticsInfo.eventHandlerCallback = eventHandlerCallback }
 | 
			
		||||
 | 
			
		||||
        fun isDebug(debug: Boolean) = apply { analyticsInfo.debug = debug }
 | 
			
		||||
 | 
			
		||||
        fun isInitPeriodicWork(isInit: Boolean) = apply { analyticsInfo.isInitPeriodicWork = isInit }
 | 
			
		||||
 | 
			
		||||
        fun setUploadEventBaseUrl(uploadEventBaseUrl: String?) =
 | 
			
		||||
            apply { analyticsInfo.uploadEventBaseUrl = uploadEventBaseUrl }
 | 
			
		||||
 | 
			
		||||
        fun setFgEventPeriodInSeconds(fgEventPeriodInSeconds: Long?) =
 | 
			
		||||
                apply { analyticsInfo.fgEventPeriodInSeconds = fgEventPeriodInSeconds }
 | 
			
		||||
 | 
			
		||||
        fun setXAppId(xAppId: String) =
 | 
			
		||||
            apply { analyticsInfo.xAppId = xAppId }
 | 
			
		||||
 | 
			
		||||
        fun setXDeviceInfo(xDeviceInfo: String) =
 | 
			
		||||
            apply { analyticsInfo.xDeviceInfo = xDeviceInfo }
 | 
			
		||||
 | 
			
		||||
        fun setMainProcess(process: String) =
 | 
			
		||||
            apply { analyticsInfo.mainProcess = process }
 | 
			
		||||
 | 
			
		||||
        fun isEnableCronet(isEnableCronet: Boolean) =
 | 
			
		||||
            apply { analyticsInfo.isEnableCronet = isEnableCronet }
 | 
			
		||||
 | 
			
		||||
        fun setUploadIpAddress(uploadIpAddress: List<String>) =
 | 
			
		||||
            apply { analyticsInfo.uploadIpAddress = uploadIpAddress }
 | 
			
		||||
 | 
			
		||||
        fun setDnsMode(@DnsMode dnsMode: Int) =
 | 
			
		||||
            apply { analyticsInfo.dnsMode = dnsMode }
 | 
			
		||||
 | 
			
		||||
        fun build(): GuruAnalytics {
 | 
			
		||||
            analyticsInfo.run {
 | 
			
		||||
                INSTANCE.initialize(
 | 
			
		||||
                    context,
 | 
			
		||||
                    batchLimit,
 | 
			
		||||
                    uploadPeriodInSeconds,
 | 
			
		||||
                    startUploadDelayInSecond,
 | 
			
		||||
                    eventExpiredInDays,
 | 
			
		||||
                    debug,
 | 
			
		||||
                    persistableLog,
 | 
			
		||||
                    eventHandlerCallback,
 | 
			
		||||
                    isInitPeriodicWork,
 | 
			
		||||
                    uploadEventBaseUrl,
 | 
			
		||||
                    fgEventPeriodInSeconds,
 | 
			
		||||
                    xAppId,
 | 
			
		||||
                    xDeviceInfo,
 | 
			
		||||
                    mainProcess,
 | 
			
		||||
                    isEnableCronet,
 | 
			
		||||
                    uploadIpAddress,
 | 
			
		||||
                    dnsMode
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            return INSTANCE
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
package guru.core.analytics.data.api
 | 
			
		||||
 | 
			
		||||
import io.reactivex.Single
 | 
			
		||||
import okhttp3.RequestBody
 | 
			
		||||
import retrofit2.Retrofit
 | 
			
		||||
import retrofit2.http.Body
 | 
			
		||||
import retrofit2.http.POST
 | 
			
		||||
 | 
			
		||||
interface AnalyticsApi {
 | 
			
		||||
 | 
			
		||||
    @POST("event")
 | 
			
		||||
    fun uploadEvent(@Body body: RequestBody): Single<Unit>
 | 
			
		||||
 | 
			
		||||
    object Creator {
 | 
			
		||||
        fun newInstance(retrofit: Retrofit): AnalyticsApi {
 | 
			
		||||
            return retrofit.create(AnalyticsApi::class.java)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
object AnalyticsApiHost {
 | 
			
		||||
    const val BASE_URL = "https://collect.saas.castbox.fm/"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
package guru.core.analytics.data.api
 | 
			
		||||
 | 
			
		||||
import guru.core.analytics.utils.GZipUtils
 | 
			
		||||
import io.reactivex.Single
 | 
			
		||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
 | 
			
		||||
import okhttp3.RequestBody.Companion.toRequestBody
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据获取汇总接口
 | 
			
		||||
 */
 | 
			
		||||
interface GuruRepository {
 | 
			
		||||
    var analyticsApi: AnalyticsApi
 | 
			
		||||
 | 
			
		||||
    fun uploadEvents(json: String): Single<Unit>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据调度具体实现
 | 
			
		||||
 */
 | 
			
		||||
class DefaultGuruRepository(
 | 
			
		||||
    override var analyticsApi: AnalyticsApi
 | 
			
		||||
) : GuruRepository {
 | 
			
		||||
 | 
			
		||||
    override fun uploadEvents(json: String): Single<Unit> {
 | 
			
		||||
        val gzipJson = GZipUtils.compress(json)
 | 
			
		||||
            ?: return Single.error(IllegalArgumentException("param json is null or gzip fail"))
 | 
			
		||||
        val requestBody = gzipJson.toRequestBody("application/json".toMediaTypeOrNull())
 | 
			
		||||
        return analyticsApi.uploadEvent(requestBody)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,238 @@
 | 
			
		|||
package guru.core.analytics.data.api
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.SystemClock
 | 
			
		||||
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.DnsMode
 | 
			
		||||
import guru.core.analytics.data.api.dns.GoogleDnsApi
 | 
			
		||||
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.LoggingInterceptor
 | 
			
		||||
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.EventHandler
 | 
			
		||||
import guru.core.analytics.utils.DateTimeUtils
 | 
			
		||||
import guru.core.analytics.utils.GsonUtil
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import okhttp3.Dispatcher
 | 
			
		||||
import okhttp3.Headers
 | 
			
		||||
import okhttp3.Interceptor
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import okhttp3.internal.platform.Platform
 | 
			
		||||
import retrofit2.Converter
 | 
			
		||||
import retrofit2.Retrofit
 | 
			
		||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
 | 
			
		||||
import retrofit2.converter.gson.GsonConverterFactory
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
// Todo: 该类都需要重构,业务不清晰,不符合打点库使用
 | 
			
		||||
object ServiceLocator {
 | 
			
		||||
 | 
			
		||||
    private var debug = false
 | 
			
		||||
 | 
			
		||||
    @Volatile
 | 
			
		||||
    private var guruRepository: GuruRepository? = null
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    fun addHeaderParam(key: String, value: String?) {
 | 
			
		||||
        if (value.isNullOrBlank()) return
 | 
			
		||||
        headerParams[key] = value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setDebug(debug: Boolean) {
 | 
			
		||||
        this.debug = debug
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    fun setUploadIpAddress(ipList: List<String>?) {
 | 
			
		||||
        if (compositeDns.ipAddress != ipList) {
 | 
			
		||||
            compositeDns.setCandidateIpAddress(ipAddress = 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 {
 | 
			
		||||
        synchronized(this) {
 | 
			
		||||
            return guruRepository
 | 
			
		||||
                ?: createQuotesRepository(context, baseUri).apply { guruRepository = this }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createQuotesRepository(context: Context, baseUri: Uri? = null): GuruRepository {
 | 
			
		||||
        return DefaultGuruRepository(provideAnalyticsApi(context, baseUri))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun provideAnalyticsApi(context: Context, baseUri: Uri? = null): AnalyticsApi {
 | 
			
		||||
        val finalBaseUrl = if (baseUri != null) {
 | 
			
		||||
            baseUri.toString()
 | 
			
		||||
        } else {
 | 
			
		||||
            val cacheBaseUrl = PreferencesManager.getInstance(context).uploadEventBaseUrl
 | 
			
		||||
            if (cacheBaseUrl.isNullOrEmpty()) AnalyticsApiHost.BASE_URL else cacheBaseUrl
 | 
			
		||||
        }
 | 
			
		||||
        synchronized(this) {
 | 
			
		||||
            return guruRepository?.analyticsApi ?: createAnalyticsApi(context, finalBaseUrl)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createAnalyticsApi(context: Context, baseUrl: String): AnalyticsApi {
 | 
			
		||||
        return AnalyticsApi.Creator.newInstance(
 | 
			
		||||
            Retrofit.Builder()
 | 
			
		||||
                .baseUrl(baseUrl)
 | 
			
		||||
                .client(createOkHttpClient(context))
 | 
			
		||||
                .addConverterFactory(createJsonConvertFactory())
 | 
			
		||||
                .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
 | 
			
		||||
                .build()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateAnalyticsBaseUrl(context: Context, baseUrl: String) {
 | 
			
		||||
        if (baseUrl.isBlank()) return
 | 
			
		||||
        guruRepository?.analyticsApi = createAnalyticsApi(context, baseUrl)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    fun createGoogleDnsApi(context: Context): GoogleDnsApi {
 | 
			
		||||
        return GoogleDnsApi.Creator.newInstance(
 | 
			
		||||
            Retrofit.Builder()
 | 
			
		||||
                .baseUrl(GoogleDnsApiHost.API)
 | 
			
		||||
                .client(createDnsOkHttpClient(context))
 | 
			
		||||
                .addConverterFactory(createJsonConvertFactory())
 | 
			
		||||
                .build()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createJsonConvertFactory(): Converter.Factory {
 | 
			
		||||
        return GsonConverterFactory.create(GsonUtil.gson)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createOkHttpClient(
 | 
			
		||||
        context: Context,
 | 
			
		||||
        readTimeOut: Long = 90L,
 | 
			
		||||
        writeTimeOut: Long = 90L,
 | 
			
		||||
    ): OkHttpClient {
 | 
			
		||||
        val dns = when (dnsMode) {
 | 
			
		||||
            DnsMode.COMPOSITE -> compositeDns
 | 
			
		||||
            DnsMode.STATIC -> StaticDns(uploadIpAddress)
 | 
			
		||||
            else -> CustomDns(context, uploadIpAddress = uploadIpAddress)
 | 
			
		||||
        }
 | 
			
		||||
        val builder = OkHttpClient.Builder()
 | 
			
		||||
            .dispatcher(Dispatcher().apply {
 | 
			
		||||
                maxRequests = 128
 | 
			
		||||
                maxRequestsPerHost = 5
 | 
			
		||||
            })
 | 
			
		||||
            .dns(dns)
 | 
			
		||||
            .connectTimeout(90L, TimeUnit.SECONDS)
 | 
			
		||||
            .readTimeout(readTimeOut, TimeUnit.SECONDS)
 | 
			
		||||
            .writeTimeout(writeTimeOut, TimeUnit.SECONDS)
 | 
			
		||||
            .addInterceptor(createAnalyticsApiInterceptor())
 | 
			
		||||
            .addInterceptor(createLoggingInterceptor())
 | 
			
		||||
        if (isEnabledCronet) {
 | 
			
		||||
            builder.addInterceptor(createCronetInterceptor())
 | 
			
		||||
        }
 | 
			
		||||
        return builder.build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createDnsOkHttpClient(context: Context): OkHttpClient {
 | 
			
		||||
        val builder = OkHttpClient.Builder()
 | 
			
		||||
            .connectTimeout(90L, TimeUnit.SECONDS)
 | 
			
		||||
            .readTimeout(90L, TimeUnit.SECONDS)
 | 
			
		||||
            .writeTimeout(90L, TimeUnit.SECONDS)
 | 
			
		||||
            .addInterceptor(createLoggingInterceptor())
 | 
			
		||||
        return builder.build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createLoggingInterceptor(): Interceptor {
 | 
			
		||||
        return LoggingInterceptor.Builder()
 | 
			
		||||
            .setLevel(Level.BASIC)
 | 
			
		||||
            .log(Platform.INFO)
 | 
			
		||||
            .request("AnalyticsRequest")
 | 
			
		||||
            .response("AnalyticsResponse")
 | 
			
		||||
            .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add the Cronet interceptor last, otherwise the subsequent interceptors will be skipped.
 | 
			
		||||
     * https://github.com/google/cronet-transport-for-okhttp
 | 
			
		||||
     */
 | 
			
		||||
    private fun createCronetInterceptor(): Interceptor {
 | 
			
		||||
        return CastboxCronetInterceptor()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createAnalyticsApiInterceptor(): Interceptor {
 | 
			
		||||
        return Interceptor { chain ->
 | 
			
		||||
            val request = chain.request()
 | 
			
		||||
            val startTime = SystemClock.elapsedRealtime()
 | 
			
		||||
            val builder = request.newBuilder()
 | 
			
		||||
                .addHeader(CONTENT_ENCODING, "gzip")
 | 
			
		||||
                .addHeader(X_EVENT_TYPE, "event")
 | 
			
		||||
            headerParams.forEach {
 | 
			
		||||
                builder.addHeader(it.key, it.value)
 | 
			
		||||
            }
 | 
			
		||||
            val newRequest = builder.build()
 | 
			
		||||
            try {
 | 
			
		||||
                chain.proceed(newRequest).also { response ->
 | 
			
		||||
                    val responseTime = (SystemClock.elapsedRealtime() - startTime) / 2
 | 
			
		||||
                    calibrationTime(responseTime, response.headers)
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_API, e.message)
 | 
			
		||||
                throw e
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var minResponseTime: Long = Long.MAX_VALUE
 | 
			
		||||
    private fun calibrationTime(responseTime: Long, headers: Headers?) {
 | 
			
		||||
        // If the current response time is less than the previous min one, calibrate again
 | 
			
		||||
        if (headers == null || responseTime >= minResponseTime) return
 | 
			
		||||
        val date = headers.getDate("Date") ?: return
 | 
			
		||||
        DateTimeUtils.initServerTime(date.time + responseTime)
 | 
			
		||||
        minResponseTime = responseTime
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const val CONTENT_TYPE = "Content-Type"
 | 
			
		||||
const val CONTENT_ENCODING = "Content-Encoding"
 | 
			
		||||
const val X_EVENT_TYPE = "x_event_type"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
package guru.core.analytics.data.api.cronet
 | 
			
		||||
 | 
			
		||||
import guru.core.analytics.handler.AnalyticsCode
 | 
			
		||||
import guru.core.analytics.handler.EventHandler
 | 
			
		||||
import okhttp3.Interceptor
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CastboxCronetInterceptor : Interceptor {
 | 
			
		||||
 | 
			
		||||
    override fun intercept(chain: Interceptor.Chain): Response {
 | 
			
		||||
        val originalRequest = chain.request()
 | 
			
		||||
        return if (CronetHelper.cronetInterceptor != null) {
 | 
			
		||||
            return try {
 | 
			
		||||
                CronetHelper.cronetInterceptor!!.intercept(chain)
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                EventHandler.INSTANCE.notifyEventHandler(
 | 
			
		||||
                    AnalyticsCode.ERROR_CRONET_INTERCEPTOR,
 | 
			
		||||
                    e.message
 | 
			
		||||
                )
 | 
			
		||||
                throw e
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            chain.proceed(originalRequest)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
package guru.core.analytics.data.api.cronet
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import com.google.android.gms.net.CronetProviderInstaller
 | 
			
		||||
import com.google.net.cronet.okhttptransport.CronetInterceptor
 | 
			
		||||
import guru.core.analytics.handler.AnalyticsCode
 | 
			
		||||
import guru.core.analytics.handler.EventHandler
 | 
			
		||||
import org.chromium.net.CronetEngine
 | 
			
		||||
 | 
			
		||||
object CronetHelper {
 | 
			
		||||
 | 
			
		||||
    var cronetInterceptor: CronetInterceptor? = null
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
    fun init(context: Context, isEnable: Boolean?, callback:((Boolean) -> Unit)? = null) {
 | 
			
		||||
        if (isEnable == true) {
 | 
			
		||||
            try {
 | 
			
		||||
                CronetProviderInstaller.installProvider(context).addOnSuccessListener {
 | 
			
		||||
                    val cronetEngine = CronetEngine.Builder(context).build()
 | 
			
		||||
                    cronetInterceptor = CronetInterceptor.newBuilder(cronetEngine).build()
 | 
			
		||||
                    callback?.invoke(true)
 | 
			
		||||
                    EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.CRONET_INIT_SUCCESS)
 | 
			
		||||
                }.addOnFailureListener {
 | 
			
		||||
                    cronetInterceptor = null
 | 
			
		||||
                    callback?.invoke(false)
 | 
			
		||||
                    EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.CRONET_INIT_FAIL, it.message)
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                callback?.invoke(false)
 | 
			
		||||
                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.CRONET_INIT_EXCEPTION, e.message)
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            cronetInterceptor = null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||