Compare commits

...

2 Commits
main ... 3.0.0

Author SHA1 Message Date
Haoyi 3803d53afe update guru_analytics
Signed-off-by: Haoyi <haoyi.zhang@castbox.fm>
2024-05-09 16:21:50 +08:00
Haoyi 5927987ff5 guru lib v3.0.0
Signed-off-by: Haoyi <haoyi.zhang@castbox.fm>
2023-12-22 12:57:28 +08:00
202 changed files with 8050 additions and 0 deletions

14
GuruConsent-Android/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
.gradle
/captures
/local.properties
/.idea/workspace.xml
.DS_Store
/build
.idea/
*iml
*.iml
*/build
/lib
wh.properties
/atom
*.txt

View File

@ -0,0 +1 @@
/build

View File

@ -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'
}

View File

@ -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)
}
}
}
}
}
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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>

View File

@ -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"
}
}
}

View File

@ -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)
}
}

View File

@ -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
)
}
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

1
GuruConsent-Android/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -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'
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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>

View File

@ -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)
}
}

View File

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

View File

@ -0,0 +1,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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">GuruGdprExample</string>
</resources>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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

Binary file not shown.

View File

@ -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

185
GuruConsent-Android/gradlew vendored Executable file
View File

@ -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" "$@"

89
GuruConsent-Android/gradlew.bat vendored Normal file
View File

@ -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

View File

@ -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'

14
guru_analytics/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
.gradle
/captures
/local.properties
/.idea/workspace.xml
.DS_Store
/build
.idea/
*iml
*.iml
*/build
/lib
wh.properties
/atom
*.txt

174
guru_analytics/CHANGELOG.md Normal file
View File

@ -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 |

1
guru_analytics/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,46 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.guruanalytics"
minSdk 21
targetSdk 32
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(project(':guru_analytics'))
// implementation 'guru.core.analytics:guru_analytics:0.1.0'
}

21
guru_analytics/app/proguard-rules.pro vendored Normal file
View File

@ -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

View File

@ -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)
}
}

View File

@ -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>

View File

@ -0,0 +1,118 @@
package com.example.guruanalytics
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import guru.core.analytics.GuruAnalytics
import guru.core.analytics.data.db.model.EventPriority
import guru.core.analytics.data.model.AnalyticsOptions
class MainActivity : AppCompatActivity() {
private var enableUpload = true
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,
// )
GuruAnalytics.Builder(this)
.setBatchLimit(25)
.setUploadPeriodInSeconds(60)
.setStartUploadDelayInSecond(3)
.setEventExpiredInDays(7)
.isPersistableLog(true)
.setEventHandlerCallback(eventHandler)
.isInitPeriodicWork(false)
.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)
.setUploadIpAddress(listOf("3.210.96.186", "34.196.69.199"))
.build()
findViewById<TextView>(R.id.tvLogEvent).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)
}
findViewById<TextView>(R.id.tvOpenTestProcessActivity).setOnClickListener {
TestProcessActivity.startActivity(this)
}
findViewById<TextView>(R.id.tvLocalLog).setOnClickListener {
val startTime = System.currentTimeMillis()
GuruAnalytics.INSTANCE.zipLogs(this)
Log.i("get_local_log", "${System.currentTimeMillis() - startTime}")
}
findViewById<TextView>(R.id.tvEventStatistic).setOnClickListener {
GuruAnalytics.INSTANCE.getEventsStatics().run {
Log.i(
"UploadEventDaemon_main",
"$eventCountAll $eventCountDeleted $eventCountUploaded"
)
}
}
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"
}
GuruAnalytics.INSTANCE.setScreen("main")
GuruAnalytics.INSTANCE.setAdId("AD_ID_01")
GuruAnalytics.INSTANCE.setUserProperty("uid", "110051")
GuruAnalytics.INSTANCE.setUserProperty("age", "12")
GuruAnalytics.INSTANCE.setUserProperty("sex", "male")
val properties = GuruAnalytics.INSTANCE.peakUserProperties()
Log.i("peakUserProperties", "properties:$properties")
GuruAnalytics.INSTANCE.getUserProperties {
Log.i("getUserProperties", "properties:$it")
}
tvEnable?.postDelayed({
val snapshot = GuruAnalytics.INSTANCE.snapshotAnalyticsAudit()
Log.i("snapshotAnalyticsAudit", "snapshot:$snapshot")
}, 10_000)
}
private val eventHandler: (Int, String?) -> Unit = { code, ext ->
Log.i("eventHandler", "$code${if (ext.isNullOrBlank()) "" else " $ext"}")
}
override fun onDestroy() {
GuruAnalytics.INSTANCE.removeEventHandler(eventHandler)
super.onDestroy()
}
}

View File

@ -0,0 +1,50 @@
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")
.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")
}
}
}

View File

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

View File

@ -0,0 +1,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>

View File

@ -0,0 +1,82 @@
<?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_marginTop="50dp"
android:padding="10dp"
android:text="SET PROPERTY"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/tvLogEvent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="LOG EVENT"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/tvEventEmergence"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Event_EMERGENCE"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/tvOpenTestProcessActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Open TestProcessActivity"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/tvLocalLog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="get local log file"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/tvEventStatistic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Event Statistic"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/tvBaseUrl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Update BaseUrl"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/tvEnable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Enable Upload"
android:layout_gravity="center_horizontal" />
</LinearLayout>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">GuruAnalytics</string>
</resources>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)
}
}

