TP-12345 | Added Alfred Updated Module
This commit is contained in:
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
@@ -10,6 +11,7 @@
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/navi-alfred" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -5,14 +5,11 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace 'com.alfred.demo'
|
||||
compileSdk 33
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.alfred.demo"
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
targetSdk 32
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
@@ -33,12 +30,6 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.3.2'
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
@@ -47,20 +38,13 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.4.0'
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
|
||||
implementation 'androidx.activity:activity-compose:1.5.1'
|
||||
implementation platform('androidx.compose:compose-bom:2022.10.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.ui:ui-graphics'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.4"
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
|
||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.alfred.demo
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.alfred.demo.ui.theme.AlfredAndroidTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
AlfredAndroidTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
Greeting("Android")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = "Hello $name!",
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun GreetingPreview() {
|
||||
AlfredAndroidTheme {
|
||||
Greeting("Android")
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.alfred.demo.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.alfred.demo.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun AlfredAndroidTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = colorScheme.primary.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.alfred.demo.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -1,6 +1,9 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
}
|
||||
plugins {
|
||||
id 'com.android.application' version '8.0.0' apply false
|
||||
id 'com.android.library' version '8.0.0' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
|
||||
id 'com.android.application' version '7.3.1' apply false
|
||||
id 'com.android.library' version '7.3.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-kapt'
|
||||
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.navi.alfred'
|
||||
compileSdk 33
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
targetSdk 31
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
@@ -21,6 +23,21 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
flavorDimensions "app"
|
||||
productFlavors {
|
||||
qa {
|
||||
dimension "app"
|
||||
buildConfigField 'String', 'BASE_URL', formatString('https://qa-alfred-ingester.np.navi-sa.in/')
|
||||
}
|
||||
dev {
|
||||
dimension "app"
|
||||
buildConfigField 'String', 'BASE_URL', formatString('https://dev-sa.navi.com/')
|
||||
}
|
||||
prod{
|
||||
dimension "app"
|
||||
buildConfigField 'String', 'BASE_URL', formatString('https://alfred-ingester.prod.navi-sa.in/')
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
@@ -29,13 +46,42 @@ android {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
static def formatString(String value) {
|
||||
return '"' + value + '"'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
implementation 'androidx.core:core-ktx:1.6.0'
|
||||
implementation "androidx.appcompat:appcompat:1.5.1"
|
||||
implementation "com.google.android.material:material:1.7.0"
|
||||
// Timber for logging
|
||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
||||
// Gson
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
// Firebase SDK for Push Notification
|
||||
api 'com.google.firebase:firebase-analytics-ktx'
|
||||
// Firebase SDK for Google Analytics (Kotlin)
|
||||
api 'com.google.firebase:firebase-crashlytics:18.3.6'
|
||||
// Import the BoM for the Firebase platform
|
||||
api platform('com.google.firebase:firebase-bom:30.1.0')
|
||||
implementation "androidx.room:room-runtime:2.4.3"
|
||||
// Kotlin + coroutines
|
||||
implementation "androidx.work:work-runtime-ktx:2.7.1"
|
||||
|
||||
|
||||
// To use Kotlin annotation processing tool (kapt)
|
||||
kapt "androidx.room:room-compiler:2.4.3"
|
||||
// optional - Kotlin Extensions and Coroutines support for Room
|
||||
implementation "androidx.room:room-ktx:2.4.3"
|
||||
api "com.squareup.retrofit2:retrofit:2.9.0"
|
||||
api 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||
api 'com.squareup.okhttp3:logging-interceptor:4.9.0'
|
||||
debugImplementation 'com.github.chuckerteam.chucker:library:3.5.2'
|
||||
releaseImplementation 'com.github.chuckerteam.chucker:library-no-op:3.5.2'
|
||||
testImplementation "androidx.room:room-testing:2.4.3"
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
</manifest>
|
||||
244
navi-alfred/src/main/java/com/navi/alfred/AlfredConfig.kt
Normal file
244
navi-alfred/src/main/java/com/navi/alfred/AlfredConfig.kt
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2022-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred
|
||||
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.text.TextUtils
|
||||
import com.navi.alfred.utils.AlfredConstants
|
||||
import com.navi.alfred.utils.getCarrierName
|
||||
import com.navi.alfred.utils.getNetworkType
|
||||
import java.util.*
|
||||
|
||||
data class AlfredConfig(
|
||||
private var appVersionCode: String = "",
|
||||
private var appVersionName: String = "",
|
||||
private var appName: String = "",
|
||||
private var framesInterval: Int = 2000,
|
||||
private var enable: Boolean = false,
|
||||
private var eventBatchSize: Int = 20,
|
||||
private var alfredSessionId: String = "",
|
||||
private var flavor: String = "",
|
||||
private var advertisingId: String? = null,
|
||||
private var latitude: Double? = null,
|
||||
private var longitude: Double? = null,
|
||||
private var userId: String? = null,
|
||||
private var alfredEventId: String = "",
|
||||
private var eventStartRecordingTime: Long? = null,
|
||||
private var nextEventStartRecordingTime: Long? = null,
|
||||
private var sessionStartRecordingTime: Long = 0L,
|
||||
private var videoQuality: String? = null,
|
||||
private var enableRecording: Boolean = false,
|
||||
private var enableAnr: Boolean = false,
|
||||
private var enableCrash: Boolean = false,
|
||||
var cpuUsageBeforeEventStart: Float? = null,
|
||||
var memoryUsageBeforeEventStart: Float? = null,
|
||||
var storageUsageBeforeEventStart: Float? = null,
|
||||
private var cpuEnableStatus: Boolean = false,
|
||||
private var memoryEnableStatus: Boolean = false,
|
||||
private var metricsApiEnableStatus: Boolean = false,
|
||||
var batteryPercentageBeforeEventStart: Float? = null,
|
||||
private var disableScreenList: List<String>? = null,
|
||||
private var disableModuleList: List<String>? = null,
|
||||
private var snapshotPerSecond: Int = 1,
|
||||
private var enableAlfred: Boolean = false,
|
||||
private var firebaseControlledCruise: Boolean = false,
|
||||
private var disableDialogScreenShot: Boolean = false
|
||||
|
||||
) {
|
||||
|
||||
fun getDeviceId(): String = Settings.System.getString(
|
||||
AlfredManager.applicationContext.contentResolver, Settings.Secure.ANDROID_ID
|
||||
)
|
||||
|
||||
fun setEventStartRecordingTime(setToNext: Boolean? = false) {
|
||||
if (setToNext == true) {
|
||||
this.eventStartRecordingTime = this.nextEventStartRecordingTime
|
||||
} else {
|
||||
this.eventStartRecordingTime = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
fun setNextEventStartRecordingTime() {
|
||||
this.nextEventStartRecordingTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
fun getEventStartRecordingTime(): Long? = this.eventStartRecordingTime
|
||||
|
||||
fun setSessionStartRecordingTime() {
|
||||
this.sessionStartRecordingTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
fun getSessionStartRecordingTime(): Long = this.sessionStartRecordingTime
|
||||
|
||||
fun getAlfredEventId(): String = this.alfredEventId
|
||||
|
||||
fun setAlfredEventId() {
|
||||
this.alfredEventId = UUID.randomUUID().toString().plus(AlfredConstants.ALFRED_EVENT_ID)
|
||||
}
|
||||
|
||||
fun setAlfredSessionId() {
|
||||
this.alfredSessionId = UUID.randomUUID().toString().plus(AlfredConstants.ALFRED_SESSION_ID)
|
||||
}
|
||||
|
||||
fun getAlfredSessionId(): String = this.alfredSessionId
|
||||
|
||||
fun getVideoQuality(): String? = this.videoQuality
|
||||
|
||||
fun setVideoQuality(videoQuality: String?) {
|
||||
this.videoQuality = videoQuality
|
||||
}
|
||||
|
||||
fun getAlfredStatus(): Boolean = this.enableAlfred
|
||||
|
||||
fun setAlfredStatus(enable: Boolean) {
|
||||
this.enableAlfred = enable
|
||||
}
|
||||
|
||||
fun getFirebaseControlledCruise(): Boolean = this.firebaseControlledCruise
|
||||
|
||||
fun setFirebaseControlledCruise(firebaseControlledCruise: Boolean) {
|
||||
this.firebaseControlledCruise = firebaseControlledCruise
|
||||
}
|
||||
|
||||
fun setEnableRecordingStatus(enableRecording: Boolean) {
|
||||
this.enableRecording = enableRecording
|
||||
}
|
||||
|
||||
fun getEnableRecordingStatus(): Boolean = this.enableRecording
|
||||
|
||||
fun getEventTimeStamp(): Long = System.currentTimeMillis()
|
||||
|
||||
fun setUserId(userId: String?) {
|
||||
this.userId = userId
|
||||
}
|
||||
|
||||
fun setLocation(latitude: Double, longitude: Double) {
|
||||
this.latitude = latitude
|
||||
this.longitude = longitude
|
||||
}
|
||||
|
||||
fun getAppVersionCode(): String = appVersionCode
|
||||
|
||||
fun getAppVersionName(): String = appVersionName
|
||||
|
||||
fun getManufacturer(): String? = Build.MANUFACTURER
|
||||
|
||||
fun getDeviceModel(): String? = Build.MODEL
|
||||
|
||||
fun getOs(): String = AlfredConstants.OS_ANDROID
|
||||
|
||||
fun getOsVersion(): String = Build.VERSION.SDK_INT.toString()
|
||||
|
||||
fun getNetworkCarrier(): String? = getCarrierName(AlfredManager.applicationContext)
|
||||
|
||||
fun getNetworkType(): String = getNetworkType(AlfredManager.applicationContext)
|
||||
|
||||
fun getBatteryPercentage(): Float =
|
||||
com.navi.alfred.utils.getBatteryPercentage(AlfredManager.applicationContext)
|
||||
|
||||
fun getUserId(): String? = userId
|
||||
|
||||
fun getEventBatchSize(): Int = eventBatchSize
|
||||
|
||||
fun getLatitude(): Double? = latitude
|
||||
|
||||
fun getEventsDelayInMilliseconds(): Long =
|
||||
AlfredConstants.DEFAULT_EVENT_DELAY_IN_SECONDS.toLong() * 1000
|
||||
|
||||
fun getLongitude(): Double? = longitude
|
||||
|
||||
fun getPostUrl(): String = AlfredConstants.DEFAULT_SEND_EVENT_POST_URL
|
||||
|
||||
fun isProd(): Boolean = TextUtils.equals(flavor, AlfredConstants.PROD)
|
||||
|
||||
fun isQa(): Boolean = TextUtils.equals(flavor, AlfredConstants.QA)
|
||||
|
||||
fun isEnable(): Boolean = enable
|
||||
|
||||
fun setCpuUsageBeforeEventStart() {
|
||||
this.cpuUsageBeforeEventStart = getCpuUsage()
|
||||
}
|
||||
|
||||
fun setMemoryUsageBeforeEventStart() {
|
||||
this.memoryUsageBeforeEventStart = getMemoryUsage()
|
||||
}
|
||||
|
||||
fun setStorageUsageBeforeEventStart() {
|
||||
this.storageUsageBeforeEventStart = getStorageUsage()
|
||||
}
|
||||
|
||||
fun setBatteryPercentageBeforeEventStart() {
|
||||
this.batteryPercentageBeforeEventStart = getBatteryPercentage()
|
||||
}
|
||||
|
||||
fun getCpuUsage(): Float = com.navi.alfred.utils.getCpuUsage()
|
||||
|
||||
fun getMemoryUsage(): Float = com.navi.alfred.utils.getMemoryUsage()
|
||||
|
||||
fun getStorageUsage(): Float {
|
||||
val (totalSize, freeSize) = com.navi.alfred.utils.getStorageUsage(context = AlfredManager.applicationContext)
|
||||
return (totalSize - freeSize).toFloat()
|
||||
}
|
||||
|
||||
fun setDisableScreenList(disableScreenList: List<String>) {
|
||||
this.disableScreenList = disableScreenList
|
||||
}
|
||||
|
||||
fun setDisableModuleList(disableModuleList: List<String>) {
|
||||
this.disableModuleList = disableModuleList
|
||||
}
|
||||
|
||||
fun getDisableModuleList(): List<String>? = this.disableModuleList
|
||||
|
||||
fun setAnrEnableStatus(status: Boolean) {
|
||||
this.enableAnr = status
|
||||
}
|
||||
|
||||
fun getAnrEnableStatus(): Boolean = this.enableAnr
|
||||
|
||||
fun setSnapshotPerSecond(frequency: Int) {
|
||||
this.snapshotPerSecond = frequency
|
||||
}
|
||||
|
||||
fun getSnapshotPerSecond(): Int = this.snapshotPerSecond
|
||||
|
||||
fun setCrashEnableStatus(status: Boolean) {
|
||||
this.enableCrash = status
|
||||
}
|
||||
|
||||
fun getCrashEnableStatus(): Boolean = this.enableCrash
|
||||
|
||||
fun getDisableScreenList(): List<String>? = this.disableScreenList
|
||||
|
||||
fun setCpuEnableStatus(status: Boolean) {
|
||||
this.cpuEnableStatus = status
|
||||
}
|
||||
|
||||
fun setMemoryEnableStatus(status: Boolean) {
|
||||
this.memoryEnableStatus = status
|
||||
}
|
||||
|
||||
fun getCpuEnableStatus(): Boolean = this.cpuEnableStatus
|
||||
|
||||
fun getMemoryEnableStatus(): Boolean = this.memoryEnableStatus
|
||||
|
||||
fun setApiMetricsEnableStatus(status: Boolean) {
|
||||
this.metricsApiEnableStatus = status
|
||||
}
|
||||
fun getMetricsApiEnableStatus(): Boolean = this.metricsApiEnableStatus
|
||||
|
||||
fun setDisableDialogScreenShot(status: Boolean){
|
||||
this.disableDialogScreenShot=status
|
||||
}
|
||||
|
||||
fun getDisableDialogScreenShot(): Boolean {
|
||||
return this.disableDialogScreenShot
|
||||
}
|
||||
|
||||
}
|
||||
890
navi-alfred/src/main/java/com/navi/alfred/AlfredManager.kt
Normal file
890
navi-alfred/src/main/java/com/navi/alfred/AlfredManager.kt
Normal file
@@ -0,0 +1,890 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2022-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.view.KeyEvent.ACTION_DOWN
|
||||
import android.view.KeyEvent.ACTION_UP
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.work.*
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.navi.alfred.utils.ScreenShotStorageHelper
|
||||
import com.navi.alfred.db.AlfredDatabase
|
||||
import com.navi.alfred.db.AlfredDatabaseHelper
|
||||
import com.navi.alfred.db.dao.ApiMetricDao
|
||||
import com.navi.alfred.db.dao.ScreenShotDao
|
||||
import com.navi.alfred.db.dao.ZipDetailsDao
|
||||
import com.navi.alfred.db.model.AnalyticsEvent
|
||||
import com.navi.alfred.db.model.ApiMetricHelper
|
||||
import com.navi.alfred.db.model.ScreenShotPathHelper
|
||||
import com.navi.alfred.db.model.ZipDetailsHelper
|
||||
import com.navi.alfred.dispatcher.AlfredDispatcher
|
||||
import com.navi.alfred.model.*
|
||||
import com.navi.alfred.network.AlfredNetworkRepository
|
||||
import com.navi.alfred.network.AlfredRetrofitProvider
|
||||
import com.navi.alfred.network.model.CruiseResponse
|
||||
import com.navi.alfred.utils.*
|
||||
import com.navi.alfred.utils.AlfredConstants.API_METRICS
|
||||
import com.navi.alfred.utils.AlfredConstants.CODE_API_SUCCESS
|
||||
import com.navi.alfred.utils.AlfredConstants.THIRD_PARTY_MODULE
|
||||
import com.navi.alfred.utils.AlfredConstants.ZIP_FILE_EXTENSION
|
||||
import com.navi.alfred.worker.AddEventTask
|
||||
import com.navi.alfred.worker.AddMetricTask
|
||||
import com.navi.alfred.worker.UploadFileWorker
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.Response
|
||||
import okio.Buffer
|
||||
import java.io.File
|
||||
import java.lang.reflect.Type
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.concurrent.fixedRateTimer
|
||||
|
||||
|
||||
object AlfredManager {
|
||||
|
||||
lateinit var config: AlfredConfig
|
||||
lateinit var applicationContext: Context
|
||||
private var previousTouchEvent: NaviMotionEvent = NaviMotionEvent()
|
||||
private val mutex = Mutex()
|
||||
private val coroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
private val repository = AlfredNetworkRepository()
|
||||
private val completableJob = Job()
|
||||
private var hasUploadFlowStarted: Boolean = false
|
||||
private lateinit var alfredDataBase: AlfredDatabase
|
||||
private lateinit var screenShotDao: ScreenShotDao
|
||||
private lateinit var zipDetailsDao: ZipDetailsDao
|
||||
private lateinit var apiMetricDao: ApiMetricDao
|
||||
private const val imageThreshHoldValue: Int = 60
|
||||
private const val imageSecondThreshHoldValue: Int = 100
|
||||
private var screenShotCaptureDelay: Long = 1000L
|
||||
private var isAppInBackground: Boolean = false
|
||||
private var hasRecordingStarted: Boolean = false
|
||||
var dialog: Dialog? = null
|
||||
private var timer: Timer? = null
|
||||
private var screenShotTimer: Timer? = null
|
||||
private var viewLayoutDelay: Long = 1000
|
||||
private var currentScreenName: String? = null
|
||||
private var currentModuleName: String? = null
|
||||
private var sessionIdForCrash: String? = null
|
||||
private var sessionStartRecordingTimeForCrash: Long? = null
|
||||
private var eventStartRecordingTimeForCrash: Long? = null
|
||||
private lateinit var zipFileDetails: List<ZipDetailsHelper>
|
||||
private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
|
||||
}
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob + exceptionHandler)
|
||||
|
||||
fun init(config: AlfredConfig, context: Context) {
|
||||
this.config = config
|
||||
this.applicationContext = context
|
||||
AlfredRetrofitProvider.init(applicationContext)
|
||||
startSyncEvents(context)
|
||||
alfredDataBase = AlfredDatabaseHelper.getAnalyticsDatabase(applicationContext)
|
||||
this.screenShotDao = alfredDataBase.screenShotDao()
|
||||
this.zipDetailsDao = alfredDataBase.zipDetailsDao()
|
||||
this.apiMetricDao = alfredDataBase.apiMetricDao()
|
||||
}
|
||||
|
||||
fun startRecording(
|
||||
context: Context,
|
||||
view: View,
|
||||
screenName: String? = null,
|
||||
moduleName: String,
|
||||
thirdPartyScreenView: View? = null
|
||||
) {
|
||||
if (config.getEnableRecordingStatus().not()) {
|
||||
return
|
||||
}
|
||||
if (!hasRecordingStarted) {
|
||||
checkDbBeforeStartRecording()
|
||||
config.setAlfredSessionId()
|
||||
config.setSessionStartRecordingTime()
|
||||
config.setEventStartRecordingTime()
|
||||
handleDeviceAttributes()
|
||||
}
|
||||
currentScreenName = screenName
|
||||
currentModuleName = moduleName
|
||||
hasRecordingStarted = true
|
||||
screenShotTimer?.cancel()
|
||||
screenShotTimer = Timer()
|
||||
var bmpForCanvas: Pair<Canvas, Bitmap>? = null
|
||||
var bmpForThirdPartySdkScreen: Bitmap? = null
|
||||
val timerTask: TimerTask = object : TimerTask() {
|
||||
override fun run() {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
if (moduleName == THIRD_PARTY_MODULE) {
|
||||
if (bmpForThirdPartySdkScreen == null) {
|
||||
bmpForThirdPartySdkScreen =
|
||||
thirdPartyScreenView?.let { captureScreenshotOfCustomView(it) }
|
||||
}
|
||||
insertScreenShotPathInDb(
|
||||
this, applicationContext, bmpForThirdPartySdkScreen
|
||||
)
|
||||
} else {
|
||||
if (bmpForCanvas == null) {
|
||||
delay(viewLayoutDelay)
|
||||
bmpForCanvas = createBitmapForView(view)
|
||||
}
|
||||
try {
|
||||
if (dialog != null) {
|
||||
captureBottomSheet(
|
||||
view,
|
||||
context,
|
||||
screenName,
|
||||
bmpForCanvas?.first,
|
||||
rootBmp = bmpForCanvas?.second,
|
||||
moduleName = moduleName
|
||||
)
|
||||
} else {
|
||||
captureScreen(
|
||||
view,
|
||||
context,
|
||||
screenName = screenName,
|
||||
scope = coroutineScope,
|
||||
canvas = bmpForCanvas?.first,
|
||||
bmp = bmpForCanvas?.second,
|
||||
moduleName = moduleName
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
}
|
||||
}
|
||||
handleScreenShot()
|
||||
}
|
||||
}
|
||||
}
|
||||
screenShotCaptureDelay = (1000 / config.getSnapshotPerSecond().toLong())
|
||||
screenShotTimer?.schedule(timerTask, 0, screenShotCaptureDelay)
|
||||
}
|
||||
|
||||
private suspend fun handleScreenShot() {
|
||||
if (ScreenShotStorageHelper.images.size == imageThreshHoldValue || ScreenShotStorageHelper.images.size == imageSecondThreshHoldValue) {
|
||||
toZip(
|
||||
screenShotDao.fetchScreenShotsPath(imageThreshHoldValue)
|
||||
)
|
||||
} else {
|
||||
if (ScreenShotStorageHelper.images.size == 1) {
|
||||
checkToStartZipUpload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkToStartZipUpload() {
|
||||
val zipFileName =
|
||||
config.getAlfredSessionId() + config.getSessionStartRecordingTime().toString()
|
||||
if (checkFileExists(
|
||||
zipFileName, applicationContext
|
||||
) != null
|
||||
) {
|
||||
if (!hasUploadFlowStarted) {
|
||||
hasUploadFlowStarted = true
|
||||
getPreSignedUrl(zipFileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun captureBottomSheet(
|
||||
view: View,
|
||||
context: Context,
|
||||
screenName: String? = null,
|
||||
rootCanvas: Canvas? = null,
|
||||
rootBmp: Bitmap? = null,
|
||||
moduleName: String? = null
|
||||
) {
|
||||
if(config.getDisableDialogScreenShot()){
|
||||
return
|
||||
}
|
||||
val bottomSheetView = dialog?.window?.decorView?.rootView
|
||||
if (bottomSheetView != null) {
|
||||
val bottomSheetCanvasForBitmap = createBitmapForView(bottomSheetView)
|
||||
val bottomSheetScreenShot = captureScreen(
|
||||
bottomSheetView,
|
||||
context,
|
||||
true,
|
||||
screenName,
|
||||
coroutineScope,
|
||||
bottomSheetCanvasForBitmap?.first,
|
||||
bottomSheetCanvasForBitmap?.second,
|
||||
moduleName = moduleName
|
||||
)
|
||||
val backgroundScreenShot =
|
||||
captureScreen(
|
||||
view,
|
||||
context,
|
||||
true,
|
||||
screenName,
|
||||
coroutineScope,
|
||||
rootCanvas,
|
||||
rootBmp,
|
||||
moduleName = moduleName
|
||||
)
|
||||
if (bottomSheetScreenShot != null && backgroundScreenShot != null) {
|
||||
combineScreenshots(
|
||||
backgroundScreenShot,
|
||||
bottomSheetScreenShot,
|
||||
context,
|
||||
screenName,
|
||||
moduleName = moduleName,
|
||||
scope = coroutineScope
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDbBeforeStartRecording() {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
if (screenShotDao.getScreenShotCount() > 0) {
|
||||
clearScreenShot(screenShotDao.fetchAllScreenShotsPath())
|
||||
screenShotDao.deleteAllScreenShot()
|
||||
}
|
||||
if (zipDetailsDao.getZipFilesDetailsCount() > 0) {
|
||||
zipFileDetails = zipDetailsDao.fetchAllZipFilesDetails()
|
||||
zipFileDetails.forEachIndexed { index, DumpZipDetailsHelper ->
|
||||
val zipFileName =
|
||||
DumpZipDetailsHelper.alfredSessionId + DumpZipDetailsHelper.sessionStartRecordingTime.toString()
|
||||
if (checkFileExists(fileName = zipFileName, applicationContext) != null) {
|
||||
getPreSignedUrl(zipFileName, true, index)
|
||||
} else {
|
||||
zipDetailsDao.deleteZipFileDetail(DumpZipDetailsHelper.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun measureInflatedView(view: View) {
|
||||
view.layoutParams = ViewGroup.LayoutParams(1080, 1920)
|
||||
view.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(
|
||||
view.layoutParams.width, View.MeasureSpec.EXACTLY
|
||||
), View.MeasureSpec.makeMeasureSpec(
|
||||
view.layoutParams.height, View.MeasureSpec.EXACTLY
|
||||
)
|
||||
)
|
||||
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
|
||||
}
|
||||
|
||||
suspend fun getCruiseConfig() {
|
||||
val response = repository.cruiseConfig(
|
||||
AlfredConstants.DEFAULT_CRUISE_CONFIG_URL,
|
||||
config.getAppVersionName(),
|
||||
config.getOsVersion(),
|
||||
config.getDeviceId()
|
||||
)
|
||||
if (response.isSuccessful && response.code() == CODE_API_SUCCESS) {
|
||||
response.body()?.let { cruiseResponse ->
|
||||
setCruiseConfig(cruiseResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setCruiseConfig(cruiseResponse: CruiseResponse) {
|
||||
cruiseResponse.data.let { responseList ->
|
||||
if (responseList.isNotEmpty()) {
|
||||
val cruiseConfig = responseList.first()
|
||||
cruiseConfig.source.let { source ->
|
||||
source.enable?.let { sourceConfig ->
|
||||
config.setAlfredStatus(sourceConfig)
|
||||
}
|
||||
source.recordingsConfig?.let { recordingConfig ->
|
||||
recordingConfig.enable?.let { enable ->
|
||||
config.setEnableRecordingStatus(enable)
|
||||
}
|
||||
recordingConfig.videoQuality?.let { videoQuality ->
|
||||
config.setVideoQuality(videoQuality)
|
||||
}
|
||||
recordingConfig.disableAnrRecording?.let { anrStatus ->
|
||||
config.setAnrEnableStatus(!anrStatus)
|
||||
}
|
||||
recordingConfig.disableCrashRecording?.let { crashStatus ->
|
||||
config.setCrashEnableStatus(!crashStatus)
|
||||
}
|
||||
recordingConfig.disableScreens?.let { disableScreen ->
|
||||
config.setDisableScreenList(disableScreen)
|
||||
}
|
||||
recordingConfig.disableModules?.let { disableModuleList ->
|
||||
config.setDisableModuleList(disableModuleList)
|
||||
}
|
||||
recordingConfig.snapshotPerSecond?.let { snapShotPerSecond ->
|
||||
config.setSnapshotPerSecond(snapShotPerSecond)
|
||||
}
|
||||
}
|
||||
source.metricsConfig?.let { metricsConfig ->
|
||||
metricsConfig.disableMemoryMonitoring?.let { memory_monitor_status ->
|
||||
config.setMemoryEnableStatus(!memory_monitor_status)
|
||||
}
|
||||
metricsConfig.disableCpuMonitoring?.let { cpu_monitor_status ->
|
||||
config.setCpuEnableStatus(!cpu_monitor_status)
|
||||
}
|
||||
metricsConfig.disableApiPerformance?.let { api_monitor_status ->
|
||||
config.setApiMetricsEnableStatus(!api_monitor_status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getPreSignedUrl(
|
||||
zipFileName: String, dumpFlow: Boolean = false, index: Int? = null
|
||||
) {
|
||||
config.setAlfredEventId()
|
||||
val bucketKey = config.getAlfredEventId().plus(ZIP_FILE_EXTENSION)
|
||||
val response = repository.getPreSignedUrl(bucketKey)
|
||||
if (response.isSuccessful && response.code() == CODE_API_SUCCESS) {
|
||||
checkFileExists(
|
||||
zipFileName, applicationContext
|
||||
)?.let { file ->
|
||||
response.body()?.data?.let { uploadFile(file, it, dumpFlow, index) }
|
||||
}
|
||||
} else {
|
||||
if (!dumpFlow) {
|
||||
hasUploadFlowStarted = false
|
||||
config.getEventStartRecordingTime()?.let { eventStartRecordingTime ->
|
||||
insertZipDetailsToDbForDumpingLater(
|
||||
config.getAlfredSessionId(),
|
||||
config.getSessionStartRecordingTime(),
|
||||
eventStartRecordingTime
|
||||
)
|
||||
}
|
||||
config.setEventStartRecordingTime(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAndInitiateFileUploadWorkManager() {
|
||||
val screenShotList = screenShotDao.fetchAllScreenShotsPath()
|
||||
screenShotDao.deleteAllScreenShot()
|
||||
val requestData = Data.Builder()
|
||||
.putString(AlfredConstants.ALFRED_SESSION_ID, config.getAlfredSessionId())
|
||||
.putLong(
|
||||
AlfredConstants.SESSION_START_RECORDING_TIME,
|
||||
config.getSessionStartRecordingTime()
|
||||
).putLong(
|
||||
AlfredConstants.EVENT_START_RECORDING_TIME,
|
||||
config.getEventStartRecordingTime() ?: 0L
|
||||
).putString(AlfredConstants.SCREENSHOT_LIST, Gson().toJson(screenShotList))
|
||||
.build()
|
||||
val constraints = Constraints.Builder().setRequiresBatteryNotLow(false)
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED).build()
|
||||
val uniqueWorkName = "YOUR_UNIQUE_WORK_NAME"
|
||||
val uniqueWorkStatus = WorkManager.getInstance(applicationContext)
|
||||
.getWorkInfosForUniqueWork(uniqueWorkName).get()
|
||||
if (uniqueWorkStatus.isNullOrEmpty()) {
|
||||
val uniqueWorkRequest = OneTimeWorkRequestBuilder<UploadFileWorker>()
|
||||
.setConstraints(constraints)
|
||||
.setInputData(requestData)
|
||||
.build()
|
||||
WorkManager.getInstance(applicationContext)
|
||||
.beginUniqueWork(uniqueWorkName, ExistingWorkPolicy.REPLACE, uniqueWorkRequest)
|
||||
.enqueue()
|
||||
} else {
|
||||
val workRequest = OneTimeWorkRequestBuilder<UploadFileWorker>()
|
||||
.setConstraints(constraints)
|
||||
.setInputData(requestData)
|
||||
.build()
|
||||
WorkManager.getInstance(applicationContext)
|
||||
.enqueue(workRequest)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun uploadFile(
|
||||
uploadFile: File, url: String, dumpFlow: Boolean = false, index: Int? = null
|
||||
) {
|
||||
val requestBody = uploadFile.asRequestBody("application/zip".toMediaTypeOrNull())
|
||||
val uploadResponse = repository.uploadZipToS3(
|
||||
url, requestBody
|
||||
)
|
||||
if (uploadResponse.isSuccessful && uploadResponse.code() == CODE_API_SUCCESS) {
|
||||
uploadFile.delete()
|
||||
sendAlfredSessionEvent(dumpFlow, index)
|
||||
} else {
|
||||
if (!dumpFlow) {
|
||||
config.getEventStartRecordingTime()?.let { eventStartRecordingTime ->
|
||||
insertZipDetailsToDbForDumpingLater(
|
||||
config.getAlfredSessionId(),
|
||||
config.getSessionStartRecordingTime(),
|
||||
eventStartRecordingTime
|
||||
)
|
||||
}
|
||||
config.setEventStartRecordingTime(true)
|
||||
}
|
||||
}
|
||||
if (!dumpFlow) {
|
||||
hasUploadFlowStarted = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertZipDetailsToDbForDumpingLater(
|
||||
alfredSessionId: String,
|
||||
sessionStartRecordingTime: Long,
|
||||
eventStartRecordingTime: Long,
|
||||
screenShotList: String? = null
|
||||
) {
|
||||
zipDetailsDao.insert(
|
||||
data = ZipDetailsHelper(
|
||||
alfredSessionId = alfredSessionId,
|
||||
sessionStartRecordingTime = sessionStartRecordingTime,
|
||||
eventStartRecordingTime = eventStartRecordingTime,
|
||||
screenShotList = screenShotList
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun deleteScreenShot() {
|
||||
try {
|
||||
val screenShotPathList: List<ScreenShotPathHelper> =
|
||||
if (screenShotDao.getScreenShotCount() >= imageThreshHoldValue) {
|
||||
screenShotDao.fetchScreenShotsPath(imageThreshHoldValue)
|
||||
} else {
|
||||
screenShotDao.fetchAllScreenShotsPath()
|
||||
}
|
||||
if (clearScreenShot(screenShotPathList)) {
|
||||
try {
|
||||
if (isAppInBackground) {
|
||||
ScreenShotStorageHelper.clearAll()
|
||||
screenShotDao.deleteAllScreenShot()
|
||||
isAppInBackground = false
|
||||
hasRecordingStarted = false
|
||||
} else {
|
||||
val idList = screenShotPathList.map { it.id }
|
||||
screenShotDao.deleteScreenShot(idList)
|
||||
ScreenShotStorageHelper.deleteKItems(idList.size)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendIngestMetric(): Boolean {
|
||||
if (config.getAlfredStatus() && config.getMetricsApiEnableStatus()) {
|
||||
mutex.withLock {
|
||||
val metricEventList = apiMetricDao.fetchApiMetric(config.getEventBatchSize())
|
||||
if (metricEventList.isNotEmpty()) {
|
||||
try {
|
||||
val detailsList = metricEventList.map { it.metric }
|
||||
val listType: Type =
|
||||
object : TypeToken<ArrayList<MetricAttribute?>?>() {}.type
|
||||
val events: ArrayList<MetricAttribute> =
|
||||
Gson().fromJson(detailsList.toString(), listType)
|
||||
if (events.size > 0) {
|
||||
val sessionId = events.first().sessionId
|
||||
val request = EventMetricRequest(
|
||||
baseAttribute = BaseAttribute(sessionId = sessionId),
|
||||
metricsAttribute = events
|
||||
)
|
||||
val response = repository.eventMetric(
|
||||
AlfredConstants.DEFAULT_INGEST_METRIC_URL,
|
||||
metricRequestBody = request
|
||||
)
|
||||
return if (response.isSuccessful && response.code() == CODE_API_SUCCESS) {
|
||||
apiMetricDao.deleteApiMetric(metricEventList.map { it.id })
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun startSyncEvents(applicationContext: Context) {
|
||||
timer = fixedRateTimer(
|
||||
AlfredConstants.TIMER_THREAD_NAME,
|
||||
false,
|
||||
AlfredConstants.DEFAULT_INITIAL_DELAY,
|
||||
config.getEventsDelayInMilliseconds()
|
||||
) {
|
||||
runBlocking {
|
||||
AddMetricTask.resetEventCount()
|
||||
sendIngestMetric()
|
||||
AddEventTask.resetEventCount()
|
||||
sendEventsToServer(applicationContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun sendEventsToServer(
|
||||
applicationContext: Context
|
||||
): Boolean {
|
||||
if (config.getAlfredStatus() && config.getEnableRecordingStatus()) {
|
||||
mutex.withLock {
|
||||
val db = AlfredDatabaseHelper.getAnalyticsDatabase(applicationContext)
|
||||
val analyticsDao = db.analyticsDao()
|
||||
val analyticsEvents = analyticsDao.fetchEvents(config.getEventBatchSize())
|
||||
if (analyticsEvents.isNotEmpty()) {
|
||||
try {
|
||||
val detailsList = analyticsEvents.map { it.details }
|
||||
val listType: Type =
|
||||
object : TypeToken<ArrayList<EventAttribute?>?>() {}.type
|
||||
val events: ArrayList<EventAttribute> =
|
||||
Gson().fromJson(detailsList.toString(), listType)
|
||||
if (events.size > 0) {
|
||||
val sessionId = events.first().sessionId
|
||||
val request = AnalyticsRequest(
|
||||
baseAttribute = BaseAttribute(
|
||||
sessionId = sessionId
|
||||
), events = events
|
||||
)
|
||||
val response = repository.sendEvents(
|
||||
config.getPostUrl(), request
|
||||
)
|
||||
return if (response.isSuccessful && response.code() == CODE_API_SUCCESS) {
|
||||
analyticsDao.deleteEvents(analyticsEvents.map { it.eventId })
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun sendAlfredSessionEvent(dumpFlow: Boolean = false, index: Int? = null) {
|
||||
var request: SessionRequest? = null
|
||||
if (dumpFlow) {
|
||||
var clientTs: Long? = null
|
||||
var sessionTimeStamp: Long? = null
|
||||
var sessionId: String? = null
|
||||
if (index != null) {
|
||||
val zipFileDetail = zipFileDetails[index]
|
||||
clientTs = zipFileDetail.eventStartRecordingTime
|
||||
sessionTimeStamp = zipFileDetail.sessionStartRecordingTime
|
||||
sessionId = zipFileDetail.alfredSessionId
|
||||
} else {
|
||||
clientTs = eventStartRecordingTimeForCrash
|
||||
sessionTimeStamp = sessionStartRecordingTimeForCrash
|
||||
sessionId = sessionIdForCrash
|
||||
}
|
||||
request = SessionRequest(
|
||||
base_attribute = BaseAttribute(
|
||||
sessionId = sessionId,
|
||||
eventTimeStamp = config.getEventTimeStamp(),
|
||||
clientTs = clientTs,
|
||||
sessionTimeStamp = sessionTimeStamp
|
||||
), session_upload_event_attributes = SessionEventAttribute(
|
||||
beginningDeviceAttributes = DeviceAttributes(),
|
||||
endDeviceAttributes = DeviceAttributes()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
request = SessionRequest(
|
||||
base_attribute = BaseAttribute(
|
||||
sessionId = config.getAlfredSessionId(),
|
||||
eventTimeStamp = config.getEventTimeStamp(),
|
||||
clientTs = config.getEventStartRecordingTime()
|
||||
), session_upload_event_attributes = SessionEventAttribute(
|
||||
beginningDeviceAttributes = DeviceAttributes(
|
||||
battery = config.batteryPercentageBeforeEventStart,
|
||||
cpu = config.cpuUsageBeforeEventStart,
|
||||
memory = config.memoryUsageBeforeEventStart,
|
||||
storage = config.storageUsageBeforeEventStart
|
||||
), endDeviceAttributes = DeviceAttributes(
|
||||
battery = config.getBatteryPercentage(),
|
||||
cpu = config.getCpuUsage(),
|
||||
memory = config.getMemoryUsage(),
|
||||
storage = config.getStorageUsage()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
coroutineScope.launch {
|
||||
repository.sendSession(
|
||||
AlfredConstants.DEFAULT_SEND_SESSION_POST_URL, request
|
||||
)
|
||||
if (!dumpFlow) {
|
||||
config.setEventStartRecordingTime(true)
|
||||
handleDeviceAttributes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeviceAttributes() {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
if (config.getAlfredStatus()) {
|
||||
if (config.getCpuEnableStatus()) {
|
||||
config.setCpuUsageBeforeEventStart()
|
||||
}
|
||||
if (config.getMemoryEnableStatus()) {
|
||||
config.setMemoryUsageBeforeEventStart()
|
||||
}
|
||||
}
|
||||
config.setStorageUsageBeforeEventStart()
|
||||
config.setBatteryPercentageBeforeEventStart()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveApiLog(request: Request, response: Response, startTime: Long, endTime: Long) {
|
||||
var bytesSent = 0L
|
||||
var byteReceived = 0L
|
||||
if (request.body != null) {
|
||||
val buffer = Buffer()
|
||||
request.body!!.writeTo(buffer)
|
||||
bytesSent = buffer.size
|
||||
}
|
||||
if (response.body != null) {
|
||||
val responseBody = response.body
|
||||
val source = responseBody?.source()
|
||||
source?.request(Long.MAX_VALUE)
|
||||
val buffer = source?.buffer()
|
||||
byteReceived = buffer?.size ?: 0
|
||||
}
|
||||
var errorMessage: String? = null
|
||||
val duration: Long = endTime - startTime
|
||||
val errorType: String? = null
|
||||
if (response.code != CODE_API_SUCCESS) {
|
||||
errorMessage = response.message
|
||||
}
|
||||
val attributes: HashMap<String, Any> = hashMapOf()
|
||||
attributes[AlfredConstants.URL] = request.url.toString()
|
||||
attributes[AlfredConstants.METHOD] = request.method
|
||||
attributes[AlfredConstants.RESPONSE_CODE] = response.code
|
||||
attributes[AlfredConstants.ERROR_MESSAGE] = errorMessage.toString()
|
||||
attributes[AlfredConstants.ERROR_TYPE] = errorType.toString()
|
||||
attributes[AlfredConstants.START_TIME] = startTime
|
||||
attributes[AlfredConstants.END_TIME] = endTime
|
||||
attributes[AlfredConstants.DURATION_IN_MS] = duration.toDouble()
|
||||
attributes[AlfredConstants.BYTES_RECEIVED] = byteReceived
|
||||
attributes[AlfredConstants.BYTES_SENT] = bytesSent
|
||||
coroutineDispatcher.executor.execute {
|
||||
val appPerformanceEvent = buildAppPerformanceEvent(
|
||||
AlfredConstants.API_METRIC_EVENT_NAME, API_METRICS, attributes
|
||||
)
|
||||
AlfredDispatcher.addTaskToQueue(
|
||||
AddMetricTask(
|
||||
appPerformanceEvent, applicationContext
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopRecording(appBackgroundView: View) {
|
||||
isAppInBackground = true
|
||||
hasRecordingStarted = false
|
||||
screenShotTimer?.cancel()
|
||||
if (config.getFirebaseControlledCruise()) {
|
||||
deleteScreenShot()
|
||||
} else {
|
||||
if (config.getAlfredStatus() && config.getEnableRecordingStatus()) {
|
||||
if (ScreenShotStorageHelper.images.size > 0) {
|
||||
startAnrCrashZipUpload(appBackgroundView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun toZipForWorkManager(
|
||||
imagePathList: List<ScreenShotPathHelper>,
|
||||
zipFileName: String,
|
||||
sessionStartRecordingTime: Long? = null,
|
||||
alfredSessionId: String? = null,
|
||||
eventStartRecordingTime: Long? = null,
|
||||
index: Int? = null
|
||||
) {
|
||||
sessionIdForCrash = alfredSessionId
|
||||
sessionStartRecordingTimeForCrash = sessionStartRecordingTime
|
||||
eventStartRecordingTimeForCrash = eventStartRecordingTime
|
||||
val fileList = ArrayList<String>()
|
||||
imagePathList.forEach { screenShotPathHelper ->
|
||||
screenShotPathHelper.screenShotPath?.let { screenShotPath ->
|
||||
if (!fileList.contains(screenShotPath)) {
|
||||
fileList.add(screenShotPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
val zipFilePath = applicationContext.filesDir.path + "/" + zipFileName
|
||||
if (zip(fileList, zipFilePath) == true) {
|
||||
clearScreenShot(imagePathList)
|
||||
getPreSignedUrl(zipFileName, true, index = index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toZip(
|
||||
imagePathList: List<ScreenShotPathHelper>
|
||||
) {
|
||||
val zipFilePath: String =
|
||||
applicationContext.filesDir.path + "/" + config.getAlfredSessionId() + config.getSessionStartRecordingTime()
|
||||
val fileList = ArrayList<String>()
|
||||
imagePathList.forEach { screenShotPathHelper ->
|
||||
screenShotPathHelper.screenShotPath?.let { screenShotPath ->
|
||||
if (!fileList.contains(screenShotPath)) {
|
||||
fileList.add(screenShotPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zip(fileList, zipFilePath) == true) {
|
||||
deleteScreenShot()
|
||||
config.setNextEventStartRecordingTime()
|
||||
}
|
||||
}
|
||||
|
||||
fun handleTouchEvent(
|
||||
currentTouchEvent: MotionEvent?, screenName: String? = null, moduleName: String? = null
|
||||
) {
|
||||
if (config.getAlfredStatus() && config.getEnableRecordingStatus()) {
|
||||
coroutineDispatcher.executor.execute {
|
||||
if (currentTouchEvent?.action == ACTION_DOWN) {
|
||||
previousTouchEvent.action = currentTouchEvent.action
|
||||
previousTouchEvent.positionX = currentTouchEvent.rawX
|
||||
previousTouchEvent.positionY = currentTouchEvent.rawY
|
||||
}
|
||||
if (currentTouchEvent?.action == ACTION_UP) {
|
||||
val touchEventData = getTouchEvent(currentTouchEvent, previousTouchEvent)
|
||||
val touchEventProperties = touchEventData.second
|
||||
touchEventProperties[AlfredConstants.SCREEN_WIDTH] = getScreenWidth().toString()
|
||||
touchEventProperties[AlfredConstants.SCREEN_HEIGHT] = getScreenHeight().toString()
|
||||
val event = buildEvent(
|
||||
touchEventData.first,
|
||||
touchEventProperties,
|
||||
screenName = screenName,
|
||||
moduleName = moduleName
|
||||
)
|
||||
AlfredDispatcher.addTaskToQueue(AddEventTask(event, applicationContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAnrCrashZipUpload(view: View) {
|
||||
if (ScreenShotStorageHelper.images.size > 0) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
var bmp: Bitmap? = null
|
||||
val screenShotCapture = async {
|
||||
bmp = captureScreenshotOfCustomView(
|
||||
view
|
||||
)
|
||||
}
|
||||
screenShotCapture.await()
|
||||
val insertScreenShotInDb = async {
|
||||
insertScreenShotPathInDb(this, applicationContext, bmp)
|
||||
}
|
||||
insertScreenShotInDb.await()
|
||||
checkAndInitiateFileUploadWorkManager()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleAnrEvent(
|
||||
anrEventProperties: Map<String, String>, anrView: View, screenName: String? = null
|
||||
) {
|
||||
startAnrCrashZipUpload(anrView)
|
||||
coroutineDispatcher.executor.execute {
|
||||
val event = buildEvent(
|
||||
AlfredConstants.ANR_EVENT, anrEventProperties as HashMap<String, String>,
|
||||
screenName = screenName,
|
||||
moduleName = currentModuleName
|
||||
)
|
||||
AlfredDispatcher.addTaskToQueue(AddEventTask(event, this.applicationContext))
|
||||
}
|
||||
}
|
||||
|
||||
fun handleSWWEvent(
|
||||
screenName: String? = null, swwEventProperties: Map<String, String>
|
||||
) {
|
||||
coroutineDispatcher.executor.execute {
|
||||
val event = buildEvent(
|
||||
AlfredConstants.ERROR_LOG,
|
||||
swwEventProperties as HashMap<String, String>,
|
||||
screenName = screenName,
|
||||
moduleName = currentModuleName
|
||||
)
|
||||
AlfredDispatcher.addTaskToQueue(AddEventTask(event, applicationContext))
|
||||
}
|
||||
}
|
||||
|
||||
fun handleCrashEvent(
|
||||
crashEventProperties: Map<String, String>,
|
||||
crashView: View,
|
||||
screenName: String? = null
|
||||
) {
|
||||
startAnrCrashZipUpload(crashView)
|
||||
coroutineDispatcher.executor.execute {
|
||||
val event = buildEvent(
|
||||
AlfredConstants.CRASH_ANALYTICS_EVENT,
|
||||
crashEventProperties as HashMap<String, String>,
|
||||
screenName = screenName,
|
||||
moduleName = currentModuleName
|
||||
)
|
||||
AlfredDispatcher.addTaskToQueue(AddEventTask(event, applicationContext))
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun buildEvent(
|
||||
eventName: String,
|
||||
properties: HashMap<String, String>? = null,
|
||||
screenName: String? = null,
|
||||
moduleName: String? = null
|
||||
): AnalyticsEvent {
|
||||
val timeStamp = System.currentTimeMillis()
|
||||
val eventData = EventAttribute(
|
||||
eventName = eventName,
|
||||
eventType = eventName,
|
||||
eventTimestamp = timeStamp,
|
||||
attributes = properties,
|
||||
sessionId = config.getAlfredSessionId(),
|
||||
screenName = screenName,
|
||||
moduleName = moduleName
|
||||
)
|
||||
return AnalyticsEvent(timeStamp, Gson().toJson(eventData))
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun buildAppPerformanceEvent(
|
||||
eventName: String, eventType: String, attribute: HashMap<String, Any>? = null
|
||||
): ApiMetricHelper {
|
||||
val timeStamp = System.currentTimeMillis()
|
||||
val metricData = MetricAttribute(
|
||||
eventId = config.getAlfredEventId(),
|
||||
eventName = eventName,
|
||||
eventType = eventType,
|
||||
sessionId = config.getAlfredSessionId(),
|
||||
attributes = attribute,
|
||||
screenName = currentScreenName,
|
||||
moduleName = currentModuleName
|
||||
)
|
||||
return ApiMetricHelper(timeStamp, Gson().toJson(metricData))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2022-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.db
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.navi.alfred.db.dao.AnalyticsDAO
|
||||
import com.navi.alfred.db.dao.ApiMetricDao
|
||||
import com.navi.alfred.db.dao.ScreenShotDao
|
||||
import com.navi.alfred.db.dao.ZipDetailsDao
|
||||
import com.navi.alfred.db.model.AnalyticsEvent
|
||||
import com.navi.alfred.db.model.ApiMetricHelper
|
||||
import com.navi.alfred.db.model.ScreenShotPathHelper
|
||||
import com.navi.alfred.db.model.ZipDetailsHelper
|
||||
|
||||
@Database(
|
||||
entities = [AnalyticsEvent::class, ScreenShotPathHelper::class, ZipDetailsHelper::class, ApiMetricHelper::class],
|
||||
version = 2,
|
||||
exportSchema = true
|
||||
)
|
||||
|
||||
abstract class AlfredDatabase : RoomDatabase() {
|
||||
abstract fun analyticsDao(): AnalyticsDAO
|
||||
abstract fun screenShotDao(): ScreenShotDao
|
||||
abstract fun zipDetailsDao(): ZipDetailsDao
|
||||
abstract fun apiMetricDao(): ApiMetricDao
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.db
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.navi.alfred.utils.AlfredConstants.EVENT_DB_NAME
|
||||
|
||||
object AlfredDatabaseHelper {
|
||||
@Volatile private var INSTANCE: AlfredDatabase? = null
|
||||
|
||||
fun getAnalyticsDatabase(context: Context): AlfredDatabase {
|
||||
return INSTANCE
|
||||
?: synchronized(this) {
|
||||
if (INSTANCE != null) {
|
||||
INSTANCE
|
||||
}
|
||||
val instance =
|
||||
Room.databaseBuilder(context, AlfredDatabase::class.java, EVENT_DB_NAME).fallbackToDestructiveMigration()
|
||||
.build()
|
||||
INSTANCE = instance
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.navi.alfred.db.model.AnalyticsEvent
|
||||
import com.navi.alfred.db.model.ApiMetricHelper
|
||||
import com.navi.alfred.db.model.ScreenShotPathHelper
|
||||
import com.navi.alfred.db.model.ZipDetailsHelper
|
||||
|
||||
@Dao
|
||||
interface AnalyticsDAO {
|
||||
@Insert
|
||||
fun insertEvent(event: AnalyticsEvent)
|
||||
|
||||
@Query("SELECT * FROM AnalyticsEvent ORDER BY time ASC LIMIT :thresholdValue")
|
||||
fun fetchEvents(thresholdValue: Int): List<AnalyticsEvent>
|
||||
|
||||
@Query("DELETE FROM AnalyticsEvent WHERE eventId IN (:idList)")
|
||||
fun deleteEvents(idList: List<Int>)
|
||||
|
||||
@Query("SELECT count(*) FROM AnalyticsEvent")
|
||||
fun getEventCount(): Int
|
||||
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface ScreenShotDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertScreenShotPath(event: ScreenShotPathHelper)
|
||||
|
||||
@Query("SELECT * FROM ScreenShotPathHelper ORDER BY time ASC LIMIT :thresholdValue")
|
||||
fun fetchScreenShotsPath(thresholdValue: Int): List<ScreenShotPathHelper>
|
||||
|
||||
@Query("SELECT * FROM ScreenShotPathHelper")
|
||||
fun fetchAllScreenShotsPath(): List<ScreenShotPathHelper>
|
||||
|
||||
@Query("DELETE FROM ScreenShotPathHelper WHERE id IN (:idList)")
|
||||
fun deleteScreenShot(idList: List<Int>)
|
||||
|
||||
@Query("DELETE FROM ScreenShotPathHelper")
|
||||
fun deleteAllScreenShot()
|
||||
|
||||
@Query("SELECT count(*) FROM ScreenShotPathHelper")
|
||||
fun getScreenShotCount(): Int
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface ZipDetailsDao{
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(data: ZipDetailsHelper)
|
||||
|
||||
@Query("SELECT count(*) FROM ZipDetailsHelper")
|
||||
fun getZipFilesDetailsCount(): Int
|
||||
|
||||
@Query("SELECT * FROM ZipDetailsHelper ORDER BY sessionStartRecordingTime")
|
||||
fun fetchAllZipFilesDetails(): List<ZipDetailsHelper>
|
||||
|
||||
@Query("DELETE FROM ZipDetailsHelper WHERE id = :id")
|
||||
fun deleteZipFileDetail(id: Int)
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface ApiMetricDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(data: ApiMetricHelper)
|
||||
|
||||
@Query("SELECT count(*) FROM ApiMetricHelper")
|
||||
fun getApiMetricCount(): Int
|
||||
|
||||
@Query("SELECT * FROM ApiMetricHelper ORDER BY time ASC LIMIT :thresholdValue")
|
||||
fun fetchApiMetric(thresholdValue: Int): List<ApiMetricHelper>
|
||||
|
||||
@Query("DELETE FROM ApiMetricHelper WHERE id IN (:idList)")
|
||||
fun deleteApiMetric(idList: List<Int>)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2022-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.db.model
|
||||
|
||||
import androidx.room.*
|
||||
import com.google.gson.Gson
|
||||
import com.navi.alfred.model.DeviceAttributes
|
||||
|
||||
@Entity
|
||||
data class AnalyticsEvent(
|
||||
@ColumnInfo(name = "time") val time: Long?,
|
||||
@ColumnInfo(name = "details") val details: String?
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var eventId: Int = 0
|
||||
}
|
||||
|
||||
@Entity
|
||||
data class ScreenShotPathHelper(
|
||||
@ColumnInfo(name = "time") val time: Long?,
|
||||
@ColumnInfo(name = "screenShotPath") val screenShotPath: String?
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Int = 0
|
||||
}
|
||||
|
||||
@Entity
|
||||
@TypeConverters(Converter::class)
|
||||
data class ZipDetailsHelper(
|
||||
@ColumnInfo(name = "alfredSessionId") val alfredSessionId: String,
|
||||
@ColumnInfo(name = "sessionStartRecordingTime") val sessionStartRecordingTime: Long,
|
||||
@ColumnInfo(name = "eventStartRecordingTime") val eventStartRecordingTime: Long,
|
||||
@ColumnInfo(name = "screenShotList") val screenShotList: String?
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Int = 0
|
||||
}
|
||||
|
||||
@Entity
|
||||
data class ApiMetricHelper(
|
||||
@ColumnInfo(name = "time") val time: Long?,
|
||||
@ColumnInfo(name = "metric") val metric: String?
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Int = 0
|
||||
}
|
||||
|
||||
class Converter {
|
||||
|
||||
@TypeConverter
|
||||
fun fromDeviceAttributes(data: DeviceAttributes): String {
|
||||
return Gson().toJson(data)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun toDeviceAttributes(data: String): DeviceAttributes {
|
||||
return Gson().fromJson(data, DeviceAttributes::class.java)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.navi.alfred.deserializer
|
||||
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.navi.alfred.model.AnalyticsRequest
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class AnalyticsDataDeserializer : JsonDeserializer<AnalyticsRequest> {
|
||||
override fun deserialize(
|
||||
json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?
|
||||
): AnalyticsRequest? {
|
||||
json?.let {
|
||||
return context?.deserialize(json, AnalyticsRequest::class.java)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.navi.alfred.deserializer
|
||||
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.navi.alfred.model.EventMetricRequest
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class MetricsDataDeserializer : JsonDeserializer<EventMetricRequest> {
|
||||
override fun deserialize(
|
||||
json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?
|
||||
): EventMetricRequest? {
|
||||
json?.let {
|
||||
return context?.deserialize(json, EventMetricRequest::class.java)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.navi.alfred.deserializer
|
||||
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.navi.alfred.model.EventMetricRequest
|
||||
import com.navi.alfred.model.SessionRequest
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class SessionDataDeserializer : JsonDeserializer<SessionRequest> {
|
||||
override fun deserialize(
|
||||
json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?
|
||||
): SessionRequest? {
|
||||
json?.let {
|
||||
return context?.deserialize(json, EventMetricRequest::class.java)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2019-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.dispatcher
|
||||
|
||||
import com.navi.alfred.worker.AnalyticsTask
|
||||
import com.navi.alfred.worker.AnalyticsTaskProcessor
|
||||
|
||||
/**
|
||||
* handles task of putting events into DB and syncing data with backend
|
||||
*/
|
||||
object AlfredDispatcher {
|
||||
private val taskProcessor: AnalyticsTaskProcessor = AnalyticsTaskProcessor()
|
||||
fun addTaskToQueue(task: AnalyticsTask) {
|
||||
taskProcessor.addTask(task)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes task in a different thread than the Queue
|
||||
*/
|
||||
fun executeInNewThread(task: AnalyticsTask) {
|
||||
taskProcessor.executeInNewThread(task)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.navi.alfred.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class EventMetricRequest(
|
||||
@SerializedName("base_attributes") val baseAttribute: BaseAttribute,
|
||||
@SerializedName("metrics_attributes") val metricsAttribute: List<MetricAttribute>
|
||||
)
|
||||
|
||||
data class MetricAttribute(
|
||||
@SerializedName("event_id") val eventId: String? = null,
|
||||
@SerializedName("event_name") val eventName: String? = null,
|
||||
@SerializedName("session_id") val sessionId: String? = null,
|
||||
@SerializedName("event_type") val eventType: String? = null,
|
||||
@SerializedName("screen_name") val screenName: String? = null,
|
||||
@SerializedName("module_name") val moduleName: String? = null,
|
||||
@SerializedName("attributes") val attributes: HashMap<String, Any>? = null
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.model
|
||||
|
||||
data class NaviMotionEvent(
|
||||
var action: Int? = null,
|
||||
var positionX: Float? = null,
|
||||
var positionY: Float? = null
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.navi.alfred.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.navi.alfred.AlfredManager
|
||||
|
||||
data class AnalyticsRequest(
|
||||
@SerializedName("base_attributes") val baseAttribute: BaseAttribute? = null,
|
||||
@SerializedName("events") val events: List<EventAttribute>? = null
|
||||
)
|
||||
|
||||
data class EventAttribute(
|
||||
@SerializedName("eventId") val eventId: String? = null,
|
||||
@SerializedName("screen_name") val screenName: String? = null,
|
||||
@SerializedName("event_name") val eventName: String? = null,
|
||||
@SerializedName("event_timestamp") val eventTimestamp: Long? = null,
|
||||
@SerializedName("event_type") val eventType: String? = null,
|
||||
@SerializedName("attributes") val attributes: Map<String, String>? = null,
|
||||
@SerializedName("session_id") val sessionId: String? = null,
|
||||
@SerializedName("module_name") val moduleName: String? = null
|
||||
)
|
||||
|
||||
data class SessionRequest(
|
||||
@SerializedName("base_attributes") val base_attribute: BaseAttribute,
|
||||
@SerializedName("session_upload_event_attributes") val session_upload_event_attributes: SessionEventAttribute
|
||||
)
|
||||
|
||||
data class BaseAttribute(
|
||||
@SerializedName("app_version_code") val appVersionCode: String = AlfredManager.config.getAppVersionCode(),
|
||||
@SerializedName("app_version_name") val appVersionName: String? = AlfredManager.config.getAppVersionName(),
|
||||
@SerializedName("client_ts") val clientTs: Long? = AlfredManager.config.getEventStartRecordingTime(),
|
||||
@SerializedName("device_id") val deviceId: String? = AlfredManager.config.getDeviceId(),
|
||||
@SerializedName("device_model") val deviceModel: String? = AlfredManager.config.getDeviceModel(),
|
||||
@SerializedName("device_manufacturer") val deviceManufacturer: String? = AlfredManager.config.getManufacturer(),
|
||||
@SerializedName("app_os") val appOs: String? = AlfredManager.config.getOs(),
|
||||
@SerializedName("os_version") val osVersion: String? = AlfredManager.config.getOsVersion(),
|
||||
@SerializedName("latitude") val latitude: Double? = AlfredManager.config.getLatitude(),
|
||||
@SerializedName("longitude") val longitude: Double? = AlfredManager.config.getLongitude(),
|
||||
@SerializedName("customer_id") val customerId: String? = AlfredManager.config.getUserId(),
|
||||
@SerializedName("up_time") val upTime: Long? = null,
|
||||
@SerializedName("carrier_name") val carrierName: String? = AlfredManager.config.getNetworkCarrier(),
|
||||
@SerializedName("parent_session_id") val parentSessionId: String? = null,
|
||||
@SerializedName("event_timestamp") val eventTimeStamp: Long? = AlfredManager.config.getEventTimeStamp(),
|
||||
@SerializedName("session_id") val sessionId: String? = AlfredManager.config.getAlfredSessionId(),
|
||||
@SerializedName("session_time_stamp") val sessionTimeStamp: Long? = AlfredManager.config.getSessionStartRecordingTime(),
|
||||
)
|
||||
|
||||
data class SessionEventAttribute(
|
||||
@SerializedName("event_id") val eventId: String? = AlfredManager.config.getAlfredEventId(),
|
||||
@SerializedName("beginning_device_attributes") val beginningDeviceAttributes: DeviceAttributes? = null,
|
||||
@SerializedName("end_device_attributes") val endDeviceAttributes: DeviceAttributes? = null
|
||||
)
|
||||
|
||||
data class DeviceAttributes(
|
||||
@SerializedName("battery") val battery: Float? = null,
|
||||
@SerializedName("cpu") val cpu: Float? = null,
|
||||
@SerializedName("storage") val storage: Float? = null,
|
||||
@SerializedName("memory") val memory: Float? = null,
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.network
|
||||
|
||||
import com.navi.alfred.model.EventMetricRequest
|
||||
import com.navi.alfred.model.SessionRequest
|
||||
import com.navi.alfred.network.model.CruiseResponse
|
||||
import com.navi.alfred.network.model.PreSignedUrlResponse
|
||||
import com.navi.alfred.utils.AlfredConstants.ALFRED
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.Response
|
||||
|
||||
class AlfredNetworkRepository {
|
||||
suspend fun sendEvents(
|
||||
url: String, analyticsRequest: com.navi.alfred.model.AnalyticsRequest
|
||||
): Response<Unit> {
|
||||
return AlfredRetrofitProvider.getApiService()
|
||||
.sendEvents(url, "application/json", ALFRED, analyticsRequest)
|
||||
}
|
||||
|
||||
suspend fun getPreSignedUrl(sessionId: String): Response<PreSignedUrlResponse> {
|
||||
return AlfredRetrofitProvider.getApiService().getPreSignedUrl(sessionId, ALFRED)
|
||||
}
|
||||
|
||||
suspend fun sendSession(
|
||||
url: String,
|
||||
sessionRequest: SessionRequest
|
||||
): Response<Unit> {
|
||||
return AlfredRetrofitProvider.getApiService()
|
||||
.sendSession(url, "application/json", ALFRED, sessionRequest)
|
||||
}
|
||||
|
||||
suspend fun uploadZipToS3(
|
||||
preSignedUrl: String, request: RequestBody
|
||||
): Response<Unit> {
|
||||
return AlfredRetrofitProvider.getApiService().uploadToS3(preSignedUrl, request, ALFRED)
|
||||
}
|
||||
|
||||
suspend fun eventMetric(
|
||||
url: String, metricRequestBody: EventMetricRequest
|
||||
): Response<Unit> {
|
||||
return AlfredRetrofitProvider.getApiService()
|
||||
.sendMetric(url, ALFRED, "application/json", metricRequestBody)
|
||||
}
|
||||
|
||||
suspend fun cruiseConfig(
|
||||
url: String, appVersionName: String, osVersionCode: String, deviceId: String
|
||||
): Response<CruiseResponse> {
|
||||
return AlfredRetrofitProvider.getApiService().getCruiseConfig(
|
||||
url,
|
||||
ALFRED,
|
||||
contentType = "application/json",
|
||||
appVersionName = appVersionName,
|
||||
osVersion = osVersionCode,
|
||||
deviceId = deviceId
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.network
|
||||
|
||||
import android.content.Context
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.navi.alfred.AlfredManager
|
||||
import com.navi.alfred.BuildConfig
|
||||
import com.navi.alfred.deserializer.AnalyticsDataDeserializer
|
||||
import com.navi.alfred.deserializer.MetricsDataDeserializer
|
||||
import com.navi.alfred.deserializer.SessionDataDeserializer
|
||||
import com.navi.alfred.model.EventMetricRequest
|
||||
import com.navi.alfred.model.SessionRequest
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object AlfredRetrofitProvider {
|
||||
private const val BASE_URL_DEBUG = "https://dev-sa.navi.com/"
|
||||
private const val BASE_URL_PROD = "https://alfred-ingester.prod.navi-sa.in/"
|
||||
private const val BASE_URL_QA = "https://qa-alfred-ingester.np.navi-sa.in/"
|
||||
private lateinit var apiService: AlfredRetrofitService
|
||||
private lateinit var okHttpClient: OkHttpClient
|
||||
|
||||
fun init(context: Context) {
|
||||
okHttpClient = OkHttpClient.Builder().apply {
|
||||
connectTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS)
|
||||
if (BuildConfig.DEBUG) {
|
||||
addInterceptor(loggingInterceptor())
|
||||
addInterceptor(
|
||||
ChuckerInterceptor.Builder(context).collector(ChuckerCollector(context))
|
||||
.alwaysReadResponseBody(false).build()
|
||||
)
|
||||
}
|
||||
}.build()
|
||||
apiService = getRetrofit().create(AlfredRetrofitService::class.java)
|
||||
}
|
||||
|
||||
private fun getRetrofit(): Retrofit {
|
||||
val baseUrl = if (AlfredManager.config.isQa()) {
|
||||
BASE_URL_QA
|
||||
} else if (AlfredManager.config.isProd()) {
|
||||
BASE_URL_PROD
|
||||
} else {
|
||||
BASE_URL_DEBUG
|
||||
}
|
||||
val providesDeserializer = GsonBuilder().registerTypeAdapter(
|
||||
com.navi.alfred.model.AnalyticsRequest::class.java,
|
||||
AnalyticsDataDeserializer()
|
||||
).registerTypeAdapter(SessionRequest::class.java, SessionDataDeserializer())
|
||||
.registerTypeAdapter(EventMetricRequest::class.java, MetricsDataDeserializer()).create()
|
||||
return Retrofit.Builder().baseUrl(baseUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create(providesDeserializer))
|
||||
.client(okHttpClient).build()
|
||||
}
|
||||
|
||||
fun getApiService(): AlfredRetrofitService {
|
||||
return apiService
|
||||
}
|
||||
|
||||
private fun loggingInterceptor() = HttpLoggingInterceptor().apply {
|
||||
setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.network
|
||||
|
||||
import com.navi.alfred.model.EventMetricRequest
|
||||
import com.navi.alfred.model.SessionRequest
|
||||
import com.navi.alfred.network.model.CruiseResponse
|
||||
import com.navi.alfred.network.model.PreSignedUrlResponse
|
||||
import com.navi.alfred.utils.AlfredConstants.APP_VERSION_NAME
|
||||
import com.navi.alfred.utils.AlfredConstants.CONTENT_TYPE
|
||||
import com.navi.alfred.utils.AlfredConstants.DEVICE_ID
|
||||
import com.navi.alfred.utils.AlfredConstants.OS_VERSION
|
||||
import com.navi.alfred.utils.AlfredConstants.SESSION_ID
|
||||
import com.navi.alfred.utils.AlfredConstants.X_TARGET
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.*
|
||||
|
||||
interface AlfredRetrofitService {
|
||||
@POST
|
||||
suspend fun sendEvents(
|
||||
@Url url: String,
|
||||
@Header(CONTENT_TYPE) contentType: String,
|
||||
@Header(X_TARGET) target: String,
|
||||
@Body analyticsRequest: com.navi.alfred.model.AnalyticsRequest
|
||||
): Response<Unit>
|
||||
|
||||
@GET("ingest/session/pre-sign/{sessionId}")
|
||||
suspend fun getPreSignedUrl(
|
||||
@Path(SESSION_ID) sessionId: String,
|
||||
@Header(X_TARGET) target: String,
|
||||
): Response<PreSignedUrlResponse>
|
||||
|
||||
@PUT
|
||||
suspend fun uploadToS3(
|
||||
@Url url: String,
|
||||
@Body file: RequestBody,
|
||||
@Header(X_TARGET) target: String,
|
||||
): Response<Unit>
|
||||
|
||||
@POST
|
||||
suspend fun sendSession(
|
||||
@Url url: String,
|
||||
@Header(CONTENT_TYPE) contentType: String,
|
||||
@Header(X_TARGET) target: String,
|
||||
@Body sessionRequest: SessionRequest
|
||||
): Response<Unit>
|
||||
|
||||
@POST
|
||||
suspend fun sendMetric(
|
||||
@Url url: String,
|
||||
@Header(X_TARGET) target: String,
|
||||
@Header(CONTENT_TYPE) contentType: String,
|
||||
@Body metricRequestBody: EventMetricRequest
|
||||
): Response<Unit>
|
||||
|
||||
@GET
|
||||
suspend fun getCruiseConfig(
|
||||
@Url url: String,
|
||||
@Header(X_TARGET) target: String,
|
||||
@Header(APP_VERSION_NAME) appVersionName: String,
|
||||
@Header(OS_VERSION) osVersion: String,
|
||||
@Header(DEVICE_ID) deviceId: String,
|
||||
@Header(CONTENT_TYPE) contentType: String,
|
||||
): Response<CruiseResponse>
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.network.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PreSignedUrlResponse(
|
||||
@SerializedName("data") val data: String? = null,
|
||||
@SerializedName("status") val status: Int? = null
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.navi.alfred.network.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CruiseResponse(
|
||||
@SerializedName("data") val data: List<CruiseConfig>, @SerializedName("status") val status: Int
|
||||
)
|
||||
|
||||
data class CruiseConfig(
|
||||
@SerializedName("_id") val id: String, @SerializedName("_source") val source: Source
|
||||
)
|
||||
|
||||
data class Source(
|
||||
@SerializedName("enable") val enable: Boolean? = false,
|
||||
@SerializedName("metrics_config") val metricsConfig: MetricsConfig? = null,
|
||||
@SerializedName("os_config") val osConfig: OsConfig? = null,
|
||||
@SerializedName("recordings_config") val recordingsConfig: RecordingsConfig? = null,
|
||||
@SerializedName("type") val type: String? = null
|
||||
)
|
||||
|
||||
data class RecordingsConfig(
|
||||
@SerializedName("disable_anr_recording") val disableAnrRecording: Boolean? = null,
|
||||
@SerializedName("disable_crash_recording") val disableCrashRecording: Boolean? = null,
|
||||
@SerializedName("enable") val enable: Boolean? = null,
|
||||
@SerializedName("snapshot_per_second") val snapshotPerSecond: Int? = null,
|
||||
@SerializedName("video_quality") val videoQuality: String? = null,
|
||||
@SerializedName("video_recording_policy") val videoRecordingPolicy: String? = null,
|
||||
@SerializedName("disable_screens") val disableScreens: List<String>? = null,
|
||||
@SerializedName("disable_modules") val disableModules: List<String>? = null
|
||||
)
|
||||
|
||||
data class OsConfig(
|
||||
@SerializedName("app_version") val appVersion: String? = null,
|
||||
@SerializedName("app_version_code") val appVersionCode: String? = null,
|
||||
@SerializedName("os_version") val osVersion: String? = null,
|
||||
)
|
||||
|
||||
data class MetricsConfig(
|
||||
@SerializedName("disable_api_performance") val disableApiPerformance: Boolean? = null,
|
||||
@SerializedName("disable_cpu_monitoring") val disableCpuMonitoring: Boolean? = null,
|
||||
@SerializedName("disable_memory_monitoring") val disableMemoryMonitoring: Boolean? = null,
|
||||
@SerializedName("disable_remote_logging") val disableRemoteLogging: Boolean? = null,
|
||||
@SerializedName("enable") val enable: Boolean? = null
|
||||
)
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.utils
|
||||
|
||||
object AlfredConstants {
|
||||
const val CODE_API_SUCCESS = 200
|
||||
const val UNDERSCORE = "_"
|
||||
const val ADD_EVENT_TASK = "AddEventTask"
|
||||
const val ADD_API_METRIC_TASK = "AddMetricTask"
|
||||
const val OS_ANDROID = "Android"
|
||||
const val DEFAULT_SEND_EVENT_POST_URL = "/ingest/event"
|
||||
const val DEFAULT_SEND_SESSION_POST_URL = "/ingest/session"
|
||||
const val DEFAULT_CRUISE_CONFIG_URL = "/cruise"
|
||||
const val DEFAULT_INGEST_METRIC_URL = "/ingest/metrics"
|
||||
const val TIMER_THREAD_NAME = "SyncTimer"
|
||||
const val DEFAULT_INITIAL_DELAY = 5000L
|
||||
const val DEFAULT_EVENT_DELAY_IN_SECONDS = 30
|
||||
const val PROD = "prod"
|
||||
const val QA = "qa"
|
||||
const val TOUCH_EVENT = "TOUCH_EVENT"
|
||||
const val SCROLL_EVENT = "SCROLL_EVENT"
|
||||
const val SCREEN_WIDTH = "SCREEN_WIDTH"
|
||||
const val SCREEN_HEIGHT = "SCREEN_HEIGHT"
|
||||
const val START_X = "START_X"
|
||||
const val START_Y = "START_Y"
|
||||
const val END_X = "END_X"
|
||||
const val END_Y = "END_Y"
|
||||
const val ANR_EVENT = "ANR_EVENT"
|
||||
const val CRASH_ANALYTICS_EVENT = "CRASH_ANALYTICS_EVENT"
|
||||
const val ERROR_LOG = "ERROR_LOG"
|
||||
const val ALFRED_EVENT_ID = "ALFRED_EVENT_ID"
|
||||
const val ALFRED_SESSION_ID = "ALFRED_SESSION_ID"
|
||||
const val API_METRIC_EVENT_NAME = "API_METRIC_EVENT"
|
||||
const val API_METRICS = "API_METRICS"
|
||||
const val SESSION_START_RECORDING_TIME = "SESSION_START_RECORDING_TIME"
|
||||
const val EVENT_START_RECORDING_TIME = "EVENT_START_RECORDING_TIME"
|
||||
const val URL = "url"
|
||||
const val METHOD = "method"
|
||||
const val RESPONSE_CODE = "response_code"
|
||||
const val ERROR_MESSAGE = "error_message"
|
||||
const val ERROR_TYPE = "error_type"
|
||||
const val START_TIME = "start_time"
|
||||
const val END_TIME = "end_time"
|
||||
const val DURATION_IN_MS = "duration_in_ms"
|
||||
const val BYTES_RECEIVED = "bytes_received"
|
||||
const val BYTES_SENT = "bytes_sent"
|
||||
const val ALFRED = "ALFRED"
|
||||
const val CONTENT_TYPE = "Content-Type"
|
||||
const val X_TARGET = "X-Target"
|
||||
const val APP_VERSION_NAME = "appVersionName"
|
||||
const val OS_VERSION = "osVersion"
|
||||
const val DEVICE_ID = "deviceId"
|
||||
const val SESSION_ID = "sessionId"
|
||||
const val THIRD_PARTY_SCREEN = "THIRD_PARTY_SCREEN"
|
||||
const val THIRD_PARTY_MODULE = "THIRD_PARTY_MODULE"
|
||||
const val INSURANCE_MODULE = "INSURANCE_MODULE"
|
||||
const val CHAT_MODULE = "CHAT_MODULE"
|
||||
const val SCREENSHOT_LIST = "SCREENSHOT_LIST"
|
||||
const val REASON = "REASON"
|
||||
const val CODE = "CODE"
|
||||
const val STATUS_CODE = "STATUS_CODE"
|
||||
const val EVENT_DB_NAME = "navi-analytics"
|
||||
const val ZIP_FILE_EXTENSION = ".zip"
|
||||
const val IMAGE_FILE_EXTENSION = ".jpeg"
|
||||
const val SYNC_EVENT_TASK = "SyncEventTask"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.navi.alfred.utils
|
||||
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
|
||||
object AlfredHelper {
|
||||
|
||||
fun recordException(e: Throwable) {
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2022-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.utils
|
||||
|
||||
// stores screenshot path
|
||||
object ScreenShotStorageHelper {
|
||||
var images = ArrayList<String>()
|
||||
|
||||
fun addImage(image: String) {
|
||||
images.add(image)
|
||||
}
|
||||
|
||||
fun clearAll() {
|
||||
images.clear()
|
||||
}
|
||||
|
||||
fun deleteKItems(k: Int) {
|
||||
if (k <= images.size) {
|
||||
images = ArrayList(images.subList(k, images.size))
|
||||
} else {
|
||||
images.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
384
navi-alfred/src/main/java/com/navi/alfred/utils/Utils.kt
Normal file
384
navi-alfred/src/main/java/com/navi/alfred/utils/Utils.kt
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2022-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Bitmap.CompressFormat
|
||||
import android.graphics.Canvas
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.BatteryManager
|
||||
import android.os.StatFs
|
||||
import android.telephony.TelephonyManager
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import com.navi.alfred.utils.ScreenShotStorageHelper
|
||||
import com.navi.alfred.AlfredManager
|
||||
import com.navi.alfred.db.AlfredDatabaseHelper
|
||||
import com.navi.alfred.db.model.ScreenShotPathHelper
|
||||
import com.navi.alfred.model.NaviMotionEvent
|
||||
import com.navi.alfred.utils.AlfredConstants.IMAGE_FILE_EXTENSION
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.*
|
||||
import java.lang.Math.subtractExact
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
suspend fun captureScreen(
|
||||
v: View,
|
||||
context: Context,
|
||||
bottomSheetFlow: Boolean? = false,
|
||||
screenName: String? = null,
|
||||
scope: CoroutineScope,
|
||||
canvas: Canvas? = null,
|
||||
bmp: Bitmap? = null,
|
||||
moduleName: String? = null
|
||||
): Bitmap? {
|
||||
if (isScreenDisabled(screenName, moduleName)) {
|
||||
return null
|
||||
}
|
||||
if (canvas != null && bmp != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
try {
|
||||
v.draw(canvas)
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
}
|
||||
}
|
||||
insertScreenShotPathInDb(scope, context, bmp, bottomSheetFlow)
|
||||
}
|
||||
return bmp
|
||||
}
|
||||
|
||||
|
||||
fun combineScreenshots(
|
||||
backgroundScreenshot: Bitmap?,
|
||||
bottomSheetScreenshot: Bitmap?,
|
||||
context: Context,
|
||||
screenName: String? = null,
|
||||
moduleName: String? = null,
|
||||
scope: CoroutineScope
|
||||
): Bitmap? {
|
||||
if (backgroundScreenshot == null || bottomSheetScreenshot == null || isScreenDisabled(
|
||||
screenName, moduleName
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
var screenWidth = Resources.getSystem().displayMetrics.widthPixels / 2
|
||||
var screenHeight = Resources.getSystem().displayMetrics.heightPixels / 2
|
||||
if (!isResolutionEven(screenWidth, screenHeight)) {
|
||||
screenHeight += 1
|
||||
screenWidth += 1
|
||||
}
|
||||
val combinedBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(combinedBitmap)
|
||||
canvas.drawBitmap(backgroundScreenshot, 0f, 0f, null)
|
||||
var bottomSheetLeft = (screenWidth - bottomSheetScreenshot.width) / 2f
|
||||
var bottomSheetTop = screenHeight - bottomSheetScreenshot.height.toFloat()
|
||||
if (!isResolutionEven(bottomSheetLeft.toInt(), bottomSheetTop.toInt())) {
|
||||
bottomSheetLeft = (bottomSheetLeft.toInt() + 1).toFloat()
|
||||
bottomSheetTop = (bottomSheetTop.toInt() + 1).toFloat()
|
||||
}
|
||||
canvas.drawBitmap(bottomSheetScreenshot, bottomSheetLeft, bottomSheetTop, null)
|
||||
insertScreenShotPathInDb(scope, context, combinedBitmap)
|
||||
return combinedBitmap
|
||||
}
|
||||
|
||||
fun insertScreenShotPathInDb(
|
||||
scope: CoroutineScope, context: Context, bitmap: Bitmap?, bottomSheetFlow: Boolean? = false
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val fileDir = context.filesDir
|
||||
val fileName = System.currentTimeMillis().toString() + IMAGE_FILE_EXTENSION
|
||||
val path = fileDir.path.plus("/").plus(fileName)
|
||||
val imageUrl = File(path)
|
||||
val fos = FileOutputStream(
|
||||
imageUrl
|
||||
)
|
||||
val videoQuality: Int = when (AlfredManager.config.getVideoQuality()) {
|
||||
VideoQuality.HIGH.name -> {
|
||||
10
|
||||
}
|
||||
VideoQuality.LOW.name -> {
|
||||
6
|
||||
}
|
||||
VideoQuality.MEDIUM.name -> {
|
||||
8
|
||||
}
|
||||
else -> {
|
||||
10
|
||||
}
|
||||
}
|
||||
bitmap?.compress(CompressFormat.JPEG, videoQuality, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
if (bottomSheetFlow == false) {
|
||||
val db = AlfredDatabaseHelper.getAnalyticsDatabase(context)
|
||||
val screenShotDao = db.screenShotDao()
|
||||
try {
|
||||
screenShotDao.insertScreenShotPath(
|
||||
ScreenShotPathHelper(
|
||||
System.currentTimeMillis(), path
|
||||
)
|
||||
)
|
||||
ScreenShotStorageHelper.addImage(path)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
|
||||
} catch (e: IOException) {
|
||||
e.log()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun captureScreenshotOfCustomView(
|
||||
view: View
|
||||
): Bitmap? {
|
||||
view.draw(Canvas())
|
||||
val bitmapForCanvas = createBitmapForView(view)
|
||||
try {
|
||||
view.draw(bitmapForCanvas?.first)
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
}
|
||||
return bitmapForCanvas?.second
|
||||
}
|
||||
|
||||
fun createBitmapForView(view: View): Pair<Canvas, Bitmap>? {
|
||||
var width = view.width / 2
|
||||
var height = view.height / 2
|
||||
if (width > 0 && height > 0) {
|
||||
if (!isResolutionEven(width, height)) {
|
||||
width += 1
|
||||
height += 1
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.scale(0.5f, 0.5f)
|
||||
return Pair(canvas, bitmap)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
fun zip(_files: ArrayList<String>, zipFilePath: String?): Boolean? {
|
||||
val buffer = 1000
|
||||
try {
|
||||
zipFilePath?.let { zipFile ->
|
||||
File(zipFile)
|
||||
}
|
||||
var origin: BufferedInputStream? = null
|
||||
val dest = FileOutputStream(zipFilePath)
|
||||
val out = ZipOutputStream(BufferedOutputStream(dest))
|
||||
val data = ByteArray(buffer)
|
||||
for (i in _files.indices) {
|
||||
try {
|
||||
val fi = FileInputStream(_files[i])
|
||||
origin = BufferedInputStream(fi, buffer)
|
||||
val entry = ZipEntry(_files[i].substring(_files[i].lastIndexOf("/") + 1))
|
||||
out.putNextEntry(entry)
|
||||
var count: Int
|
||||
while (origin.read(data, 0, buffer).also { count = it } != -1) {
|
||||
out.write(data, 0, count)
|
||||
}
|
||||
origin.close()
|
||||
} catch (e: Exception) {
|
||||
e.log()
|
||||
}
|
||||
}
|
||||
out.close()
|
||||
return true
|
||||
} catch (e: java.lang.Exception) {
|
||||
e.log()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun clearScreenShot(path: List<ScreenShotPathHelper>): Boolean {
|
||||
path.forEach { data ->
|
||||
val file = data.screenShotPath?.let { File(it) }
|
||||
if (file?.exists() == true) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun isScreenDisabled(screenName: String? = null, moduleName: String? = null): Boolean {
|
||||
if (moduleName != null && AlfredManager.config.getDisableModuleList()
|
||||
?.contains(moduleName) == true
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
if (screenName != null && AlfredManager.config.getDisableScreenList()
|
||||
?.contains(screenName) == true
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun checkFileExists(fileName: String, context: Context): File? {
|
||||
val file = File(context.filesDir, fileName)
|
||||
return if (file.exists()) {
|
||||
file
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getTouchEvent(
|
||||
currentTouchEvent: MotionEvent?, previousTouchEvent: NaviMotionEvent?
|
||||
): Pair<String, HashMap<String, String>> {
|
||||
val properties = java.util.HashMap<String, String>()
|
||||
val eventName = if (difference(currentTouchEvent?.rawX, previousTouchEvent?.positionX) && difference(
|
||||
currentTouchEvent?.rawY, previousTouchEvent?.positionY
|
||||
)
|
||||
) {
|
||||
properties[AlfredConstants.START_X] = previousTouchEvent?.positionX.toString()
|
||||
properties[AlfredConstants.START_Y] = previousTouchEvent?.positionY.toString()
|
||||
AlfredConstants.TOUCH_EVENT
|
||||
} else {
|
||||
properties[AlfredConstants.START_X] = previousTouchEvent?.positionX.toString()
|
||||
properties[AlfredConstants.START_Y] = previousTouchEvent?.positionY.toString()
|
||||
properties[AlfredConstants.END_X] = currentTouchEvent?.rawX.toString()
|
||||
properties[AlfredConstants.END_Y] = currentTouchEvent?.rawY.toString()
|
||||
AlfredConstants.SCROLL_EVENT
|
||||
}
|
||||
return Pair(eventName, properties)
|
||||
}
|
||||
|
||||
fun difference(value1: Float? = 0F, value2: Float? = 0F, threshold: Int = 100): Boolean {
|
||||
return abs(subtractExact(value1?.toInt().orZero(), value2?.toInt().orZero())) <= threshold
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun getNetworkType(context: Context): String {
|
||||
try {
|
||||
val connManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
|
||||
if (mWifi != null && mWifi.isConnected) return "Wifi"
|
||||
val mTelephonyManager =
|
||||
context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||
return when (mTelephonyManager.networkType) {
|
||||
TelephonyManager.NETWORK_TYPE_GPRS, TelephonyManager.NETWORK_TYPE_EDGE, TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyManager.NETWORK_TYPE_IDEN -> "2G"
|
||||
TelephonyManager.NETWORK_TYPE_UMTS, TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyManager.NETWORK_TYPE_HSDPA, TelephonyManager.NETWORK_TYPE_HSUPA, TelephonyManager.NETWORK_TYPE_HSPA, TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyManager.NETWORK_TYPE_HSPAP -> "3G"
|
||||
TelephonyManager.NETWORK_TYPE_LTE -> "4G"
|
||||
TelephonyManager.NETWORK_TYPE_NR -> "5G"
|
||||
else -> "Unknown" + AlfredConstants.UNDERSCORE + mTelephonyManager.networkType
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
fun getCarrierName(context: Context): String? {
|
||||
return try {
|
||||
(context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).networkOperatorName
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Int?.orZero() = this ?: 0
|
||||
|
||||
fun Float?.orZero() = this ?: 0F
|
||||
|
||||
fun getBatteryPercentage(context: Context): Float {
|
||||
val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
|
||||
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY).toFloat()
|
||||
}
|
||||
|
||||
fun getMemoryUsage(): Float {
|
||||
val runtime = Runtime.getRuntime()
|
||||
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
|
||||
return usedMemory / (1024f * 1024f)
|
||||
}
|
||||
|
||||
fun getCpuUsage(): Float {
|
||||
val command = "top -n 1 -d 1"
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val reader = BufferedReader(InputStreamReader(process.inputStream))
|
||||
var line: String? = reader.readLine()
|
||||
var cpuUsage = 0f
|
||||
while (line != null) {
|
||||
if (line.contains("% cpu")) {
|
||||
val regex = "\\s+".toRegex()
|
||||
val tokens = regex.split(line)
|
||||
cpuUsage = tokens[0].toFloat()
|
||||
break
|
||||
}
|
||||
line = reader.readLine()
|
||||
}
|
||||
reader.close()
|
||||
return cpuUsage
|
||||
|
||||
}
|
||||
|
||||
fun getStorageUsage(context: Context): Pair<Long, Long> {
|
||||
val path = context.filesDir.path
|
||||
val stat = StatFs(path)
|
||||
val blockSize = stat.blockSizeLong
|
||||
val totalBlocks = stat.blockCountLong
|
||||
val freeBlocks = stat.availableBlocksLong
|
||||
val totalSize = blockSize * totalBlocks
|
||||
val freeSize = blockSize * freeBlocks
|
||||
return Pair(totalSize, freeSize)
|
||||
}
|
||||
|
||||
enum class VideoQuality {
|
||||
LOW, HIGH, MEDIUM
|
||||
}
|
||||
|
||||
private fun Any.tag(): String {
|
||||
return try {
|
||||
this::class.java.simpleName.ifEmpty { "Empty" }
|
||||
} catch (e: Exception) {
|
||||
"EmptyTag"
|
||||
}
|
||||
}
|
||||
|
||||
private fun isResolutionEven(width: Int, height: Int): Boolean {
|
||||
if (width.mod(2) == 0 && height.mod(2) == 0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun java.lang.Exception.log() {
|
||||
if (AlfredManager.config.isQa()) {
|
||||
Timber.tag(tag()).e(this)
|
||||
}
|
||||
AlfredHelper.recordException(this)
|
||||
}
|
||||
|
||||
fun getScreenWidth(): Int {
|
||||
return Resources.getSystem().displayMetrics.widthPixels
|
||||
}
|
||||
|
||||
fun getScreenHeight(): Int {
|
||||
return Resources.getSystem().displayMetrics.heightPixels
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2022-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.navi.alfred.AlfredManager
|
||||
import com.navi.alfred.db.AlfredDatabaseHelper
|
||||
import com.navi.alfred.db.model.AnalyticsEvent
|
||||
import com.navi.alfred.dispatcher.AlfredDispatcher
|
||||
import com.navi.alfred.utils.AlfredConstants
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class AddEventTask(
|
||||
private val event: AnalyticsEvent,
|
||||
private val context: Context
|
||||
) : AnalyticsTask {
|
||||
companion object {
|
||||
private var eventCount: AtomicInteger = AtomicInteger(0)
|
||||
fun resetEventCount() {
|
||||
eventCount = AtomicInteger(0)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun execute(): Boolean {
|
||||
val db = AlfredDatabaseHelper.getAnalyticsDatabase(context)
|
||||
val analyticsDao = db.analyticsDao()
|
||||
return try {
|
||||
analyticsDao.insertEvent(event)
|
||||
eventCount.incrementAndGet()
|
||||
checkIfSyncIsNeeded()
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfSyncIsNeeded() {
|
||||
if (eventCount.get() >= AlfredManager.config.getEventBatchSize()) {
|
||||
resetEventCount()
|
||||
AlfredDispatcher.executeInNewThread(SyncDataTask(context))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTaskName(): String {
|
||||
return AlfredConstants.ADD_EVENT_TASK
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.navi.alfred.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.navi.alfred.AlfredManager
|
||||
import com.navi.alfred.db.AlfredDatabaseHelper
|
||||
import com.navi.alfred.db.model.ApiMetricHelper
|
||||
import com.navi.alfred.dispatcher.AlfredDispatcher
|
||||
import com.navi.alfred.utils.AlfredConstants
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
|
||||
class AddMetricTask(
|
||||
private val event: ApiMetricHelper, private val context: Context
|
||||
) : AnalyticsTask {
|
||||
companion object {
|
||||
private var eventCount: AtomicInteger = AtomicInteger(0)
|
||||
fun resetEventCount() {
|
||||
eventCount = AtomicInteger(0)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun execute(): Boolean {
|
||||
val db = AlfredDatabaseHelper.getAnalyticsDatabase(context)
|
||||
val analyticsDao = db.apiMetricDao()
|
||||
return try {
|
||||
analyticsDao.insert(event)
|
||||
eventCount.incrementAndGet()
|
||||
checkIfSyncIsNeeded()
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfSyncIsNeeded() {
|
||||
if (eventCount.get() >= AlfredManager.config.getEventBatchSize()) {
|
||||
resetEventCount()
|
||||
AlfredDispatcher.executeInNewThread(SyncDataTask(context))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTaskName(): String {
|
||||
return AlfredConstants.ADD_API_METRIC_TASK
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2019-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.worker
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
|
||||
interface AnalyticsTask {
|
||||
@WorkerThread
|
||||
fun execute(): Boolean
|
||||
|
||||
fun getTaskName(): String
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2019-2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.worker
|
||||
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import java.util.concurrent.BlockingDeque
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.LinkedBlockingDeque
|
||||
|
||||
class AnalyticsTaskProcessor {
|
||||
private val taskQueue: BlockingDeque<AnalyticsTask> = LinkedBlockingDeque<AnalyticsTask>()
|
||||
private val coroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
private val singleThreadExecutorForSync = Executors.newSingleThreadExecutor()
|
||||
private var active: AnalyticsTask? = null
|
||||
private var isSyncEventTaskExecuted: Boolean = true
|
||||
fun addTask(task: AnalyticsTask?) {
|
||||
if (task != null) {
|
||||
taskQueue.add(task)
|
||||
startExecution()
|
||||
}
|
||||
}
|
||||
|
||||
fun addTaskToFront(task: AnalyticsTask?) {
|
||||
if (task != null) {
|
||||
taskQueue.addFirst(task)
|
||||
startExecution()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startExecution() {
|
||||
if (active == null) {
|
||||
scheduleNext()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleNext() {
|
||||
if (taskQueue.poll().also { active = it } != null) {
|
||||
coroutineDispatcher.executor.execute {
|
||||
active?.let {
|
||||
executeTask(it)
|
||||
scheduleNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeTask(task: AnalyticsTask) {
|
||||
task.execute()
|
||||
}
|
||||
|
||||
fun executeInNewThread(task: AnalyticsTask) {
|
||||
if (isSyncEventTaskExecuted) {
|
||||
isSyncEventTaskExecuted = false
|
||||
singleThreadExecutorForSync.execute {
|
||||
task.execute()
|
||||
isSyncEventTaskExecuted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.alfred.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.navi.alfred.AlfredManager
|
||||
import com.navi.alfred.utils.AlfredConstants.SYNC_EVENT_TASK
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class SyncDataTask(private val context: Context) : AnalyticsTask {
|
||||
@WorkerThread
|
||||
override fun execute(): Boolean {
|
||||
return runBlocking {
|
||||
AlfredManager.sendIngestMetric()
|
||||
AlfredManager.sendEventsToServer(context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTaskName(): String {
|
||||
return SYNC_EVENT_TASK
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.navi.alfred.worker
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.navi.alfred.AlfredManager
|
||||
import com.navi.alfred.db.model.ScreenShotPathHelper
|
||||
import com.navi.alfred.utils.AlfredConstants
|
||||
import com.navi.alfred.utils.checkFileExists
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.reflect.Type
|
||||
|
||||
|
||||
class UploadFileWorker(
|
||||
context: Context, workerParams: WorkerParameters
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
|
||||
val alfredSessionId = inputData.getString(AlfredConstants.ALFRED_SESSION_ID)
|
||||
val sessionStartRecordingTime =
|
||||
inputData.getLong(AlfredConstants.SESSION_START_RECORDING_TIME, 0L)
|
||||
val eventStartRecordingTime =
|
||||
inputData.getLong(AlfredConstants.EVENT_START_RECORDING_TIME, 0L)
|
||||
val screenShotList = inputData.getString(AlfredConstants.SCREENSHOT_LIST)
|
||||
val listType: Type = object : TypeToken<List<ScreenShotPathHelper?>?>() {}.type
|
||||
val screenShots: List<ScreenShotPathHelper> =
|
||||
Gson().fromJson(screenShotList.toString(), listType)
|
||||
val zipFileName = alfredSessionId + sessionStartRecordingTime.toString()
|
||||
try {
|
||||
if (alfredSessionId == null || sessionStartRecordingTime == 0L || eventStartRecordingTime == 0L) {
|
||||
handleWorkFailure(zipFileName)
|
||||
Result.failure()
|
||||
} else {
|
||||
AlfredManager.toZipForWorkManager(screenShots, zipFileName,sessionStartRecordingTime,alfredSessionId, eventStartRecordingTime)
|
||||
Result.success()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
handleWorkFailure(zipFileName)
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWorkFailure(zipFileName:String) {
|
||||
checkFileExists(zipFileName, context = applicationContext)?.delete()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user