Release 1.0.9 (#57)

Co-authored-by: Sayed Owais Ali <sayed.owais@navi.com>
This commit is contained in:
Girish Suragani
2023-11-27 13:04:42 +05:30
committed by GitHub
parent fe2962523f
commit 0cbdbbaf12
25 changed files with 380 additions and 153 deletions

View File

@@ -6,7 +6,7 @@ plugins {
id 'kotlin-parcelize'
}
def VERSION_NAME = "1.0.8"
def VERSION_NAME = "1.0.9"
android {
namespace 'com.navi.alfred'

View File

@@ -57,6 +57,7 @@ data class AlfredConfig(
private var codePushVersion: String? = null,
private var apiKey: String = "",
private var userId: String? = null,
private var latestScreenshotTimestamp: Long = 0L,
internal var cpuUsageBeforeEventStart: Float? = null,
internal var memoryUsageBeforeEventStart: Float? = null,
internal var storageUsageBeforeEventStart: Float? = null,
@@ -329,4 +330,10 @@ data class AlfredConfig(
}
fun getAppName(): String = this.appName
fun getLatestScreenshotTimestamp(): Long = this.latestScreenshotTimestamp
fun setLatestScreenshotTimestamp(timestamp: Long) {
this.latestScreenshotTimestamp = timestamp
}
}

View File

@@ -12,7 +12,6 @@ import android.app.Dialog
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.util.Log
import android.view.KeyEvent.ACTION_DOWN
import android.view.KeyEvent.ACTION_UP
import android.view.LayoutInflater
@@ -21,17 +20,22 @@ import android.view.View
import androidx.appcompat.widget.AppCompatTextView
import com.navi.alfred.db.AlfredDatabase
import com.navi.alfred.db.AlfredDatabaseHelper
import com.navi.alfred.db.dao.AnalyticsDAO
import com.navi.alfred.db.dao.ApiMetricDao
import com.navi.alfred.db.dao.FailureEventDao
import com.navi.alfred.db.dao.NegativeCaseDao
import com.navi.alfred.db.dao.ScreenShotDao
import com.navi.alfred.db.dao.ZipDetailsDao
import com.navi.alfred.db.model.ZipDetailsHelper
import com.navi.alfred.dispatcher.AlfredDispatcher
import com.navi.alfred.model.CurrentScreen
import com.navi.alfred.model.NaviMotionEvent
import com.navi.alfred.model.WorkManagerFailureInputData
import com.navi.alfred.network.AlfredFailureRetrofitProvider
import com.navi.alfred.network.AlfredNetworkRepository
import com.navi.alfred.network.AlfredRetrofitProvider
import com.navi.alfred.network.model.CruiseResponse
import com.navi.alfred.repository.ComposeMaskingRepoImpl
import com.navi.alfred.utils.AlfredConstants
import com.navi.alfred.utils.AlfredConstants.API_METRICS
import com.navi.alfred.utils.AlfredConstants.CODE_API_SUCCESS
@@ -50,6 +54,7 @@ import com.navi.alfred.utils.getTouchEvent
import com.navi.alfred.utils.handleDeviceAttributes
import com.navi.alfred.utils.handleScreenShot
import com.navi.alfred.utils.insertScreenShotPathInDb
import com.navi.alfred.utils.isScreenDisabled
import com.navi.alfred.utils.log
import com.navi.alfred.utils.measureInflatedView
import com.navi.alfred.utils.startAnrCrashZipUpload
@@ -84,7 +89,7 @@ object AlfredManager {
internal var hasUploadFlowStarted: Boolean = false
internal var isAppInBackground: Boolean = false
internal var hasRecordingStarted: Boolean = false
internal var currentScreenName: String? = null
internal var currentScreen: CurrentScreen = CurrentScreen()
internal var currentModuleName: String? = null
internal var sessionIdForCrash: String? = null
internal var sessionStartRecordingTimeForCrash: Long? = null
@@ -96,11 +101,15 @@ object AlfredManager {
internal lateinit var screenShotDao: ScreenShotDao
internal lateinit var zipDetailsDao: ZipDetailsDao
internal lateinit var apiMetricDao: ApiMetricDao
internal lateinit var analyticsDao: AnalyticsDAO
internal lateinit var negativeCaseDao: NegativeCaseDao
internal lateinit var failureEventDao: FailureEventDao
internal lateinit var zipFileDetails: List<ZipDetailsHelper>
internal lateinit var applicationContext: Context
internal const val imageThreshHoldValue: Int = 60
internal const val imageSecondThreshHoldValue: Int = 100
var dialog: Dialog? = null
lateinit var sensitiveComposeRepository: ComposeMaskingRepoImpl
fun init(config: AlfredConfig, context: Context) {
this.config = config
@@ -153,21 +162,41 @@ object AlfredManager {
override fun run() {
coroutineScope.launch(Dispatchers.IO) {
if (moduleName == THIRD_PARTY_MODULE) {
currentScreenName = AlfredConstants.THIRD_PARTY_SCREEN
if (bmpForThirdPartySdkScreen == null) {
val thirdPartyScreenView = LayoutInflater.from(applicationContext)
.inflate(R.layout.third_party_screen, null)
measureInflatedView(thirdPartyScreenView)
thirdPartyScreenView.findViewById<AppCompatTextView>(R.id.tv_third_party_name).text =
activity?.localClassName.toString()
bmpForThirdPartySdkScreen =
thirdPartyScreenView?.let { captureScreenshotOfCustomView(it) }
currentScreen.name = activity?.localClassName.toString()
if (isScreenDisabled(currentScreen.name, moduleName)) {
if (bmpForThirdPartySdkScreen == null) {
val thirdPartyScreenView =
LayoutInflater.from(applicationContext)
.inflate(R.layout.third_party_screen, null)
measureInflatedView(thirdPartyScreenView)
thirdPartyScreenView.findViewById<AppCompatTextView>(R.id.tv_third_party_name).text = currentScreen.name
bmpForThirdPartySdkScreen =
thirdPartyScreenView?.let { captureScreenshotOfCustomView(it) }
}
insertScreenShotPathInDb(
this,
applicationContext,
bmpForThirdPartySdkScreen
)
} else {
if (bmpForCanvas == null) {
delay(viewLayoutDelay)
bmpForCanvas = createBitmapForView(view)
}
try {
captureScreen(
view,
context,
screenName = currentScreen.name,
scope = coroutineScope,
canvas = bmpForCanvas?.first,
bmp = bmpForCanvas?.second,
moduleName = moduleName
)
} catch (e: Exception) {
e.log()
}
}
insertScreenShotPathInDb(
this,
applicationContext,
bmpForThirdPartySdkScreen
)
} else {
if (bmpForCanvas == null) {
delay(viewLayoutDelay)
@@ -181,7 +210,7 @@ object AlfredManager {
view,
bottomSheetView,
context,
currentScreenName,
currentScreen.name,
bmpForCanvas?.first,
rootBmp = bmpForCanvas?.second,
moduleName = moduleName
@@ -190,7 +219,7 @@ object AlfredManager {
captureScreen(
view,
context,
screenName = currentScreenName,
screenName = currentScreen.name,
scope = coroutineScope,
canvas = bmpForCanvas?.first,
bmp = bmpForCanvas?.second,
@@ -220,7 +249,7 @@ object AlfredManager {
measureInflatedView(appBackgroundView)
val stopRecordingEvent = buildEvent(
AlfredConstants.STOP_RECORDING_EVENT,
screenName = currentScreenName,
screenName = currentScreen.name,
moduleName = currentModuleName
)
AlfredDispatcher.addTaskToQueue(AddEventTask(stopRecordingEvent, applicationContext))
@@ -234,13 +263,16 @@ object AlfredManager {
CoroutineScope(Dispatchers.IO).launch {
try {
getCruiseConfig(cruiseApiSuccessful = { response ->
Log.d("Alfred", "getAlfredCruiseInfo cruiseApiSuccessfulresponse = $response")
cruiseApiSuccessful(response)
startSyncEvents(applicationContext)
alfredDataBase = AlfredDatabaseHelper.getAnalyticsDatabase(applicationContext)
analyticsDao = alfredDataBase.analyticsDao()
screenShotDao = alfredDataBase.screenShotDao()
zipDetailsDao = alfredDataBase.zipDetailsDao()
apiMetricDao = alfredDataBase.apiMetricDao()
negativeCaseDao = alfredDataBase.negativeCaseDao()
failureEventDao = alfredDataBase.failureEventDao()
sensitiveComposeRepository = ComposeMaskingRepoImpl()
startSyncEvents()
})
} catch (e: Exception) {
e.log()
@@ -336,6 +368,29 @@ object AlfredManager {
}
}
fun handleScreenTransitionEvent(
screenName: String?,
moduleName: String? = null
) {
if (isAlfredRecordingEnabled()) {
if (config.getAlfredSessionId().isNotEmpty()) {
coroutineDispatcher.executor.execute {
try {
val event =
buildEvent(
AlfredConstants.SCREEN_TRANSITION_EVENT,
screenName = screenName,
moduleName = moduleName
)
AlfredDispatcher.addTaskToQueue(AddEventTask(event, applicationContext))
} catch (e: Exception) {
e.log()
}
}
}
}
}
fun handleAnrEvent(anrEventProperties: Map<String, String>) {
if (config.getAlfredStatus() && config.getAnrEnableStatus()) {
val anrView =
@@ -348,7 +403,7 @@ object AlfredManager {
buildNegativeCaseEvent(
AlfredConstants.ANR_EVENT,
anrEventProperties as HashMap<String, String>,
screenName = currentScreenName,
screenName = currentScreen.name,
moduleName = currentModuleName
)
AlfredDispatcher.addTaskToQueue(
@@ -374,7 +429,7 @@ object AlfredManager {
buildNegativeCaseEvent(
AlfredConstants.CRASH_ANALYTICS_EVENT,
crashEventProperties as HashMap<String, String>,
screenName = currentScreenName,
screenName = currentScreen.name,
moduleName = currentModuleName
)
AlfredDispatcher.addTaskToQueue(AddNegativeCase(event, applicationContext))
@@ -391,7 +446,7 @@ object AlfredManager {
buildNegativeCaseEvent(
AlfredConstants.ERROR_LOG,
swwEventProperties as HashMap<String, String>,
screenName = currentScreenName,
screenName = currentScreen.name,
moduleName = currentModuleName
)
AlfredDispatcher.addTaskToQueue(AddNegativeCase(event, applicationContext))
@@ -400,9 +455,10 @@ object AlfredManager {
}
}
fun setCurrentScreenName(screenName: String?) {
fun setCurrentScreenName(screenName: String?, isComposeScreen: Boolean? = false) {
if (isAlfredRecordingEnabled()) {
currentScreenName = screenName
currentScreen.name = screenName ?: ""
currentScreen.isComposeScreen = isComposeScreen ?: false
}
}

View File

@@ -31,7 +31,7 @@ import com.navi.alfred.db.model.ZipDetailsHelper
NegativeCase::class,
FailureEvent::class
],
version = 4,
version = 5,
exportSchema = true
)
abstract class AlfredDatabase : RoomDatabase() {

View File

@@ -33,7 +33,9 @@ data class ZipDetailsHelper(
@ColumnInfo(name = "alfredSessionId") val alfredSessionId: String,
@ColumnInfo(name = "sessionStartRecordingTime") val sessionStartRecordingTime: Long,
@ColumnInfo(name = "eventStartRecordingTime") val eventStartRecordingTime: Long,
@ColumnInfo(name = "zipFileName") val zipFileName: String
@ColumnInfo(name = "zipFileName") val zipFileName: String,
@ColumnInfo(name = "alfredEventId") val alfredEventId: String,
@ColumnInfo(name = "latestScreenshotTimestamp") val latestScreenshotTimestamp: Long
) {
@PrimaryKey(autoGenerate = true) var id: Int = 0
}

View File

@@ -0,0 +1,12 @@
package com.navi.alfred.model
data class CurrentScreen(
var name: String? = "",
var isComposeScreen: Boolean? = false
) {
override fun toString(): String {
return "CurrentScreen(name='$name', isComposeScreen=$isComposeScreen)"
}
}

View File

@@ -24,5 +24,7 @@ data class WorkManagerFailureInputData(
var eventStartRecordingTime: Long = 0L,
var screenShots: List<ScreenShotPathHelper> = emptyList(),
val id: UUID,
val zipFileName: String
val zipFileName: String,
val alfredEventId: String,
val latestScreenshotTimestamp: Long = 0L
)

View File

@@ -0,0 +1,22 @@
package com.navi.alfred.model
data class MaskingBounds(
val left: Float,
val top: Float,
val right: Float,
val bottom: Float
) {
val width: Float
get() {
return right - left
}
val height: Float
get() {
return bottom - top
}
override fun toString(): String {
return "MaskingBounds(left=$left, top=$top, right=$right, bottom=$bottom)"
}
}

View File

@@ -29,6 +29,8 @@ data class EventAttribute(
@SerializedName("module_name") val moduleName: String? = null,
@SerializedName("fragment_list") val fragmentList: List<String>? = null,
@SerializedName("zip_name") val zipName: String? = null,
@SerializedName("session_time_stamp") val sessionTimeStamp: Long? = AlfredManager.config.getSessionStartRecordingTime(),
@SerializedName("screenshot_timestamp") val screenShotTimeStamp: Long? = null
)
data class SessionRequest(
@@ -61,11 +63,11 @@ data class BaseAttribute(
@SerializedName("event_timestamp")
val eventTimeStamp: Long? = AlfredManager.config.getEventTimeStamp(),
@SerializedName("session_id")
val sessionId: String? = AlfredManager.config.getAlfredSessionId(),
val sessionId: String? = null,
@SerializedName("session_time_stamp")
val sessionTimeStamp: Long? = AlfredManager.config.getSessionStartRecordingTime(),
val sessionTimeStamp: Long? = null,
@SerializedName("event_end_time_stamp")
val eventEndTimeStamp: Long? = AlfredManager.config.getEventTimeStamp(),
val latestScreenshotTimestamp: Long? = null,
@SerializedName("timezone")
val timezone: String? = AlfredManager.config.getCurrentTimeZone(),
@SerializedName("metadata") val metaData: MetaData = MetaData(),

View File

@@ -0,0 +1,9 @@
package com.navi.alfred.repository
import android.view.View
import com.navi.alfred.model.MaskingBounds
interface ComposeMaskingRepo {
fun maskSensitiveComposable(id: String, coordinates: MaskingBounds?, currentView: View?)
fun removeSensitiveComposable(id: String)
}

View File

@@ -0,0 +1,41 @@
package com.navi.alfred.repository
import android.view.View
import com.navi.alfred.model.MaskingBounds
import java.lang.ref.WeakReference
class ComposeMaskingRepoImpl : ComposeMaskingRepo {
private val sensitiveCoordinates = mutableMapOf<String, MaskingBounds>()
private var rootView = WeakReference<View>(null)
override fun maskSensitiveComposable(
id: String,
coordinates: MaskingBounds?,
rootView: View?
) {
if (rootView != this.rootView.get()) {
removeAllSensitiveComposables()
}
if (coordinates != null) {
this.rootView = WeakReference(rootView)
sensitiveCoordinates[id] = coordinates
}
}
override fun removeSensitiveComposable(id: String) {
if (sensitiveCoordinates.containsKey(id)) {
sensitiveCoordinates.remove(id)
}
}
internal fun removeAllSensitiveComposables() {
sensitiveCoordinates.clear()
}
internal fun getAllSensitiveComposableCoordinates(): List<MaskingBounds> {
return sensitiveCoordinates.values.toList()
}
internal fun getRootViewOfComposeScreen(): View? {
return rootView.get()
}
}

View File

@@ -76,6 +76,7 @@ object AlfredConstants {
const val DISABLE_ALFRED_LOGS = "DISABLE_ALFRED_LOGS"
const val DISABLE_CDN_LOGS = "DISABLE_CDN_LOGS"
const val SENSITIVE_VIEW_TAG = "sensitive_view"
const val SENSITIVE_COMPOSE_VIEW_TAG = "sensitive_compose_view"
const val EVENT_DB_NAME = "navi-analytics"
const val ZIP_FILE_EXTENSION = ".zip"
const val IMAGE_FILE_EXTENSION = ".jpeg"
@@ -106,4 +107,6 @@ object AlfredConstants {
const val GET_PRE_SIGNED_URL_FAILURE = "GET_PRE_SIGNED_URL_FAILURE"
const val API_ERROR = "API_ERROR"
const val ZIP_ERROR = "ZIP_ERROR"
const val SCREEN_TRANSITION_EVENT = "SCREEN_TRANSITION_EVENT"
const val LATEST_SCREENSHOT_TIMESTAMP = "LATEST_SCREENSHOT_TIMESTAMP"
}

View File

@@ -104,8 +104,8 @@ internal fun getBatteryPercentage(context: Context): Float {
internal fun getMemoryUsagePercentage(): Float {
val runtime = Runtime.getRuntime()
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
val maxMemory = runtime.maxMemory()
return usedMemory.toFloat() / maxMemory.toFloat() * 100
val totalMemory = runtime.totalMemory()
return usedMemory.toFloat() / totalMemory.toFloat() * 100
}
internal fun getCpuUsage(): Float {

View File

@@ -1,11 +1,9 @@
package com.navi.alfred.utils
import android.content.Context
import androidx.annotation.WorkerThread
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.navi.alfred.AlfredManager
import com.navi.alfred.db.AlfredDatabaseHelper
import com.navi.alfred.db.model.AnalyticsEvent
import com.navi.alfred.db.model.ApiMetricHelper
import com.navi.alfred.db.model.FailureEvent
@@ -43,14 +41,12 @@ import kotlinx.coroutines.sync.withLock
import java.lang.reflect.Type
import kotlin.concurrent.fixedRateTimer
internal suspend fun sendEventsToServer(applicationContext: Context): Boolean {
internal suspend fun sendEventsToServer(): Boolean {
if (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getEnableRecordingStatus()) {
try {
AlfredManager.mutex.withLock {
val db = AlfredDatabaseHelper.getAnalyticsDatabase(applicationContext)
val analyticsDao = db.analyticsDao()
val analyticsEvents = analyticsDao.fetchEvents(AlfredManager.config.getEventBatchSize())
val analyticsEvents =
AlfredManager.analyticsDao.fetchEvents(AlfredManager.config.getEventBatchSize())
if (analyticsEvents.isNotEmpty()) {
try {
val detailsList = analyticsEvents.map { it.details }
@@ -59,10 +55,9 @@ internal suspend fun sendEventsToServer(applicationContext: Context): Boolean {
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),
baseAttribute = BaseAttribute(),
events = events
)
val response = AlfredManager.networkRepository.sendEvents(
@@ -72,7 +67,7 @@ internal suspend fun sendEventsToServer(applicationContext: Context): Boolean {
)
return if (
response.isSuccessful && response.code() == AlfredConstants.CODE_API_SUCCESS) {
analyticsDao.deleteEvents(analyticsEvents.map { it.eventId })
AlfredManager.analyticsDao.deleteEvents(analyticsEvents.map { it.eventId })
true
} else {
val zipNamesList: MutableList<String> = mutableListOf()
@@ -98,7 +93,7 @@ internal suspend fun sendEventsToServer(applicationContext: Context): Boolean {
)
)
if (response.code() == AlfredConstants.CODE_API_BAD_REQUEST) {
analyticsDao.deleteEvents(analyticsEvents.map { it.eventId })
AlfredManager.analyticsDao.deleteEvents(analyticsEvents.map { it.eventId })
true
} else {
false
@@ -123,15 +118,12 @@ internal suspend fun sendEventsToServer(applicationContext: Context): Boolean {
return false
}
internal suspend fun sendNegativeCaseToServer(applicationContext: Context): Boolean {
internal suspend fun sendNegativeCaseToServer(): Boolean {
if (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getEnableRecordingStatus()) {
try {
AlfredManager.mutex.withLock {
val db = AlfredDatabaseHelper.getAnalyticsDatabase(applicationContext)
val negativeCaseDao = db.negativeCaseDao()
val negativeEvents =
negativeCaseDao.fetchNegativeCase(AlfredManager.config.getEventBatchSize())
AlfredManager.negativeCaseDao.fetchNegativeCase(AlfredManager.config.getEventBatchSize())
if (negativeEvents.isNotEmpty()) {
try {
val detailsList = negativeEvents.map { it.details }
@@ -140,10 +132,9 @@ internal suspend fun sendNegativeCaseToServer(applicationContext: Context): Bool
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),
baseAttribute = BaseAttribute(),
events = events
)
val response = AlfredManager.networkRepository.sendNegativeCase(
@@ -154,7 +145,7 @@ internal suspend fun sendNegativeCaseToServer(applicationContext: Context): Bool
return if (
response.isSuccessful && response.code() == AlfredConstants.CODE_API_SUCCESS
) {
negativeCaseDao.deleteNegativeCase(negativeEvents.map { it.negativeCaseId })
AlfredManager.negativeCaseDao.deleteNegativeCase(negativeEvents.map { it.negativeCaseId })
true
} else {
val failureEvent = buildFailureEvent(
@@ -172,7 +163,7 @@ internal suspend fun sendNegativeCaseToServer(applicationContext: Context): Bool
)
)
if (response.code() == AlfredConstants.CODE_API_BAD_REQUEST) {
negativeCaseDao.deleteNegativeCase(negativeEvents.map { it.negativeCaseId })
AlfredManager.negativeCaseDao.deleteNegativeCase(negativeEvents.map { it.negativeCaseId })
true
} else {
false
@@ -197,15 +188,12 @@ internal suspend fun sendNegativeCaseToServer(applicationContext: Context): Bool
return false
}
internal suspend fun sendFailureEventsToServer(applicationContext: Context): Boolean {
internal suspend fun sendFailureEventsToServer(): Boolean {
if (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getEnableRecordingStatus()) {
try {
AlfredManager.mutex.withLock {
val db = AlfredDatabaseHelper.getAnalyticsDatabase(applicationContext)
val failureEventDao = db.failureEventDao()
val failureEvents =
failureEventDao.fetchFailureEvents(AlfredManager.config.getFailureEventBatchSize())
AlfredManager.failureEventDao.fetchFailureEvents(AlfredManager.config.getFailureEventBatchSize())
if (failureEvents.isNotEmpty()) {
try {
val detailsList = failureEvents.map { it.details }
@@ -228,7 +216,7 @@ internal suspend fun sendFailureEventsToServer(applicationContext: Context): Boo
(response.isSuccessful && response.code() == AlfredConstants.CODE_API_SUCCESS) or
(response.code() == AlfredConstants.CODE_API_BAD_REQUEST)
) {
failureEventDao.deleteFailureEvents(failureEvents.map { it.eventId })
AlfredManager.failureEventDao.deleteFailureEvents(failureEvents.map { it.eventId })
true
} else {
false
@@ -255,14 +243,14 @@ internal suspend fun sendFailureEventsToServer(applicationContext: Context): Boo
internal fun sendAlfredSessionEvent(
dumpFlow: Boolean = false,
index: Int? = null,
currentZipName: String? = null
currentZipName: String? = null,
latestScreenshotTimestamp: Long? = null
) {
var request: SessionRequest? = null
if (dumpFlow) {
var clientTs: Long? = null
var sessionTimeStamp: Long? = null
var sessionId: String? = null
var eventEndTimeStamp: Long? = null
if (index != null) {
val zipFileDetail = AlfredManager.zipFileDetails[index]
clientTs = zipFileDetail.eventStartRecordingTime
@@ -274,14 +262,6 @@ internal fun sendAlfredSessionEvent(
sessionId = AlfredManager.sessionIdForCrash
}
clientTs?.let {
eventEndTimeStamp =
java.lang.Long.min(
it.plus(AlfredConstants.INCREMENT_OF_ONE_MINUTE),
AlfredManager.config.getEventTimeStamp()
)
}
request =
SessionRequest(
base_attribute =
@@ -290,7 +270,7 @@ internal fun sendAlfredSessionEvent(
eventTimeStamp = AlfredManager.config.getEventTimeStamp(),
clientTs = clientTs,
sessionTimeStamp = sessionTimeStamp,
eventEndTimeStamp = eventEndTimeStamp
latestScreenshotTimestamp = latestScreenshotTimestamp
),
session_upload_event_attributes =
SessionEventAttribute(
@@ -300,13 +280,6 @@ internal fun sendAlfredSessionEvent(
)
)
} else {
val eventEndTimeStamp = AlfredManager.config.getEventStartRecordingTime()
?.let {
java.lang.Long.min(
it.plus(AlfredConstants.INCREMENT_OF_ONE_MINUTE),
AlfredManager.config.getEventTimeStamp()
)
}
request =
SessionRequest(
base_attribute =
@@ -314,7 +287,7 @@ internal fun sendAlfredSessionEvent(
sessionId = AlfredManager.config.getAlfredSessionId(),
eventTimeStamp = AlfredManager.config.getEventTimeStamp(),
clientTs = AlfredManager.config.getEventStartRecordingTime(),
eventEndTimeStamp = eventEndTimeStamp
latestScreenshotTimestamp = latestScreenshotTimestamp
),
session_upload_event_attributes =
SessionEventAttribute(
@@ -391,7 +364,7 @@ internal suspend fun sendIngestMetric(): Boolean {
val sessionId = events.first().sessionId
val request =
EventMetricRequest(
baseAttribute = BaseAttribute(sessionId = sessionId),
baseAttribute = BaseAttribute(),
metricsAttribute = events
)
val response =
@@ -444,7 +417,7 @@ internal suspend fun sendIngestMetric(): Boolean {
return false
}
internal fun startSyncEvents(applicationContext: Context) {
internal fun startSyncEvents() {
AlfredManager.timer =
fixedRateTimer(
AlfredConstants.TIMER_THREAD_NAME,
@@ -456,11 +429,11 @@ internal fun startSyncEvents(applicationContext: Context) {
AddMetricTask.resetEventCount()
sendIngestMetric()
AddEventTask.resetEventCount()
sendEventsToServer(applicationContext)
sendEventsToServer()
AddNegativeCase.resetEventCount()
sendNegativeCaseToServer(applicationContext)
sendNegativeCaseToServer()
AddFailureTask.resetEventCount()
sendFailureEventsToServer(applicationContext)
sendFailureEventsToServer()
}
}
}
@@ -483,7 +456,8 @@ internal fun buildEvent(
screenName = screenName,
moduleName = moduleName,
fragmentList = getFragmentList(),
zipName = AlfredManager.config.getAlfredEventId()
zipName = AlfredManager.config.getAlfredEventId(),
screenShotTimeStamp = AlfredManager.config.getLatestScreenshotTimestamp()
)
return AnalyticsEvent(timeStamp, Gson().toJson(eventData))
}
@@ -551,7 +525,7 @@ internal fun buildAppPerformanceEvent(
eventType = eventType,
sessionId = AlfredManager.config.getAlfredSessionId(),
attributes = attribute,
screenName = AlfredManager.currentScreenName,
screenName = AlfredManager.currentScreen.name,
moduleName = AlfredManager.currentModuleName,
fragmentList = getFragmentList()
)

View File

@@ -4,11 +4,15 @@ import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.navi.alfred.AlfredManager
import com.navi.alfred.db.AlfredDatabaseHelper
import com.navi.alfred.db.model.ScreenShotPathHelper
import com.navi.alfred.model.MaskingBounds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -74,8 +78,10 @@ internal fun insertScreenShotPathInDb(
scope.launch(Dispatchers.IO) {
try {
val fileDir = context.filesDir
val currentTime = System.currentTimeMillis()
AlfredManager.config.setLatestScreenshotTimestamp(currentTime)
val fileName =
System.currentTimeMillis().toString() + AlfredConstants.IMAGE_FILE_EXTENSION
currentTime.toString() + AlfredConstants.IMAGE_FILE_EXTENSION
val path = fileDir.path.plus("/").plus(fileName)
val imageUrl = File(path)
val fos = FileOutputStream(imageUrl)
@@ -189,34 +195,53 @@ internal suspend fun captureScreen(
return null
}
if (canvas != null && bmp != null) {
if (isMaskingEnabled(screenName) || (v.tag == AlfredConstants.SENSITIVE_VIEW_TAG)) {
val maskedViewsList = findViewWithTagRecursive(
v,
AlfredConstants.SENSITIVE_VIEW_TAG, mutableListOf()
)
withContext(Dispatchers.Main) {
try {
if (maskedViewsList.isEmpty()) {
v.draw(canvas)
val currentScreen = AlfredManager.currentScreen
val sensitiveCoordinates = AlfredManager.sensitiveComposeRepository.getAllSensitiveComposableCoordinates()
val rootView = AlfredManager.sensitiveComposeRepository.getRootViewOfComposeScreen()
if (isMaskingEnabled(screenName) || (v.tag == AlfredConstants.SENSITIVE_VIEW_TAG) || (v.tag == AlfredConstants.SENSITIVE_COMPOSE_VIEW_TAG)) {
try {
if (currentScreen.isComposeScreen == true) {
if (sensitiveCoordinates.isNotEmpty() && rootView != null) {
withContext(Dispatchers.Main) {
captureComposeViewWithMasking(canvas, sensitiveCoordinates, rootView)
}
} else {
val alphaList = mutableListOf<Pair<View, Float>>()
for (maskedView in maskedViewsList) {
alphaList.add(Pair(maskedView, maskedView.alpha))
maskedView.alpha = 0.0f
withContext(Dispatchers.Main) {
blurScreen(canvas, v)
}
}
} else {
val maskedViewsList = findViewWithTagRecursive(
v,
AlfredConstants.SENSITIVE_VIEW_TAG, mutableListOf()
)
withContext(Dispatchers.Main) {
try {
if (maskedViewsList.isEmpty()) {
v.draw(canvas)
} else {
val alphaList = mutableListOf<Pair<View, Float>>()
for (maskedView in maskedViewsList) {
alphaList.add(Pair(maskedView, maskedView.alpha))
maskedView.alpha = 0.0f
}
v.draw(canvas)
for (alphaView in alphaList) {
val view = alphaView.first
view.alpha = alphaView.second
}
alphaList.clear()
}
} catch (e: Exception) {
e.log()
}
v.draw(canvas)
for (alphaView in alphaList) {
val view = alphaView.first
view.alpha = alphaView.second
}
alphaList.clear()
}
} catch (e: Exception) {
e.log()
}
} catch (e: Exception) {
e.log()
}
} else {
withContext(Dispatchers.Main) {
@@ -281,6 +306,39 @@ internal suspend fun captureBottomSheet(
}
}
internal fun captureComposeViewWithMasking(
canvas: Canvas,
sensitiveCoordinates: List<MaskingBounds>,
rootView: View
) {
rootView.draw(canvas)
val paint = Paint()
paint.color = Color.BLACK
sensitiveCoordinates.forEach { bounds ->
if (bounds.bottom > 0 && bounds.top < rootView.height && bounds.height > 0 && bounds.width > 0) {
canvas.drawRect(bounds.left, bounds.top, bounds.right, bounds.bottom, paint)
}
}
}
internal fun blurScreen(
canvas: Canvas,
rootView: View
) {
rootView.draw(canvas)
val paint = Paint()
paint.color = Color.GRAY
paint.alpha = 90
canvas.drawRect(
rootView.left.toFloat(),
rootView.top.toFloat(),
rootView.right.toFloat(),
rootView.bottom.toFloat(),
paint
)
}
@JvmOverloads
internal fun measureInflatedView(view: View, width: Int = 1080, height: Int = 1920) {
view.layoutParams = ViewGroup.LayoutParams(width, height)

View File

@@ -28,13 +28,19 @@ internal suspend fun getPreSignedUrl(
zipFileName: String,
dumpFlow: Boolean = false,
index: Int? = null,
workManagerFlow: Boolean? = false
workManagerFlow: Boolean? = false,
alfredEventIdForDumpFlow: String? = null,
latestScreenshotTimestamp: Long? = null
) {
val currentZipName: String
if (dumpFlow && workManagerFlow == false) {
currentZipName = UUID.randomUUID().toString().plus(AlfredConstants.ALFRED_EVENT_ID)
val latestScreenshotName: Long
if (dumpFlow) {
currentZipName = alfredEventIdForDumpFlow ?: UUID.randomUUID().toString().plus(AlfredConstants.ALFRED_EVENT_ID)
latestScreenshotName = latestScreenshotTimestamp ?: 0L
} else {
currentZipName = AlfredManager.config.getAlfredEventId()
latestScreenshotName = AlfredManager.config.getLatestScreenshotTimestamp()
AlfredManager.config.setAlfredEventId()
}
val bucketKey = currentZipName.plus(AlfredConstants.ZIP_FILE_EXTENSION)
@@ -50,7 +56,8 @@ internal suspend fun getPreSignedUrl(
index,
zipFileName,
workManagerFlow = workManagerFlow,
currentZipName = currentZipName
currentZipName = currentZipName,
latestScreenshotTimestamp = latestScreenshotName
)
}
}
@@ -78,13 +85,15 @@ internal suspend fun getPreSignedUrl(
AlfredManager.config.getAlfredSessionId(),
AlfredManager.config.getSessionStartRecordingTime(),
eventStartRecordingTime,
zipFileName
zipFileName,
currentZipName,
latestScreenshotName
)
}
AlfredManager.config.setEventStartRecordingTime(true)
}
if (workManagerFlow == true) {
uploadWorkManagerFailedZip()
uploadWorkManagerFailedZip(currentZipName, latestScreenshotName)
}
}
}
@@ -108,6 +117,11 @@ internal fun checkAndInitiateFileUploadWorkManager() {
AlfredManager.config.getEventStartRecordingTime() ?: 0L
)
.putString(AlfredConstants.SCREENSHOT_LIST, Gson().toJson(screenShotList))
.putString(AlfredConstants.ALFRED_EVENT_ID, AlfredManager.config.getAlfredEventId())
.putLong(
AlfredConstants.LATEST_SCREENSHOT_TIMESTAMP,
AlfredManager.config.getLatestScreenshotTimestamp()
)
.build()
buildWorkManager(requestData)
}
@@ -149,13 +163,14 @@ internal suspend fun uploadFile(
index: Int? = null,
zipFileName: String,
workManagerFlow: Boolean? = false,
currentZipName: String? = null
currentZipName: String? = "",
latestScreenshotTimestamp: Long? = 0L
) {
val requestBody = uploadFile.asRequestBody("application/zip".toMediaTypeOrNull())
val uploadResponse = AlfredManager.networkRepository.uploadZipToS3(url, requestBody)
if (uploadResponse.isSuccessful && uploadResponse.code() == AlfredConstants.CODE_API_SUCCESS) {
uploadFile.delete()
sendAlfredSessionEvent(dumpFlow, index, currentZipName)
sendAlfredSessionEvent(dumpFlow, index, currentZipName, latestScreenshotTimestamp)
} else {
val failureEvent = buildFailureEvent(
errorType = ZIP_ERROR,
@@ -179,13 +194,15 @@ internal suspend fun uploadFile(
AlfredManager.config.getAlfredSessionId(),
AlfredManager.config.getSessionStartRecordingTime(),
eventStartRecordingTime,
zipFileName
zipFileName,
currentZipName ?: "",
latestScreenshotTimestamp ?: 0L
)
}
AlfredManager.config.setEventStartRecordingTime(true)
}
if (workManagerFlow == true) {
uploadWorkManagerFailedZip()
uploadWorkManagerFailedZip(currentZipName, latestScreenshotTimestamp)
}
}
if (!dumpFlow) {

View File

@@ -90,7 +90,13 @@ internal fun checkDbBeforeStartRecording() {
AlfredManager.applicationContext
) != null
) {
getPreSignedUrl(DumpZipDetailsHelper.zipFileName, true, index)
getPreSignedUrl(
zipFileName = DumpZipDetailsHelper.zipFileName,
dumpFlow = true,
index = index,
alfredEventIdForDumpFlow = DumpZipDetailsHelper.alfredEventId,
latestScreenshotTimestamp = DumpZipDetailsHelper.latestScreenshotTimestamp
)
} else {
AlfredManager.zipDetailsDao.deleteZipFileDetail(DumpZipDetailsHelper.id)
}
@@ -117,7 +123,9 @@ internal suspend fun toZipForWorkManager(
sessionStartRecordingTime: Long? = null,
alfredSessionId: String? = null,
eventStartRecordingTime: Long? = null,
index: Int? = null
index: Int? = null,
alfredEventId: String,
latestScreenshotTimestamp: Long
) {
AlfredManager.sessionIdForCrash = alfredSessionId
AlfredManager.sessionStartRecordingTimeForCrash = sessionStartRecordingTime
@@ -137,7 +145,9 @@ internal suspend fun toZipForWorkManager(
zipFileName,
index = index,
workManagerFlow = true,
dumpFlow = true
dumpFlow = true,
alfredEventIdForDumpFlow = alfredEventId,
latestScreenshotTimestamp = latestScreenshotTimestamp
)
}
}
@@ -146,7 +156,9 @@ internal fun insertZipDetailsToDbForDumpingLater(
alfredSessionId: String,
sessionStartRecordingTime: Long,
eventStartRecordingTime: Long,
zipFileName: String
zipFileName: String,
alfredEventId: String,
latestScreenshotTimestamp: Long
) {
AlfredManager.zipDetailsDao.insert(
data =
@@ -154,7 +166,9 @@ internal fun insertZipDetailsToDbForDumpingLater(
alfredSessionId = alfredSessionId,
sessionStartRecordingTime = sessionStartRecordingTime,
eventStartRecordingTime = eventStartRecordingTime,
zipFileName = zipFileName
zipFileName = zipFileName,
alfredEventId = alfredEventId,
latestScreenshotTimestamp = latestScreenshotTimestamp
)
)
}
@@ -174,7 +188,7 @@ internal fun startAnrCrashZipUpload(view: View) {
}
}
internal fun uploadWorkManagerFailedZip() {
internal fun uploadWorkManagerFailedZip(alfredEventId: String? = null, latestScreenshotTimestamp: Long? = null) {
if (AlfredManager.workFailureData.size > 0) {
val inputData = AlfredManager.workFailureData[0]
if (AlfredManager.zipUploadRetryCount < 3) {
@@ -206,7 +220,9 @@ internal fun uploadWorkManagerFailedZip() {
alfredSessionId = inputData.alfredSessionId.toString(),
eventStartRecordingTime = inputData.eventStartRecordingTime,
sessionStartRecordingTime = inputData.sessionStartRecordingTime,
zipFileName = inputData.zipFileName
zipFileName = inputData.zipFileName,
alfredEventId = alfredEventId.toString(),
latestScreenshotTimestamp = latestScreenshotTimestamp ?: 0L
)
}
}

View File

@@ -43,7 +43,7 @@ class AddEventTask(private val event: AnalyticsEvent, private val context: Conte
private fun checkIfSyncIsNeeded() {
if (eventCount.get() >= AlfredManager.config.getEventBatchSize()) {
resetEventCount()
AlfredDispatcher.executeInNewThread(SyncDataTask(context))
AlfredDispatcher.executeInNewThread(SyncDataTask())
}
}

View File

@@ -43,7 +43,7 @@ class AddFailureTask(private val event: FailureEvent, private val context: Conte
private fun checkIfSyncIsNeeded() {
if (eventCount.get() >= AlfredManager.config.getFailureEventBatchSize()) {
resetEventCount()
AlfredDispatcher.executeInNewThread(SyncFailureTask(context))
AlfredDispatcher.executeInNewThread(SyncFailureTask())
}
}

View File

@@ -43,7 +43,7 @@ class AddMetricTask(private val event: ApiMetricHelper, private val context: Con
private fun checkIfSyncIsNeeded() {
if (eventCount.get() >= AlfredManager.config.getEventBatchSize()) {
resetEventCount()
AlfredDispatcher.executeInNewThread(SyncDataTask(context))
AlfredDispatcher.executeInNewThread(SyncDataTask())
}
}

View File

@@ -43,7 +43,7 @@ class AddNegativeCase(private val negativeCase: NegativeCase, private val contex
private fun checkIfSyncIsNeeded() {
if (eventCount.get() >= AlfredManager.config.getNegativeCaseBatchSize()) {
resetEventCount()
AlfredDispatcher.executeInNewThread(SyncNegativeCaseTask(context))
AlfredDispatcher.executeInNewThread(SyncNegativeCaseTask())
}
}

View File

@@ -7,19 +7,18 @@
package com.navi.alfred.worker
import android.content.Context
import androidx.annotation.WorkerThread
import com.navi.alfred.utils.AlfredConstants.SYNC_EVENT_TASK
import com.navi.alfred.utils.sendEventsToServer
import com.navi.alfred.utils.sendIngestMetric
import kotlinx.coroutines.runBlocking
class SyncDataTask(private val context: Context) : AnalyticsTask {
class SyncDataTask() : AnalyticsTask {
@WorkerThread
override fun execute(): Boolean {
return runBlocking {
sendIngestMetric()
sendEventsToServer(context)
sendEventsToServer()
}
}

View File

@@ -7,17 +7,16 @@
package com.navi.alfred.worker
import android.content.Context
import androidx.annotation.WorkerThread
import com.navi.alfred.utils.AlfredConstants.SYNC_FAILURE_TASK
import com.navi.alfred.utils.sendFailureEventsToServer
import kotlinx.coroutines.runBlocking
class SyncFailureTask(private val context: Context) : AnalyticsTask {
class SyncFailureTask() : AnalyticsTask {
@WorkerThread
override fun execute(): Boolean {
return runBlocking {
sendFailureEventsToServer(context)
sendFailureEventsToServer()
}
}

View File

@@ -7,17 +7,16 @@
package com.navi.alfred.worker
import android.content.Context
import androidx.annotation.WorkerThread
import com.navi.alfred.utils.AlfredConstants.SYNC_NEGATIVE_CASE_TASK
import com.navi.alfred.utils.sendNegativeCaseToServer
import kotlinx.coroutines.runBlocking
class SyncNegativeCaseTask(private val context: Context) : AnalyticsTask {
class SyncNegativeCaseTask() : AnalyticsTask {
@WorkerThread
override fun execute(): Boolean {
return runBlocking {
sendNegativeCaseToServer(context)
sendNegativeCaseToServer()
}
}

View File

@@ -12,7 +12,6 @@ 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.AlfredManager.workFailureData
import com.navi.alfred.db.model.ScreenShotPathHelper
import com.navi.alfred.model.WorkManagerFailureInputData
@@ -21,6 +20,7 @@ import com.navi.alfred.utils.toZipForWorkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.lang.reflect.Type
import java.util.UUID
class UploadFileWorker(context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
@@ -38,8 +38,13 @@ class UploadFileWorker(context: Context, workerParams: WorkerParameters) :
Gson().fromJson(screenShotList.toString(), listType)
val zipFileName =
alfredSessionId +
sessionStartRecordingTime +
AlfredConstants.WORKER_ZIP_FILENAME_ENDPOINT
sessionStartRecordingTime +
AlfredConstants.WORKER_ZIP_FILENAME_ENDPOINT
val alfredEventId =
inputData.getString(AlfredConstants.ALFRED_EVENT_ID) ?: UUID.randomUUID().toString()
.plus(AlfredConstants.ALFRED_EVENT_ID)
val latestScreenshotTimestamp =
inputData.getLong(AlfredConstants.LATEST_SCREENSHOT_TIMESTAMP, 0L)
workFailureData.add(
WorkManagerFailureInputData(
alfredSessionId = alfredSessionId,
@@ -47,23 +52,27 @@ class UploadFileWorker(context: Context, workerParams: WorkerParameters) :
eventStartRecordingTime,
screenShots,
id,
zipFileName
zipFileName,
alfredEventId,
latestScreenshotTimestamp
)
)
try {
if (
alfredSessionId == null ||
sessionStartRecordingTime == 0L ||
eventStartRecordingTime == 0L
sessionStartRecordingTime == 0L ||
eventStartRecordingTime == 0L
) {
Result.failure()
} else {
toZipForWorkManager(
screenShots,
zipFileName,
sessionStartRecordingTime,
alfredSessionId,
eventStartRecordingTime
imagePathList = screenShots,
zipFileName = zipFileName,
sessionStartRecordingTime = sessionStartRecordingTime,
alfredSessionId = alfredSessionId,
eventStartRecordingTime = eventStartRecordingTime,
alfredEventId = alfredEventId,
latestScreenshotTimestamp = latestScreenshotTimestamp
)
Result.success()
}