From f85a95059213a30b3b170584f9876ed5479fbc1a Mon Sep 17 00:00:00 2001 From: xuduo Date: Mon, 11 Dec 2023 23:30:46 +0900 Subject: [PATCH] improve stability --- .../TouchAccessibilityService.kt | 26 +----- .../testrecorder/data/ActionCodeConverter.kt | 2 + .../com/xd/testrecorder/data/ActionImage.kt | 9 +- .../xd/testrecorder/overlay/OverlayService.kt | 85 ++++++++----------- .../testrecorder/recorder/RecorderService.kt | 12 ++- .../recording/RecordingViewModel.kt | 4 +- .../xd/testrecorder/service/BaseService.kt | 15 ++++ .../res/xml/accessibility_service_config.xml | 2 +- 8 files changed, 66 insertions(+), 89 deletions(-) create mode 100644 model/src/main/java/com/xd/testrecorder/service/BaseService.kt diff --git a/model/src/main/java/com/xd/testrecorder/accessibility/TouchAccessibilityService.kt b/model/src/main/java/com/xd/testrecorder/accessibility/TouchAccessibilityService.kt index fd9213d..00ab9db 100644 --- a/model/src/main/java/com/xd/testrecorder/accessibility/TouchAccessibilityService.kt +++ b/model/src/main/java/com/xd/testrecorder/accessibility/TouchAccessibilityService.kt @@ -11,6 +11,7 @@ import android.view.accessibility.AccessibilityNodeInfo import androidx.core.graphics.toRect import com.xd.common.logger.Logger import com.xd.testrecorder.overlay.OverlayService +import com.xd.testrecorder.service.serviceFailed import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -25,10 +26,6 @@ class TouchAccessibilityService : AccessibilityService() { companion object { var service: TouchAccessibilityService? = null - fun isTargetPackage(): Boolean { - return service?.recordingPackageName == service?.foregroundPackageName - } - fun getStatusBarHeight(): Int { return service?.getStatusBarHeight() ?: 0 } @@ -69,10 +66,10 @@ class TouchAccessibilityService : AccessibilityService() { override fun onDestroy() { super.onDestroy() - logger.d("onDestroy") + logger.i("onDestroy") service = null recordingPackageName = "not.recording" - OverlayService.service?.adjustPassThrough() + OverlayService.service?.serviceFailed() } override fun onAccessibilityEvent(event: AccessibilityEvent?) { @@ -83,23 +80,6 @@ class TouchAccessibilityService : AccessibilityService() { ) }" ) - if (event.eventType == AccessibilityEvent.TYPE_TOUCH_INTERACTION_START) { - // タッチイベントが開始した時の処理 - logger.d("TYPE_TOUCH_INTERACTION_START $event") - } - - if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - if (event.packageName != null) { - foregroundPackageName = event.packageName.toString() - } - logger.i("TYPE_WINDOW_STATE_CHANGED $foregroundPackageName") - OverlayService.service?.adjustPassThrough() - } - - if (event.eventType == AccessibilityEvent.TYPE_TOUCH_INTERACTION_END) { - // タッチイベントが終了した時の処理 - logger.d("TYPE_TOUCH_INTERACTION_END $event") - } } fun getViewForGesture(gesture: GestureDescription?): Pair { diff --git a/model/src/main/java/com/xd/testrecorder/data/ActionCodeConverter.kt b/model/src/main/java/com/xd/testrecorder/data/ActionCodeConverter.kt index 2f8862c..8fab218 100644 --- a/model/src/main/java/com/xd/testrecorder/data/ActionCodeConverter.kt +++ b/model/src/main/java/com/xd/testrecorder/data/ActionCodeConverter.kt @@ -12,6 +12,8 @@ sealed class ActionCodeConverter { code = "clickContentDescription(\"${action.viewContentDescription}\")" } else if (action.viewText.isNotEmpty()) { code = "clickText(\"${action.viewText}\")" + } else{ + code = "// no ContentDescription or Text detected" } return code } diff --git a/model/src/main/java/com/xd/testrecorder/data/ActionImage.kt b/model/src/main/java/com/xd/testrecorder/data/ActionImage.kt index 43df3a9..09320c5 100644 --- a/model/src/main/java/com/xd/testrecorder/data/ActionImage.kt +++ b/model/src/main/java/com/xd/testrecorder/data/ActionImage.kt @@ -32,7 +32,7 @@ data class ActionImage( ) fun convertImageToByteArray(image: Image, cropTop: Int = 0, cropHeight: Int = 0): ByteArray { - Log.d("convertImageToByteArray", "convertImageToByteArray ${image.width} ${image.height}") + Log.d("convertImageToByteArray", "convertImageToByteArray ${image.width} ${image.height} $cropTop") val originalBitmap = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888) originalBitmap.copyPixelsFromBuffer(image.planes[0].buffer) @@ -60,10 +60,3 @@ fun convertImageToByteArray(image: Image, cropTop: Int = 0, cropHeight: Int = 0) return byteArray } - -private fun getBitmapFromRGBA(bytes: ByteArray, width: Int, height: Int): Bitmap { - val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val buffer = ByteBuffer.wrap(bytes) - bitmap.copyPixelsFromBuffer(buffer) - return bitmap -} diff --git a/model/src/main/java/com/xd/testrecorder/overlay/OverlayService.kt b/model/src/main/java/com/xd/testrecorder/overlay/OverlayService.kt index e2df47c..09671a4 100644 --- a/model/src/main/java/com/xd/testrecorder/overlay/OverlayService.kt +++ b/model/src/main/java/com/xd/testrecorder/overlay/OverlayService.kt @@ -2,6 +2,7 @@ package com.xd.testrecorder.overlay import android.accessibilityservice.AccessibilityService.GestureResultCallback import android.accessibilityservice.GestureDescription +import android.annotation.SuppressLint import android.app.Service import android.content.Intent import android.gesture.GestureOverlayView @@ -23,6 +24,7 @@ import com.xd.testrecorder.data.ActionImage import com.xd.testrecorder.data.convertImageToByteArray import com.xd.testrecorder.data.convertMotionEventsToAction import com.xd.testrecorder.recorder.RecorderService +import com.xd.testrecorder.service.serviceFailed import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -33,6 +35,7 @@ import javax.inject.Inject class OverlayService : Service() { companion object { + @SuppressLint("StaticFieldLeak") var service: OverlayService? = null } @@ -46,11 +49,7 @@ class OverlayService : Service() { private lateinit var windowManager: WindowManager private lateinit var overlayView: View - private val type: Int = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - WindowManager.LayoutParams.TYPE_SYSTEM_ALERT - } else { - WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY - } + private val type: Int = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY private val flagsPassThrough = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE @@ -100,9 +99,10 @@ class OverlayService : Service() { // overlayView.setBackgroundColor(Color.parseColor("#60FF0000")) // Set OnTouchListener + overlayView.setOnTouchListener { view, event -> // Handle touch events here - logger.d("OnTouchListener $event ${isPassThrough()}") + logger.d("OnTouchListener $event") if (isPassThrough()) { return@setOnTouchListener false } @@ -127,12 +127,7 @@ class OverlayService : Service() { params.gravity = Gravity.TOP or Gravity.START params.x = 0; params.y = 0; - - if (TouchAccessibilityService.isTargetPackage()) { - params.flags = flagsCapture - } else { - params.flags = flagsPassThrough - } + params.flags = flagsCapture overlayView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { @@ -149,6 +144,9 @@ class OverlayService : Service() { } private fun handleMotionActionUp() { + if (serviceFailed()) { + return + } changeToPassThrough() if (recordingId < 0) { logger.e("recordingId less than 0") @@ -190,32 +188,34 @@ class OverlayService : Service() { ) action.clickableViewClassName = node.className?.toString() ?: "" action.featureViewClassName = node.className?.toString() ?: "" - } - val feature = pair.second - if (feature != null && feature != node) { - action.viewContentDescription = feature.contentDescription?.toString() ?: "" - action.viewText = feature.text?.toString() ?: "" - val rect = Rect() - feature.getBoundsInScreen(rect) - action.featureViewBounds = Rect( - rect.left, - rect.top - TouchAccessibilityService.getStatusBarHeight(), - rect.right, - rect.bottom - TouchAccessibilityService.getStatusBarHeight() + val feature = pair.second + if (feature != null && feature != node) { + action.viewContentDescription = feature.contentDescription?.toString() ?: "" + action.viewText = feature.text?.toString() ?: "" + val rect = Rect() + feature.getBoundsInScreen(rect) + action.featureViewBounds = Rect( + rect.left, + rect.top - TouchAccessibilityService.getStatusBarHeight(), + rect.right, + rect.bottom - TouchAccessibilityService.getStatusBarHeight() + ) + action.featureViewClassName = feature.className?.toString() ?: "" + } + val actionId = actionDao.insertAction(action) + val actionImage = ActionImage( + actionId = actionId, + screenShot = convertImageToByteArray( + image, + TouchAccessibilityService.getStatusBarHeight(), + overlayView.height + ) ) - action.featureViewClassName = feature.className?.toString() ?: "" + actionImageDao.insertActionImage(actionImage) + logger.i("insert action success $action") + } else { + logger.i("not target package skip insert") } - val actionId = actionDao.insertAction(action) - val actionImage = ActionImage( - actionId = actionId, - screenShot = convertImageToByteArray( - image, - TouchAccessibilityService.getStatusBarHeight(), - overlayView.height - ) - ) - actionImageDao.insertActionImage(actionImage) - logger.i("insert action success $action") delay(100) withContext(Dispatchers.Main) { TouchAccessibilityService.dispatchGesture( @@ -227,17 +227,6 @@ class OverlayService : Service() { } } - fun adjustPassThrough() { - if (TouchAccessibilityService.isTargetPackage()) { - changeToCapture() - } else { - changeToPassThrough() - } - if (TouchAccessibilityService.service == null) { - stopSelf() - } - } - private fun copyMotionEvent(originalEvent: MotionEvent): MotionEvent { return MotionEvent.obtain( originalEvent.downTime, @@ -266,7 +255,7 @@ class OverlayService : Service() { override fun onDestroy() { super.onDestroy() - logger.d("onDestroy") + logger.i("onDestroy") service = null removeOverlay() } diff --git a/model/src/main/java/com/xd/testrecorder/recorder/RecorderService.kt b/model/src/main/java/com/xd/testrecorder/recorder/RecorderService.kt index a125fd8..1b46cc1 100644 --- a/model/src/main/java/com/xd/testrecorder/recorder/RecorderService.kt +++ b/model/src/main/java/com/xd/testrecorder/recorder/RecorderService.kt @@ -23,6 +23,7 @@ import androidx.core.content.ContextCompat import com.xd.common.logger.Logger import com.xd.testrecorder.accessibility.TouchAccessibilityService import com.xd.testrecorder.model.R +import com.xd.testrecorder.service.serviceFailed import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -73,6 +74,10 @@ class RecorderService : Service() { logger.i("RecorderService onCreate") service = this CoroutineScope(Dispatchers.Main).launch { + if (serviceFailed()) { + stopSelf() + return@launch + } delay(1000L) startRecording() } @@ -149,13 +154,6 @@ class RecorderService : Service() { latestImage = null } - private fun convertNanosecondsToReadable(timestamp: Long): String { - val instant = Instant.ofEpochSecond(0, timestamp) - val formatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()) - return formatter.format(instant) - } - private fun createNotification(): Notification { val notificationChannelId = "media_projection_service_channel" diff --git a/model/src/main/java/com/xd/testrecorder/recording/RecordingViewModel.kt b/model/src/main/java/com/xd/testrecorder/recording/RecordingViewModel.kt index c8c8732..0438625 100644 --- a/model/src/main/java/com/xd/testrecorder/recording/RecordingViewModel.kt +++ b/model/src/main/java/com/xd/testrecorder/recording/RecordingViewModel.kt @@ -90,8 +90,8 @@ class RecordingViewModel @Inject constructor( val lastTimeUsed = lastTimeUsedMap[appInfo.packageName] ?: 0 val intent = context.packageManager.getLaunchIntentForPackage(appInfo.packageName) - logger.v("getLaunchIntentForPackage ${appInfo.packageName} ${intent != null} ${iconBitmap != null} ${iconDrawable != null}") - if (iconBitmap != null && intent != null) { + logger.v("getLaunchIntentForPackage ${appInfo.packageName} ${intent != null} ${iconBitmap != null}") + if (iconBitmap != null && intent != null && context.packageName != appInfo.packageName) { AppInfo(iconBitmap, appInfo.packageName, appName, lastTimeUsed) } else null } catch (e: PackageManager.NameNotFoundException) { diff --git a/model/src/main/java/com/xd/testrecorder/service/BaseService.kt b/model/src/main/java/com/xd/testrecorder/service/BaseService.kt new file mode 100644 index 0000000..3476fb8 --- /dev/null +++ b/model/src/main/java/com/xd/testrecorder/service/BaseService.kt @@ -0,0 +1,15 @@ +package com.xd.testrecorder.service + +import android.app.Service +import com.xd.testrecorder.accessibility.TouchAccessibilityService +import com.xd.testrecorder.overlay.OverlayService +import com.xd.testrecorder.recorder.RecorderService + +fun Service.serviceFailed(): Boolean { + return if(TouchAccessibilityService.service == null || OverlayService.service == null || RecorderService.service == null){ + stopSelf() + true + } else { + false + } +} \ No newline at end of file diff --git a/model/src/main/res/xml/accessibility_service_config.xml b/model/src/main/res/xml/accessibility_service_config.xml index 8fc0967..ccfff96 100644 --- a/model/src/main/res/xml/accessibility_service_config.xml +++ b/model/src/main/res/xml/accessibility_service_config.xml @@ -1,5 +1,5 @@