TP-67637 | PixelCopy Capture (#195)

Co-authored-by: Varun Jain <varun.jain@navi.com>
This commit is contained in:
Sayed Owais Ali
2024-06-26 13:49:06 +05:30
committed by GitHub
parent abdb4380c8
commit 0db7eb2db4
21 changed files with 540 additions and 299 deletions

View File

@@ -43,7 +43,6 @@ import com.navi.alfred.utils.ScreenShotStorageHelper
import com.navi.alfred.utils.buildAppPerformanceEvent
import com.navi.alfred.utils.buildEvent
import com.navi.alfred.utils.buildNegativeCaseEvent
import com.navi.alfred.utils.captureBottomSheet
import com.navi.alfred.utils.captureScreen
import com.navi.alfred.utils.captureScreenshotOfCustomView
import com.navi.alfred.utils.checkDbAndDeleteCorruptScreenshots
@@ -78,7 +77,8 @@ object AlfredManager {
private val exceptionHandler = CoroutineExceptionHandler { _, _ -> }
private var previousTouchEvent: NaviMotionEvent = NaviMotionEvent()
private var screenShotCaptureDelay: Long = 1000L
private var reactBottomSheetView: WeakReference<View>? = null
private var screenShotTimerStartDelay: Long = 500L
internal var reactBottomSheetView: WeakReference<View>? = null
private var screenShotTimer: Timer? = null
private var viewLayoutDelay: Long = 1000
internal val mutex = Mutex()
@@ -113,6 +113,11 @@ object AlfredManager {
internal var hasCheckedDb: Boolean = true
internal var criticalJourneyResponseCode: Int = 0
internal var criticalJourneyResponseMessage: String = ""
var bmpForCanvas: Pair<Canvas, Bitmap>? = null
private var isCruiseApiCalled: Boolean = false
private var isActivityResumed: Boolean = false
internal var currentView: WeakReference<View>? = null
internal var bmpForThirdPartySdkScreen: Bitmap? = null
fun init(
config: AlfredConfig,
@@ -133,18 +138,32 @@ object AlfredManager {
return config.getAlfredStatus() && config.getEnableRecordingStatus()
}
fun startRecording(
context: Context,
view: View,
fun onActivityResumed(
screenName: String? = null,
moduleName: String,
activity: Activity? = null
activity: Activity? = null,
applicationContext: Context
) {
isActivityResumed = true
if (isAlfredRecordingEnabled() || !isCruiseApiCalled) {
currentActivity = WeakReference(activity)
setCurrentScreenName(screenName)
currentModuleName = moduleName
currentView = WeakReference(activity?.window?.decorView?.rootView)
if (moduleName == THIRD_PARTY_MODULE) {
handleScreenTransitionEvent(activity?.localClassName.toString(), moduleName)
}
}
if (isCruiseApiCalled && isAlfredRecordingEnabled()) {
initAlfredRecording(applicationContext)
startRecording()
}
}
private fun initAlfredRecording(context: Context) {
if (config.getEnableRecordingStatus().not()) {
return
}
screenShotTimer?.cancel()
screenShotTimer = Timer()
if (!hasRecordingStarted) {
checkDbAndDeleteCorruptScreenshots()
config.setAlfredSessionId()
@@ -155,28 +174,32 @@ object AlfredManager {
val startRecordingEvent =
buildEvent(
AlfredConstants.START_RECORDING_EVENT,
screenName = screenName,
moduleName = moduleName
screenName = currentScreenName,
moduleName = currentModuleName
)
AlfredDispatcher.addTaskToQueue(AddEventTask(startRecordingEvent, context))
hasRecordingStarted = true
hasCheckedDb = false
}
currentActivity = WeakReference(activity)
setCurrentScreenName(screenName)
currentModuleName = moduleName
hasRecordingStarted = true
var bmpForCanvas: Pair<Canvas, Bitmap>? = null
var bmpForThirdPartySdkScreen: Bitmap? = null
if (moduleName == THIRD_PARTY_MODULE) {
handleScreenTransitionEvent(activity?.localClassName.toString(), moduleName)
}
private fun startRecording() {
if (
config.getEnableRecordingStatus().not() ||
currentActivity == null ||
currentScreenName == null ||
currentModuleName == null
) {
return
}
screenShotTimer?.cancel()
screenShotTimer = Timer()
val timerTask: TimerTask =
object : TimerTask() {
override fun run() {
coroutineScope.launch(Dispatchers.IO) {
if (moduleName == THIRD_PARTY_MODULE) {
currentScreenName = activity?.localClassName.toString()
if (isScreenDisabled(currentScreenName, moduleName)) {
coroutineScope.launch {
if (currentModuleName == THIRD_PARTY_MODULE) {
if (isScreenDisabled(currentScreenName, currentModuleName)) {
if (bmpForThirdPartySdkScreen == null) {
val thirdPartyScreenView =
LayoutInflater.from(applicationContext)
@@ -184,7 +207,7 @@ object AlfredManager {
measureInflatedView(thirdPartyScreenView)
thirdPartyScreenView
.findViewById<AppCompatTextView>(R.id.tv_third_party_name)
.text = screenName
.text = currentScreenName
bmpForThirdPartySdkScreen =
thirdPartyScreenView?.let {
captureScreenshotOfCustomView(it)
@@ -198,18 +221,20 @@ object AlfredManager {
} else {
if (bmpForCanvas == null) {
delay(viewLayoutDelay)
bmpForCanvas = createBitmapForView(view)
bmpForCanvas =
currentView?.get()?.let { createBitmapForView(it) }
}
try {
captureScreen(
view,
context,
screenName = screenName,
scope = coroutineScope,
canvas = bmpForCanvas?.first,
bmp = bmpForCanvas?.second,
moduleName = moduleName
)
currentView?.get()?.let { view ->
captureScreen(
view,
screenName = currentScreenName,
canvas = bmpForCanvas?.first,
bmp = bmpForCanvas?.second,
moduleName = currentModuleName,
activity = currentActivity?.get()
)
}
} catch (e: Exception) {
e.log()
}
@@ -217,31 +242,17 @@ object AlfredManager {
} else {
if (bmpForCanvas == null) {
delay(viewLayoutDelay)
bmpForCanvas = createBitmapForView(view)
bmpForCanvas = currentView?.get()?.let { createBitmapForView(it) }
}
try {
val bottomSheetView =
reactBottomSheetView?.get()
?: dialog?.window?.decorView?.rootView
if (bottomSheetView != null) {
captureBottomSheet(
view,
bottomSheetView,
context,
screenName,
bmpForCanvas?.first,
rootBmp = bmpForCanvas?.second,
moduleName = moduleName
)
} else {
currentView?.get()?.let { view ->
captureScreen(
view,
context,
screenName = screenName,
scope = coroutineScope,
screenName = currentScreenName,
canvas = bmpForCanvas?.first,
bmp = bmpForCanvas?.second,
moduleName = moduleName
moduleName = currentModuleName,
activity = currentActivity?.get()
)
}
} catch (e: Exception) {
@@ -253,13 +264,14 @@ object AlfredManager {
}
}
screenShotCaptureDelay = (1000 / config.getSnapshotPerSecond().toLong())
screenShotTimer?.schedule(timerTask, 0, screenShotCaptureDelay)
screenShotTimer?.schedule(timerTask, screenShotTimerStartDelay, screenShotCaptureDelay)
}
fun stopRecording() {
if (isAlfredRecordingEnabled()) {
isAppInBackground = true
hasRecordingStarted = false
isActivityResumed = false
screenShotTimer?.cancel()
isCriticalUserJourneyActive.set(false)
val appBackgroundView =
@@ -280,25 +292,34 @@ object AlfredManager {
}
fun getAlfredCruiseInfo(cruiseApiSuccessful: (response: CruiseResponse) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
try {
getCruiseConfig(
cruiseApiSuccessful = { response ->
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()
cruiseApiSuccessful(response)
}
)
} catch (e: Exception) {
e.log()
if (!isCruiseApiCalled) {
coroutineScope.launch {
try {
getCruiseConfig(
cruiseApiSuccessful = { response ->
isCruiseApiCalled = true
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()
cruiseApiSuccessful(response)
if (isAlfredRecordingEnabled() && isActivityResumed) {
if (!hasRecordingStarted) {
initAlfredRecording(context = applicationContext)
}
startRecording()
}
}
)
} catch (e: Exception) {
e.log()
}
}
}
}
@@ -399,7 +420,7 @@ object AlfredManager {
}
fun handleScreenTransitionEvent(screenName: String?, moduleName: String? = null) {
if (isAlfredRecordingEnabled()) {
if (isAlfredRecordingEnabled() && screenName != null && moduleName != null) {
if (config.getAlfredSessionId().isNotEmpty()) {
coroutineDispatcher.executor.execute {
try {

View File

@@ -0,0 +1,68 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.alfred.utils
import android.graphics.Bitmap
import android.graphics.Canvas
import android.view.View
import com.navi.alfred.AlfredManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal suspend fun getScreenShotUsingCanvasDraw(
view: View,
bitmap: Bitmap,
canvas: Canvas,
shouldMaskComposeScreen: Boolean? = false,
shouldMaskXmlScreen: Boolean? = false
) {
try {
withContext(Dispatchers.Main) { view.draw(canvas) }
if (shouldMaskComposeScreen == true) {
val sensitiveCoordinates = AlfredManager.sensitiveComposeRepository.sensitiveCoordinates
captureComposeViewWithMasking(
canvas = canvas,
rootView = view,
sensitiveCoordinates = sensitiveCoordinates
)
}
if (shouldMaskXmlScreen == true) {
captureXmlViewWithMasking(canvas = canvas, view = view)
}
val bottomSheetView =
AlfredManager.reactBottomSheetView?.get()
?: AlfredManager.dialog?.window?.decorView?.rootView
if (bottomSheetView != null && !AlfredManager.config.getDisableDialogScreenShot()) {
val bottomSheetCanvasForBitmap = createBitmapForView(bottomSheetView)
withContext(Dispatchers.Main) {
bottomSheetCanvasForBitmap?.first?.let { bottomSheetView.draw(it) }
}
if (bottomSheetCanvasForBitmap?.second != null) {
combineScreenshots(
backgroundScreenshot = bitmap,
bottomSheetScreenshot = bottomSheetCanvasForBitmap.second,
context = AlfredManager.applicationContext,
moduleName = AlfredManager.currentModuleName,
screenName = AlfredManager.currentScreenName,
scope = AlfredManager.coroutineScope
)
}
} else {
insertScreenShotPathInDb(
scope = AlfredManager.coroutineScope,
context = AlfredManager.applicationContext,
bitmap = bitmap,
bottomSheetFlow = false
)
}
} catch (e: Exception) {
e.log()
}
}

View File

@@ -47,7 +47,6 @@ import java.lang.reflect.Type
import java.util.UUID
import kotlin.concurrent.fixedRateTimer
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.withLock
internal suspend fun sendEventsToServer(
@@ -498,7 +497,7 @@ internal fun startSyncEvents() {
AlfredConstants.DEFAULT_INITIAL_DELAY,
AlfredManager.config.getEventsDelayInMilliseconds()
) {
runBlocking {
AlfredManager.coroutineScope.launch {
AddMetricTask.resetEventCount()
sendIngestMetric()
AddEventTask.resetEventCount()

View File

@@ -10,20 +10,19 @@ package com.navi.alfred.utils
import android.view.View
import android.view.ViewGroup
internal fun findViewWithTagRecursive(
view: View,
tag: String,
maskedViews: MutableList<View>
): List<View> {
internal fun findViewWithTagRecursive(view: View, tag: String, maskedViews: MutableList<View>) {
if (view.tag == tag) {
maskedViews.add(view)
return
}
if (view is ViewGroup) {
for (i in 0 until view.childCount) {
val childView = view.getChildAt(i)
findViewWithTagRecursive(childView, tag, maskedViews)
if (childView != null) {
findViewWithTagRecursive(childView, tag, maskedViews)
}
}
}
return maskedViews
return
}

View File

@@ -0,0 +1,230 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.alfred.utils
import android.app.Activity
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.view.PixelCopy
import android.view.View
import com.navi.alfred.AlfredManager
import com.navi.alfred.model.MaskingBounds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
fun getScreenShotUsingPixelCopy(
view: View,
bitmap: Bitmap?,
isBottomSheet: Boolean? = false,
activity: Activity?,
shouldMaskComposeScreen: Boolean? = false,
shouldMaskXmlScreen: Boolean? = false,
bitmapForBackground: Bitmap? = null,
canvas: Canvas
) {
if (isViewCaptureNotReady(activity, view)) return
val handlerThread = HandlerThread("PixelCopyThread")
handlerThread.start()
val captureWindow =
if (isBottomSheet == false) {
activity?.window
} else {
AlfredManager.dialog?.window
}
captureWindow?.let { window ->
val locationOfViewInWindow = IntArray(2)
view.getLocationInWindow(locationOfViewInWindow)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (bitmap != null) {
var sensitiveCoordinates: Map<String, MaskingBounds> = emptyMap()
if (shouldMaskComposeScreen == true) {
sensitiveCoordinates =
AlfredManager.sensitiveComposeRepository.sensitiveCoordinates
}
PixelCopy.request(
window,
Rect(
locationOfViewInWindow[0],
locationOfViewInWindow[1],
locationOfViewInWindow[0] + view.width,
locationOfViewInWindow[1] + view.height
),
bitmap,
{ copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
if (shouldMaskComposeScreen == true) {
captureComposeViewWithMasking(
canvas,
view,
sensitiveCoordinates
)
}
onPixelCopySuccess(
bitmap = bitmap,
isBottomSheetCaptured = isBottomSheet,
activity = activity,
scope = AlfredManager.coroutineScope,
bitmapForBackground = bitmapForBackground,
shouldMaskComposeScreen = shouldMaskComposeScreen,
shouldMaskXmlScreen = shouldMaskXmlScreen,
canvas = canvas,
view = view
)
} else {
onPixelCopyError()
}
handlerThread.quitSafely()
},
Handler(handlerThread.looper)
)
}
}
} catch (e: IllegalArgumentException) {
e.log()
}
}
}
fun onPixelCopySuccess(
bitmap: Bitmap?,
isBottomSheetCaptured: Boolean?,
activity: Activity?,
scope: CoroutineScope,
bitmapForBackground: Bitmap?,
shouldMaskComposeScreen: Boolean? = false,
shouldMaskXmlScreen: Boolean? = false,
canvas: Canvas,
view: View
) {
if (shouldMaskComposeScreen == true) {
val sensitiveCoordinates = AlfredManager.sensitiveComposeRepository.sensitiveCoordinates
captureComposeViewWithMasking(canvas, view, sensitiveCoordinates)
}
if (shouldMaskXmlScreen == true) {
captureXmlViewWithMasking(view, canvas)
}
if (isBottomSheetCaptured == true) {
combineScreenshots(
backgroundScreenshot = bitmapForBackground,
bottomSheetScreenshot = bitmap,
context = AlfredManager.applicationContext,
moduleName = AlfredManager.currentModuleName,
screenName = AlfredManager.currentScreenName,
scope = scope
)
} else {
val bottomSheetView =
AlfredManager.reactBottomSheetView?.get()
?: AlfredManager.dialog?.window?.decorView?.rootView
if (!AlfredManager.config.getDisableDialogScreenShot() && bottomSheetView != null) {
captureBottomSheetUsingPixelCopy(
bitmap = bitmap,
activity = activity,
bottomSheetView = bottomSheetView,
canvas = canvas
)
} else {
insertScreenShotPathInDb(
scope = scope,
context = AlfredManager.applicationContext,
bitmap = bitmap,
bottomSheetFlow = false
)
}
}
}
fun captureBottomSheetUsingPixelCopy(
bitmap: Bitmap?,
activity: Activity?,
bottomSheetView: View,
canvas: Canvas
) {
val bottomSheetCanvasForBitmap = createBitmapForView(bottomSheetView)
getScreenShotUsingPixelCopy(
view = bottomSheetView,
bitmap = bottomSheetCanvasForBitmap?.second,
isBottomSheet = true,
activity = activity,
bitmapForBackground = bitmap,
canvas = canvas
)
}
fun onPixelCopyError() {
return
}
internal fun captureComposeViewWithMasking(
canvas: Canvas,
rootView: View,
sensitiveCoordinates: Map<String, MaskingBounds>
) {
try {
val paint = Paint()
paint.color = Color.BLACK
if (sensitiveCoordinates.isNotEmpty()) {
sensitiveCoordinates.forEach { (key, 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)
}
}
}
} catch (e: Exception) {
e.log()
}
}
internal fun captureXmlViewWithMasking(view: View, canvas: Canvas) {
runBlocking {
try {
val maskedViewsList: MutableList<View> = mutableListOf()
findViewWithTagRecursive(view, AlfredConstants.SENSITIVE_VIEW_TAG, maskedViewsList)
val sensitiveCoordinatesList: MutableList<MaskingBounds> = mutableListOf()
maskedViewsList.forEach { view ->
val bounds =
MaskingBounds(
left = view.left.toFloat(),
top = view.top.toFloat(),
right = view.right.toFloat(),
bottom = view.bottom.toFloat()
)
sensitiveCoordinatesList.add(bounds)
}
sensitiveCoordinatesList.forEach {
canvas.drawRect(
it.left,
it.top,
it.right,
it.bottom,
Paint().apply { color = Color.BLACK }
)
}
} catch (e: Exception) {
e.log()
}
}
}

View File

@@ -7,14 +7,17 @@
package com.navi.alfred.utils
import android.app.Activity
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.os.Build
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.navi.alfred.AlfredManager
import com.navi.alfred.db.AlfredDatabaseHelper
import com.navi.alfred.db.model.ScreenShotPathHelper
@@ -24,7 +27,6 @@ import java.io.IOException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
internal suspend fun handleScreenShot() {
if (
@@ -133,7 +135,7 @@ internal fun insertScreenShotPathInDb(
)
ScreenShotStorageHelper.addImage(path)
} catch (e: Exception) {
e.printStackTrace()
e.log()
}
}
} catch (e: Exception) {
@@ -203,117 +205,95 @@ enum class VideoQuality {
internal suspend fun captureScreen(
v: View,
context: Context,
bottomSheetFlow: Boolean? = false,
screenName: String? = null,
scope: CoroutineScope,
canvas: Canvas? = null,
bmp: Bitmap? = null,
moduleName: String? = null
moduleName: String? = null,
activity: Activity? = null
): Bitmap? {
if (
isScreenDisabled(screenName, moduleName) ||
isScreenDisabled(AlfredManager.currentScreenName, moduleName) ||
canvas == null ||
bmp == null
bmp == null ||
activity == null
) {
return null
}
withContext(Dispatchers.Main) {
val rootView = AlfredManager.sensitiveComposeRepository.getRootViewOfComposeScreen()
if (isMaskingEnabled(screenName)) {
AlfredManager.coroutineScope.launch {
if (isMaskingEnabled(screenName) || isMaskingEnabled(AlfredManager.currentScreenName)) {
try {
val rootView = AlfredManager.sensitiveComposeRepository.getRootViewOfComposeScreen()
if (rootView != null) {
if (AlfredManager.sensitiveComposeRepository.getBlurSensitiveScreenStatus()) {
blurScreen(canvas, rootView)
} else {
captureComposeViewWithMasking(canvas, rootView)
captureScreenManager(
view = rootView,
bitmap = bmp,
canvas = canvas,
shouldMaskComposeScreen = true,
activity = activity
)
}
} else {
if (v.tag == AlfredConstants.SENSITIVE_VIEW_TAG) {
captureXmlViewWithMasking(canvas, v)
captureScreenManager(
view = v,
bitmap = bmp,
canvas = canvas,
shouldMaskXmlScreen = true,
activity = activity
)
} else {
v.draw(canvas)
captureScreenManager(
view = v,
bitmap = bmp,
canvas = canvas,
activity = activity
)
}
}
} catch (e: Exception) {
e.log()
}
} else {
v.draw(canvas)
captureScreenManager(view = v, bitmap = bmp, canvas = canvas, activity = activity)
}
}
insertScreenShotPathInDb(scope, context, bmp, bottomSheetFlow)
return bmp
}
internal suspend fun captureBottomSheet(
internal suspend fun captureScreenManager(
view: View,
bottomSheetView: View,
context: Context,
screenName: String? = null,
rootCanvas: Canvas? = null,
rootBmp: Bitmap? = null,
moduleName: String? = null
bitmap: Bitmap,
canvas: Canvas,
shouldMaskComposeScreen: Boolean? = false,
shouldMaskXmlScreen: Boolean? = false,
activity: Activity?
) {
if (AlfredManager.config.getDisableDialogScreenShot()) {
return
}
val bottomSheetCanvasForBitmap = createBitmapForView(bottomSheetView)
val bottomSheetScreenShot =
captureScreen(
bottomSheetView,
context,
true,
screenName,
AlfredManager.coroutineScope,
bottomSheetCanvasForBitmap?.first,
bottomSheetCanvasForBitmap?.second,
moduleName = moduleName
)
val backgroundScreenShot =
captureScreen(
view,
context,
true,
screenName,
AlfredManager.coroutineScope,
rootCanvas,
rootBmp,
moduleName = moduleName
)
if (bottomSheetScreenShot != null && backgroundScreenShot != null) {
combineScreenshots(
backgroundScreenShot,
bottomSheetScreenShot,
context,
screenName,
moduleName = moduleName,
scope = AlfredManager.coroutineScope
)
}
}
internal fun captureComposeViewWithMasking(canvas: Canvas, rootView: View) {
rootView.draw(canvas)
val sensitiveCoordinates = AlfredManager.sensitiveComposeRepository.sensitiveCoordinates
val paint = Paint()
paint.color = Color.BLACK
if (sensitiveCoordinates.isNotEmpty()) {
sensitiveCoordinates.forEach { (key, 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)
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getScreenShotUsingPixelCopy(
view = view,
bitmap = bitmap,
activity = activity,
canvas = canvas,
shouldMaskComposeScreen = shouldMaskComposeScreen,
shouldMaskXmlScreen = shouldMaskXmlScreen
)
} else {
getScreenShotUsingCanvasDraw(
view = view,
bitmap = bitmap,
canvas = canvas,
shouldMaskComposeScreen = shouldMaskComposeScreen,
shouldMaskXmlScreen = shouldMaskXmlScreen
)
}
} catch (e: Exception) {
e.log()
}
}
@@ -330,33 +310,6 @@ internal fun blurScreen(canvas: Canvas, rootView: View) {
)
}
internal fun captureXmlViewWithMasking(canvas: Canvas, v: View) {
val maskedViewsList =
findViewWithTagRecursive(v, AlfredConstants.SENSITIVE_VIEW_TAG, mutableListOf())
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()
}
}
@JvmOverloads
internal fun measureInflatedView(view: View, width: Int = 1080, height: Int = 1920) {
view.layoutParams = ViewGroup.LayoutParams(width, height)
@@ -386,3 +339,13 @@ internal fun createBitmapForView(view: View): Pair<Canvas, Bitmap>? {
internal fun isResolutionEven(width: Int, height: Int): Boolean {
return width.mod(2) == 0 && height.mod(2) == 0
}
fun isViewCaptureNotReady(activity: Activity?, view: View): Boolean {
return activity == null ||
!view.isVisible ||
!view.isAttachedToWindow ||
view.width == 0 ||
view.height == 0 ||
activity.isFinishing ||
activity.isDestroyed
}

View File

@@ -226,7 +226,7 @@ internal suspend fun uploadFile(
}
}
internal fun buildWorkManagerForZipUploadRetry(requestData: Data) {
internal fun buildWorkManagerForZipUploadRetry() {
val constraints =
Constraints.Builder()
.setRequiresBatteryNotLow(false)
@@ -239,19 +239,13 @@ internal fun buildWorkManagerForZipUploadRetry(requestData: Data) {
.get()
if (uniqueWorkStatus.isNullOrEmpty()) {
val uniqueWorkRequest =
OneTimeWorkRequestBuilder<ZipUploadRetryWorker>()
.setConstraints(constraints)
.setInputData(requestData)
.build()
OneTimeWorkRequestBuilder<ZipUploadRetryWorker>().setConstraints(constraints).build()
WorkManager.getInstance(AlfredManager.applicationContext)
.beginUniqueWork(uniqueWorkName, ExistingWorkPolicy.KEEP, uniqueWorkRequest)
.enqueue()
} else {
val workRequest =
OneTimeWorkRequestBuilder<ZipUploadRetryWorker>()
.setConstraints(constraints)
.setInputData(requestData)
.build()
OneTimeWorkRequestBuilder<ZipUploadRetryWorker>().setConstraints(constraints).build()
WorkManager.getInstance(AlfredManager.applicationContext).enqueue(workRequest)
}
}

View File

@@ -12,7 +12,6 @@ import android.graphics.Bitmap
import android.view.View
import androidx.work.Data
import androidx.work.WorkManager
import com.google.gson.Gson
import com.navi.alfred.AlfredManager
import com.navi.alfred.db.model.ScreenShotPathHelper
import com.navi.alfred.db.model.ZipDetailsHelper
@@ -218,15 +217,7 @@ internal fun startAnrCrashZipUpload(view: View) {
}
AlfredManager.coroutineScope.launch(Dispatchers.IO) {
if (AlfredManager.zipDetailsDao.getZipFilesDetailsCount() > 0) {
val zipFileDetailList = AlfredManager.zipDetailsDao.fetchAllZipFilesDetails()
val requestData =
Data.Builder()
.putString(
AlfredConstants.ZIP_FILE_DETAIL_LIST,
Gson().toJson(zipFileDetailList)
)
.build()
buildWorkManagerForZipUploadRetry(requestData)
buildWorkManagerForZipUploadRetry()
}
}
}

View File

@@ -27,7 +27,7 @@ class AddEventTask(private val event: AnalyticsEvent, private val context: Conte
}
@WorkerThread
override fun execute(): Boolean {
override suspend fun execute(): Boolean {
val db = AlfredDatabaseHelper.getAnalyticsDatabase(context)
val analyticsDao = db.analyticsDao()
return try {

View File

@@ -27,7 +27,7 @@ class AddFailureTask(private val event: FailureEvent, private val context: Conte
}
@WorkerThread
override fun execute(): Boolean {
override suspend fun execute(): Boolean {
val db = AlfredDatabaseHelper.getAnalyticsDatabase(context)
val failureDao = db.failureEventDao()
return try {

View File

@@ -27,7 +27,7 @@ class AddMetricTask(private val event: ApiMetricHelper, private val context: Con
}
@WorkerThread
override fun execute(): Boolean {
override suspend fun execute(): Boolean {
val db = AlfredDatabaseHelper.getAnalyticsDatabase(context)
val analyticsDao = db.apiMetricDao()
return try {

View File

@@ -27,7 +27,7 @@ class AddNegativeCase(private val negativeCase: NegativeCase, private val contex
}
@WorkerThread
override fun execute(): Boolean {
override suspend fun execute(): Boolean {
val db = AlfredDatabaseHelper.getAnalyticsDatabase(context)
val negativeCaseDao = db.negativeCaseDao()
return try {

View File

@@ -10,7 +10,7 @@ package com.navi.alfred.worker
import androidx.annotation.WorkerThread
interface AnalyticsTask {
@WorkerThread fun execute(): Boolean
@WorkerThread suspend fun execute(): Boolean
fun getTaskName(): String
}

View File

@@ -7,10 +7,12 @@
package com.navi.alfred.worker
import com.navi.alfred.AlfredManager
import java.util.concurrent.BlockingDeque
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingDeque
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
class AnalyticsTaskProcessor {
private val taskQueue: BlockingDeque<AnalyticsTask> = LinkedBlockingDeque<AnalyticsTask>()
@@ -43,14 +45,16 @@ class AnalyticsTaskProcessor {
if (taskQueue.poll().also { active = it } != null) {
coroutineDispatcher.executor.execute {
active?.let {
executeTask(it)
scheduleNext()
AlfredManager.coroutineScope.launch {
executeTask(it)
scheduleNext()
}
}
}
}
}
private fun executeTask(task: AnalyticsTask) {
private suspend fun executeTask(task: AnalyticsTask) {
task.execute()
}
@@ -58,8 +62,10 @@ class AnalyticsTaskProcessor {
if (isSyncEventTaskExecuted) {
isSyncEventTaskExecuted = false
singleThreadExecutorForSync.execute {
task.execute()
isSyncEventTaskExecuted = true
AlfredManager.coroutineScope.launch {
task.execute()
isSyncEventTaskExecuted = true
}
}
}
}

View File

@@ -11,15 +11,11 @@ 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() : AnalyticsTask {
@WorkerThread
override fun execute(): Boolean {
return runBlocking {
sendIngestMetric()
sendEventsToServer()
}
override suspend fun execute(): Boolean {
return sendEventsToServer() && sendIngestMetric()
}
override fun getTaskName(): String {

View File

@@ -10,12 +10,11 @@ package com.navi.alfred.worker
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() : AnalyticsTask {
@WorkerThread
override fun execute(): Boolean {
return runBlocking { sendFailureEventsToServer() }
override suspend fun execute(): Boolean {
return sendFailureEventsToServer()
}
override fun getTaskName(): String {

View File

@@ -10,12 +10,11 @@ package com.navi.alfred.worker
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() : AnalyticsTask {
@WorkerThread
override fun execute(): Boolean {
return runBlocking { sendNegativeCaseToServer() }
override suspend fun execute(): Boolean {
return sendNegativeCaseToServer()
}
override fun getTaskName(): String {

View File

@@ -53,7 +53,6 @@ class UploadEventsWorker(context: Context, workerParams: WorkerParameters) :
Result.success()
}
} catch (e: Exception) {
e.printStackTrace()
Result.retry()
}
}

View File

@@ -97,7 +97,6 @@ class UploadFileWorker(context: Context, workerParams: WorkerParameters) :
Result.success()
}
} catch (e: Exception) {
e.printStackTrace()
Result.retry()
}
}

View File

@@ -11,13 +11,12 @@ import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
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.AlfredDatabaseHelper
import com.navi.alfred.db.model.ZipDetailsHelper
import com.navi.alfred.utils.AlfredConstants
import com.navi.alfred.utils.buildWorkManagerForZip
import com.navi.alfred.utils.sendAlfredSessionEvent
import java.lang.reflect.Type
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -27,14 +26,11 @@ class ZipUploadRetryWorker(context: Context, workerParams: WorkerParameters) :
override suspend fun doWork(): Result =
withContext(Dispatchers.IO) {
try {
val zipDetailList = inputData.getString(AlfredConstants.ZIP_FILE_DETAIL_LIST)
val listType: Type = object : TypeToken<List<ZipDetailsHelper?>?>() {}.type
AlfredManager.alfredDataBase =
AlfredDatabaseHelper.getAnalyticsDatabase(AlfredManager.applicationContext)
AlfredManager.zipDetailsDao = AlfredManager.alfredDataBase.zipDetailsDao()
val zipFileDetailList: List<ZipDetailsHelper> =
if (zipDetailList != null) {
Gson().fromJson(zipDetailList, listType)
} else {
emptyList()
}
AlfredManager.zipDetailsDao.fetchAllZipFilesDetails()
zipFileDetailList.forEach {
if (it.zipUploadStatus) {
@@ -62,7 +58,6 @@ class ZipUploadRetryWorker(context: Context, workerParams: WorkerParameters) :
}
Result.success()
} catch (e: Exception) {
e.printStackTrace()
Result.retry()
}
}