대환장 통화 관련 기능 구현하기(1) - 임시저장

undefined·2023년 8월 15일
0

삽질로그

목록 보기
4/7

먼저 이 앱을 위해 필요한 권한들이다.

android.permission.INTERNET: 인터넷에 연결하고, 소켓 연결을 만들수 있도록 허용합니다.
android.permission.VIBRATE: 기기가 진동할 수 있도록 허용합니다.
android.permission.RECEIVE_SMS: SMS 수신을 위해 필요합니다.
android.permission.READ_CALL_LOG: 수신, 발신 및 부재중 전화 로그를 읽을 수 있도록 허용합니다.
android.permission.READ_CONTACTS: 연락처 읽기를 허용합니다.
android.permission.COM_ANDROID_ALARM.PERMISSION.SET_ALARM: 알람 설정을 허용합니다.
android.permission.SCHEDULE_EXACT_ALARM: 정확한 시간에 알람이 울릴 수 있도록 허용합니다.
android.permission.WAKE_LOCK: 기기의 화면 꺼짐을 방지하는 기능을 수행하기 위해 필요합니다.
android.permission.SYSTEM_ALERT_WINDOW: 다른 앱 위에 띄워지는 시스템 알림을 표시하기 위해 필요합니다.
android.permission.USE_FULL_SCREEN_INTENT: 전체 화면 모드로 알림을 띄우는 기능을 사용하기 위해 필요합니다.
android.permission.READ_PHONE_STATE: 현재 전화 상태를 읽는 것이 가능합니다.
android.permission.ANSWER_PHONE_CALLS: 전화에 대한 응답 권한을 부여합니다.
android.permission.DISABLE_KEYGUARD: 잠금 화면을 비활성화할 수 있도록 허용합니다.
android.permission.PROCESS_INCOMING_CALLS: 전화 발신 및 수신, 전화 로그 등의 처리를 수행하기 위해 필요합니다.
android.permission.CALL_PHONE: 전화 발신 권한을 부여합니다.
android.permission.MANAGE_OWN_CALLS: 자신이 수신한 전화를 제어하고 관리할 수 있는 권한을 부여합니다.
android.permission.WRITE_CONTACTS: 연락처 작성 권한을 허용합니다.
android.permission.USE_EXACT_ALARM: 정확한 시간에 알람이 울릴 수 있도록 허용합니다.
android.permission.FOREGROUND_SERVICE: 백그라운드 프로세스에서 포그라운드 서비스를 실행할 수 있도록 허용합니다.
android.permission.BIND_INCALL_SERVICE: 보이스 콜 제어와 같은 기능 수행을 위해 인컬 서비스 바인드를 허용합니다.

어려웠던 부분은, 잠금화면에서 팝업이 두번 뜨는 이슈를 제어하는 것이었다.
브로드캐스트 리시버를 이용해서 전화의 상태를 받아왔는데, 전화 수신시 동일한 이벤트를 두번 받으면서 문제가 발생했다.

그리고 안드로이드 버전별로 로직을 분기처리 해야 하는 부분이 까다로웠다.

통화 관련 기능을 제어하기 위해서 전화 기본앱으로 설정해야 할지, 일부 기능만 구현하여 사용해야 할지 고민이 되었다.

그리고 이번 프로젝트를 통해서 WindowManager를 이용하여 뷰를 그리는 방법에 대해 공부하게 되었다.
개인적으로는 액티비티나 프래그먼트를 이용해서 뷰를 그리는 것을 선호하였는데, 이렇게 생성된 뷰는 애플리케이션 안에서만 사용할 때 사용하는 방법이라고 한다.
내가 구현하고자 했던 팝업의 경우에는 특정 조건에서 시스템의 다른 부분과 상호작용 해야 하기 때문에 앱 외부에서 뷰를 그리고 제어해야 했다. 따라서 broadcastReceiver를 통해 service를 Foreground로 실행해서 뷰를 그려주는 방식으로 구현했다.