View File

@ -0,0 +1,25 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
// maven { url 'http://localhost:8081/repository/maven-public/' }
google()
mavenCentral()
mavenLocal()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
// maven { url 'http://localhost:8081/repository/maven-public/' }
mavenCentral()
google()
mavenLocal()
}
}

View File

@ -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

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
guru_analytics/gradlew vendored Executable file
View File

@ -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" "$@"

89
guru_analytics/gradlew.bat vendored Normal file
View File

@ -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

View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,61 @@
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'
android {
compileSdk android.compileSdk
defaultConfig {
// applicationId "guru.core.analytics"
minSdk android.minSdk
targetSdk android.targetSdk
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 {
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
}

View File

@ -0,0 +1,67 @@
ext {
compiler = [
java : JavaVersion.VERSION_1_8,
kotlin: '1.6.10'
]
android = [
buildTools: '30.0.3',
minSdk : 21,
targetSdk : 32,
compileSdk: 32
]
androidXCoreVersion = '1.7.0'
timberVersion = '4.7.1'
roomVersion = '2.4.3'
gsonVersion = '2.8.5'
retrofitVersion = '2.7.1'
okhttpVersion = '4.9.3'
preferenceVersion = '1.2.0'
processVersion = '2.4.0'
workVersion = '2.7.1'
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"
]
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"
]
}

View File

@ -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.0.3' // 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)
}
}
}
}
}
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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>

View File

@ -0,0 +1,77 @@
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"
}
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"
}
}

View File

@ -0,0 +1,161 @@
package guru.core.analytics
import android.content.Context
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,
)
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
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 build(): GuruAnalytics {
analyticsInfo.run {
INSTANCE.initialize(
context,
batchLimit,
uploadPeriodInSeconds,
startUploadDelayInSecond,
eventExpiredInDays,
debug,
persistableLog,
eventHandlerCallback,
isInitPeriodicWork,
uploadEventBaseUrl,
fgEventPeriodInSeconds,
xAppId,
xDeviceInfo,
mainProcess,
isEnableCronet,
uploadIpAddress,
)
}
return INSTANCE
}
}
}

View File

@ -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/"
}

View File

@ -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)
}
}

View File

@ -0,0 +1,229 @@
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.CustomDns
import guru.core.analytics.data.api.dns.GoogleDnsApi
import guru.core.analytics.data.api.dns.GoogleDnsApiHost
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.handler.AnalyticsCode
import guru.core.analytics.handler.EventHandler
import guru.core.analytics.utils.AndroidUtils
import guru.core.analytics.utils.DateTimeUtils
import guru.core.analytics.utils.GsonUtil
import okhttp3.Dispatcher
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody
import okhttp3.internal.platform.Platform
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
object ServiceLocator {
private var debug = false
@Volatile
private var guruRepository: GuruRepository? = null
@Volatile
private var googleDnsApi: GoogleDnsApi? = null
private val headerParams = mutableMapOf<String, String>()
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
}
fun setUploadIpAddress(ipList: List<String>?) {
uploadIpAddress = ipList
}
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 provideGoogleDnsApi(context: Context): GoogleDnsApi {
synchronized(this) {
return googleDnsApi
?: createGoogleDnsApi(context).apply { googleDnsApi = this }
}
}
private 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 = 30L,
writeTimeOut: Long = 30L
): OkHttpClient {
val builder = OkHttpClient.Builder()
.dispatcher(Dispatcher().apply {
maxRequests = 128
maxRequestsPerHost = 10
})
.dns(CustomDns(context, uploadIpAddress))
.connectTimeout(20L, TimeUnit.SECONDS)
.readTimeout(readTimeOut, TimeUnit.SECONDS)
.writeTimeout(writeTimeOut, TimeUnit.SECONDS)
.addInterceptor(createCacheControlInterceptor(context))
.addInterceptor(createAnalyticsApiInterceptor())
.addInterceptor(createLoggingInterceptor())
.addInterceptor(createCronetInterceptor())
return builder.build()
}
private fun createDnsOkHttpClient(context: Context): OkHttpClient {
val builder = OkHttpClient.Builder()
.addInterceptor(createCacheControlInterceptor(context))
.addInterceptor(createLoggingInterceptor())
.addInterceptor(createCronetInterceptor())
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 createCacheControlInterceptor(context: Context) = Interceptor { chain ->
try {
val originalResponse = chain.proceed(chain.request())
when {
originalResponse.headers.names().contains("Cache-Control") -> {
return@Interceptor originalResponse.newBuilder().build()
}
AndroidUtils.isInternetAvailable(context) -> {
val maxAge = 60 * 5 // read from cache for 5 minute
return@Interceptor originalResponse.newBuilder()
.header("Cache-Control", "public, max-age=$maxAge")
.build()
}
else -> {
val maxStale = 60 * 60 * 24 * 28 // tolerate 4-weeks stale
return@Interceptor originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=$maxStale")
.build()
}
}
} catch (e: Exception) {
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_CACHE_CONTROL, e.message)
throw e
}
}
private fun createAnalyticsApiInterceptor(): Interceptor {
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"

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

Some files were not shown because too many files have changed in this diff Show More