+package com.example.shorcutnaver
+
+import android.accessibilityservice.AccessibilityService
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.Uri
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityNodeInfo
+import java.net.URLEncoder
+
+class NmapAccessibilityService : AccessibilityService() {
+
+ companion object {
+ private const val TAG = "NMAP_ACTIVITY"
+ private const val START_NAVIGATION = "com.example.shorcutnaver.START_NAVIGATION"
+ private const val NMAP_PACKAGE = "com.nhn.android.nmap"
+ private const val CHECK_INTERVAL = 3000L
+ }
+
+ private val handler = Handler(Looper.getMainLooper())
+ private var isServiceConnected = false
+
+ private var navigationName: String? = null
+ private var navigationLatitude: String? = null
+ private var navigationLongitude: String? = null
+
+ private data class Waypoint(val name: String, val latitude: String, val longitude: String)
+
+ private val navigationWaypoints = mutableListOf<Waypoint>()
+
+ private val checkRunnable = object : Runnable {
+ override fun run() {
+ if (isServiceConnected) {
+ checkCurrentScreen()
+ }
+ handler.postDelayed(this, CHECK_INTERVAL)
+ }
+ }
+
+ private val navigationRequestReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action != START_NAVIGATION) return
+
+ Log.d(TAG, "================================")
+ Log.d(TAG, "START_NAVIGATION 수신!")
+
+ val name = intent.getStringExtra("name")
+ val latitude = intent.getStringExtra("latitude")
+ val longitude = intent.getStringExtra("longitude")
+
+ if (name.isNullOrBlank() || latitude.isNullOrBlank() || longitude.isNullOrBlank()) {
+ Log.e(TAG, "목적지 좌표 데이터 없음")
+ return
+ }
+
+ navigationName = name
+ navigationLatitude = latitude
+ navigationLongitude = longitude
+ navigationWaypoints.clear()
+
+ val waypointCount = intent.getIntExtra("waypoint_count", 0)
+
+ Log.d(TAG, "목적지")
+ Log.d(TAG, "name = $name")
+ Log.d(TAG, "latitude = $latitude")
+ Log.d(TAG, "longitude = $longitude")
+ Log.d(TAG, "경유지 수 = $waypointCount")
+
+ for (i in 1..waypointCount) {
+ val waypointName = intent.getStringExtra("waypoint_${i}_name")
+ val waypointLatitude = intent.getStringExtra("waypoint_${i}_latitude")
+ val waypointLongitude = intent.getStringExtra("waypoint_${i}_longitude")
+
+ if (waypointName.isNullOrBlank() || waypointLatitude.isNullOrBlank() || waypointLongitude.isNullOrBlank()) {
+ Log.d(TAG, "경유지 $i 데이터 없음 → 무시")
+ continue
+ }
+
+ navigationWaypoints += Waypoint(
+ name = waypointName,
+ latitude = waypointLatitude,
+ longitude = waypointLongitude
+ )
+
+ Log.d(TAG, "경유지 $i 저장")
+ Log.d(TAG, "name = $waypointName")
+ Log.d(TAG, "latitude = $waypointLatitude")
+ Log.d(TAG, "longitude = $waypointLongitude")
+ }
+
+ Log.d(TAG, "실제 저장된 경유지 = ${navigationWaypoints.size}개")
+ Log.d(TAG, "좌표 전달 완료 → Navigation 즉시 실행")
+
+ launchNavigation()
+
+ Log.d(TAG, "================================")
+ }
+ }
+
+ override fun onServiceConnected() {
+ super.onServiceConnected()
+ isServiceConnected = true
+
+ Log.d(TAG, "접근성 서비스 연결됨")
+
+ val filter = IntentFilter(START_NAVIGATION)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ registerReceiver(navigationRequestReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
+ } else {
+ @Suppress("UnspecifiedRegisterReceiverFlag")
+ registerReceiver(navigationRequestReceiver, filter)
+ }
+
+ checkCurrentScreen()
+
+ handler.removeCallbacks(checkRunnable)
+ handler.postDelayed(checkRunnable, CHECK_INTERVAL)
+ }
+
+ override fun onAccessibilityEvent(event: AccessibilityEvent?) {
+ if (event == null) return
+
+ if (event.packageName?.toString() != NMAP_PACKAGE) return
+
+ Log.d(TAG, "네이버 지도 이벤트 발생: type=${event.eventType}")
+
+ if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ checkCurrentScreen()
+ }
+ }
+
+ private fun checkCurrentScreen() {
+ val root = rootInActiveWindow
+
+ if (root == null) {
+ Log.d(TAG, "현재 화면을 읽을 수 없음")
+ return
+ }
+
+ val packageName = root.packageName?.toString()
+
+ if (packageName != NMAP_PACKAGE) {
+ root.recycle()
+ return
+ }
+
+ checkNavigation(root)
+ root.recycle()
+ }
+
+ private fun clickText(root: AccessibilityNodeInfo, target: String): Boolean {
+ val nodes = root.findAccessibilityNodeInfosByText(target)
+
+ for (node in nodes) {
+ var current: AccessibilityNodeInfo? = node
+
+ while (current != null) {
+ if (current.isClickable) {
+ val result = current.performAction(AccessibilityNodeInfo.ACTION_CLICK)
+ Log.d(TAG, "$target 클릭 / 결과=$result")
+ node.recycle()
+ return result
+ }
+
+ current = current.parent
+ }
+
+ node.recycle()
+ }
+
+ return false
+ }
+
+ private fun checkNavigation(root: AccessibilityNodeInfo) {
+ if (findText(root, "경로 안내를 종료하고 길찾기로 이동하시겠습니까?")) {
+ Log.d(TAG, "경로 종료 확인창 발견 → 아니요 클릭")
+ clickText(root, "아니요")
+ return
+ }
+
+ val condition1 = findText(root, "예약 탭") && findText(root, "대중교통 탭")
+ val condition2 = findText(root, "최근 경로 더보기") && findText(root, "최근 장소 더보기") && findText(root, "출발지 도착지 전환")
+
+ Log.d(TAG, "화면 검사: condition1=$condition1, condition2=$condition2")
+
+ if (condition1 || condition2) {
+ Log.d(TAG, "★★★ 네비게이션 화면 감지됨 ★★★")
+ Log.d(TAG, "condition1 = $condition1")
+ Log.d(TAG, "condition2 = $condition2")
+ launchNavigation()
+ }
+ }
+
+ private fun findText(node: AccessibilityNodeInfo, target: String): Boolean {
+ if (node.text?.toString() == target) return true
+ if (node.contentDescription?.toString() == target) return true
+
+ for (i in 0 until node.childCount) {
+ val child = node.getChild(i) ?: continue
+ val found = findText(child, target)
+ child.recycle()
+ if (found) return true
+ }
+
+ return false
+ }
+
+ private fun launchNavigation() {
+ val name = navigationName
+ val latitude = navigationLatitude
+ val longitude = navigationLongitude
+
+ if (name.isNullOrBlank() || latitude.isNullOrBlank() || longitude.isNullOrBlank()) {
+ Log.d(TAG, "저장된 좌표 없음 → 기존 Navigation 숏컷 실행")
+
+ val intent = Intent().apply {
+ component = ComponentName(NMAP_PACKAGE, "com.naver.map.LaunchActivity")
+ action = "com.naver.map.action.SHORTCUT"
+ data = Uri.parse("nmap://navigation")
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+
+ try {
+ Log.d(TAG, "Navigation 숏컷 실행")
+ startActivity(intent)
+ Log.d(TAG, "Navigation 실행 성공")
+ } catch (e: Exception) {
+ Log.e(TAG, "Navigation 실행 실패", e)
+ }
+
+ return
+ }
+
+ val encodedName = URLEncoder.encode(name, "UTF-8").replace("+", "%20")
+
+ val uriBuilder = StringBuilder(
+ "nmap://navigation?dlat=$latitude&dlng=$longitude&dname=$encodedName"
+ )
+
+ navigationWaypoints.forEachIndexed { index, waypoint ->
+ val number = index + 1
+ val encodedWaypointName = URLEncoder.encode(waypoint.name, "UTF-8").replace("+", "%20")
+
+ uriBuilder.append("&v${number}lat=${waypoint.latitude}")
+ uriBuilder.append("&v${number}lng=${waypoint.longitude}")
+ uriBuilder.append("&v${number}name=$encodedWaypointName")
+ }
+
+ uriBuilder.append("&appname=$packageName")
+
+ val uri = Uri.parse(uriBuilder.toString())
+
+ val intent = Intent().apply {
+ component = ComponentName(NMAP_PACKAGE, "com.naver.map.LaunchActivity")
+ action = "com.naver.map.action.SHORTCUT"
+ data = uri
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+
+ try {
+ Log.d(TAG, "================================")
+ Log.d(TAG, "Navigation 숏컷 실행")
+ Log.d(TAG, "목적지 = $name")
+ Log.d(TAG, "latitude = $latitude")
+ Log.d(TAG, "longitude = $longitude")
+ Log.d(TAG, "경유지 수 = ${navigationWaypoints.size}")
+
+ navigationWaypoints.forEachIndexed { index, waypoint ->
+ Log.d(TAG, "경유지 ${index + 1} = ${waypoint.name}")
+ Log.d(TAG, "경유지 ${index + 1} = ${waypoint.latitude}, ${waypoint.longitude}")
+ }
+
+ Log.d(TAG, "최종 URI = $uri")
+
+ startActivity(intent)
+
+ Log.d(TAG, "Navigation 실행 성공")
+ Log.d(TAG, "================================")
+
+ navigationName = null
+ navigationLatitude = null
+ navigationLongitude = null
+ navigationWaypoints.clear()
+ } catch (e: Exception) {
+ Log.e(TAG, "Navigation 실행 실패", e)
+ }
+ }
+
+ override fun onInterrupt() {
+ Log.d(TAG, "서비스 중단")
+ }
+
+ override fun onDestroy() {
+ isServiceConnected = false
+ handler.removeCallbacks(checkRunnable)
+
+ try {
+ unregisterReceiver(navigationRequestReceiver)
+ } catch (_: Exception) {
+ }
+
+ Log.d(TAG, "서비스 종료")
+ super.onDestroy()
+ }
+}
\ No newline at end of file