이 과정에서 각 상태에 따라 뷰를 그리는 방법을 정리해 볼 수 있었다.

화면이 꺼져있을 때 뷰를 그려야 하는 경우

<uses-permission android:name="android.permission.WAKE_LOCK" />
	override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
        wakeLock = powerManager.newWakeLock(
            PowerManager.FULL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
            "YourApp::YourTag"
        )
        wakeLock.acquire()
		...
    }

    override fun onDestroy() {
        super.onDestroy()
        removePopup()

        if (wakeLock.isHeld) {
            wakeLock.release()
        }
    }

WakeLock은 디바이스의 CPU를 활성 상태로 유지한 다음, 화면이 켜지면 뷰가 표시되도록 한다. 안드로이드에서는 Notification을 이용해서 노티를 클릭하면 뷰를 띄우도록 하길 권장하긴 했지만, 요구사항과 달랐기 때문에 이 방법을 사용했다.

잠금 화면 위에 뷰를 그려야 하는 경우

        params = WindowManager.LayoutParams(
            width.toInt(),
            WindowManager.LayoutParams.WRAP_CONTENT,
            layoutFlag,
            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
                    WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
                    WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
                    WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
                    WindowManager.LayoutParams.FLAG_FULLSCREEN,
            PixelFormat.TRANSLUCENT
        )
    window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
    window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)

위와 같이 설정했다.

나는 서비스에서 뷰를 그렸기 때문에 위처럼 플래그를 설정했지만, 액티비티같은 경우에는 아래처럼 manifest파일에서 설정을 해주어도 된다고 한다.

        <activity
			...
            android:showOnLockScreen="true"
            android:turnScreenOn="true"

####앱이 대기상태에 있을 때 뷰를 그려야 하는 경우.
안드로이드 애플리케이션에서는 백그라운드에서 직접 뷰를 그리는 것을 제한하고 있다. 그래서 강제적으로 시스템 수준으로 뷰를 그리도록 하는 방법을 사용해야 한다.

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

그리고 서비스에서 뷰를 그리기 때문에 windowmanager로

        val params = WindowManager.LayoutParams().apply {
            type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY //UI를 가장 위에 띄우기
            format = PixelFormat.TRANSLUCENT //투명 배경
            flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            gravity = Gravity.TOP or Gravity.START
            width = WindowManager.LayoutParams.WRAP_CONTENT
            height = WindowManager.LayoutParams.WRAP_CONTENT
        }

또 통화 제어 기능이 필요하다 보니 TelephonyManager와 TelecomManager를 접하게 되었는데, 이 둘의 차이를 명확하게 이해하지 못해서 기능 구현에 어려움이 있었다.
짧게 정리하자면 TelecomManager는 전화 걸기, 받기, 끊기와 같은 기능을 제어할 때 사용하고, TelephonyManager는 디바이스의 전화상태(통화중, 수신중, 발신중..?), 통화내역, sms정보, SIM정보 등을 얻을 때 사용한다.(아직 추가적인 정리가 필요할 것 같다)

현재 전화 받기 기능은 아래처럼 구현해두었다.

    private fun acceptInCall() {
        telecomManager?.let {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                it.acceptRingingCall()
            } else {
                val mClass = Class.forName(it.javaClass.name)
                val method = mClass.getDeclaredMethod("answerRingingCall")
                method.invoke(it)
            }
        }
        stopSelf()
    }

acceptRingingCall()도 Deprecated 되었기 때문에 callSelfManaged()를 사용해야 한다고 되어있다. 하지만 ConnectionService를 구현해야 하기 때문에... 흐린눈 하는 중,, 나중에 리팩토링 할때 처리하는걸로

profile
이것저것 하고 싶은 게 많은 병아리 개발자

1개의 댓글

comment-user-thumbnail
2023년 8월 15일

공감하며 읽었습니다. 좋은 글 감사드립니다.

답글 달기
Powered by GraphCDN, the GraphQL CDN