반응형
Greensky0026
삽질러의 대환장 공사판
Greensky0026
전체 방문자
오늘
어제
  • 분류 전체보기 (241)
    • Language (56)
      • JAVA (13)
      • Swift (38)
      • Kotlin (4)
      • Dart (0)
      • PHP (0)
      • JavaScript (1)
    • IDE & Framework (92)
      • Spring (10)
      • Android (48)
      • iOS (8)
      • NodeJS (1)
      • CodeIgniter (3)
      • Flutter (1)
      • 분류중 (18)
    • Infra (8)
    • Database (12)
    • VCS (4)
    • Debug Log (34)
      • java (7)
      • swift (3)
      • Android (9)
      • Web (7)
      • 삽질기 (7)
    • Others (19)
      • 코딩테스트 풀이 (0)
      • IT 이야기 (18)
    • 쉼터 (2)
    • 개발공부 (14)
      • Network (1)
      • 자료구조와 알고리즘 (2)
      • Code design (8)
      • ETC (1)
      • 아카이브 (1)

블로그 메뉴

    공지사항

    • 프로그래머스 코딩 테스트 관련글 비공개 처리

    인기 글

    태그

    • IT
    • 구축
    • 타입
    • 프로그래머스
    • swfit
    • 개발
    • 공부
    • Java
    • JSP
    • map
    • Android
    • kotlin
    • swift
    • 코딩테스트
    • 예제
    • ios
    • xcode
    • 프로그래밍
    • level1
    • reduce

    최근 댓글

    최근 글

    티스토리

    hELLO · Designed By 정상우.
    Greensky0026

    삽질러의 대환장 공사판

    [Kotlin] InCallService로 Default Dialer 개발하기
    IDE & Framework/Android

    [Kotlin] InCallService로 Default Dialer 개발하기

    2021. 12. 19. 16:11
    반응형

    1. ConnectionService와 InCallService의 차이

    InCallService

    대부분의 스펙은 구현이 되어있으며, 구현된 항목을 선언하고 요청만 해주면 구현할 수 있다.

    ConnectionService는 VOIP개발에 더 적합하다.

    gsm통화를 개발하기 위해 각종 편의사항이 제공되는 InCallService와는 다르게,

    연결을 직접 받아 등록하고 통화의 시작, 종료까지 모두 구현해주어야 한다.

    그래서 VOIP구현엔 오픈소스 라이브러리를 주로 사용한다 (kakao talk도 보이스톡도 그렇다!)

     

     

     

     

    2. InCallService 사용을 위한 사전 준비

    기본 통화앱이 되도록 Manifest 및 userpermission 수정

    ㄱ. Manifest - ues permission tags

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_LOG"/>
    <uses-permission android:name="android.permission.CALL_PHONE" />
    
    //OUT_GOING_CALL -> CALL_LOG : 명확환 permission 관리를 위해 세분화되었음

     매니패스트에 선언 후에 사용자에게 권한을 요청합니다.

    var permissions =
    	arrayOf(
    		Manifest.permission.READ_PHONE_STATE,
    		Manifest.permission.CALL_LOG,
    		Manifest.permission.CALL_PHONE
    	)
        
    val permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
    	isGranted: Boolean ->
    		if(!isGranted){
    			Toast.makeText(this,"권한이 없다면 앱이 정상적으로 동작하지 않습니다.", Toast.LENGTH_SHORT).show()
    		} else {
    			//허용시 수행할 작업 입력
    		}
    	}
    }
    
    for (permission in permissions){
    	permissionLauncher.launch(permission)
    }

     

    ㄴ. Manifest - addtional Activity tag

     <activity android:name="your.package.YourDialerActivity"
               android:label="@string/yourDialerActivityLabel">
          <intent-filter>
               <action android:name="android.intent.action.DIAL" />
               <category android:name="android.intent.category.DEFAULT" />
          </intent-filter>
          <intent-filter>
               <action android:name="android.intent.action.DIAL" />
               <category android:name="android.intent.category.DEFAULT" />
               <data android:scheme="tel" />
          </intent-filter>
     </activity>

     기본 통화 앱 설정은 manifest의 추가와 더불어 Activity에서 사용자에게 요청을 해야 합니다.

    API 30부터는 RoleManager 및 registerForActivityResult를 사용해 요청해야 합니다.

    registerForActivityResult에 대한 포스팅도 있으니, 여기를 참조하세요.

    a. less API 29 use ActivityForResult

    private void offerReplacingDefaultDialer() {
      val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
      lateinit var defualtAppIntent: Intent
    
      if (!getPackageName().equals(telecomManager.getDefaultDialerPackage())) {
      	Intent intent = new Intent(ACTION_CHANGE_DEFAULT_DIALER)
        	.putExtra(EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, getPackageName())
            
        startActivity(intent)
      }
    }

    b. more than API 30 use RoleManager

    lateinit var defualtAppIntent: Intent
      val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
      lateinit var defualtAppIntent: Intent
      val roleManager = getSystemService(ROLE_SERVICE) as RoleManager
      val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER)
      defualtAppIntent = intent
    
      var requireDefualtDialerLauncher =
      registerForActivityResult(StartActivityForResult()){
        val thisDefaultDialer = packageName == telecomManager.defaultDialerPackage
        if(!thisDefaultDialer){
          Toast.makeText(this,"기본 전화 앱 설정은 필수입니다.", Toast.LENGTH_SHORT).show()
          finish()
        }
    }
    
    if(packageName != telecomManager.defaultDialerPackage){
    	requireDefualtDialerLauncher.launch(defualtAppIntent)
    }

     

    3. InCallService 사용을 위한 등록 및 구현

     <service android:name="your.package.YourInCallServiceImplementation"
              android:permission="android.permission.BIND_INCALL_SERVICE">
          <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
          <meta-data android:name="android.telecom.IN_CALL_SERVICE_RINGING"
              android:value="true" />
          <intent-filter>
              <action android:name="android.telecom.InCallService"/>
          </intent-filter>
     </service>
    class YourInCallServiceImplementation: InCallService() {
    
        override fun onCallAdded(call: Call?) {
            super.onCallAdded(call)
        }
        
    }

     

    4. 상세 구현

    지금까지 InCallService를 구축하기 위한 기본적인 준비였습니다.

    준비가 좀 길었다구요? 구현은 간단합니다.

    YourInCallServiceImplementation에서 override한 onCallAdded 메서드 안에서, 매개변수로 받은 call 객체만 다뤄주면 됩니다.

    https://developer.android.com/reference/android/telecom/Call#getState()

    class YourInCallServiceImplementation: InCallService() {
    	static lastState = ""
    
        override fun onCallAdded(call: Call?) {
            val callState = call.detail.state
            
            when (callState) {
                STATE_RINGING -> println("ringing")
                STATE_DIALING -> println("dialing")
                STATE_ACTIVE -> println("active")
                STATE_DISCONNECTED -> println("disconnected")
            }
        }
    }

     위에서 순서대로 각각 수신중, 발신중, 통화중, call 종료의 상태를 나타냅니다.

    call의 상태가 변경될때마다 호출되므로, 각 상태에 대한 분기작업을 실행하면 됩니다.

    각 상황에 맞는 activity를 팝업하면서 call 객체를 넘겨주면 handling이 되겠죠?

     

    여기서 observe 패턴을 RxJava나 Corutine으로 구현해주면 더욱더 짜임새 높은 코드가 될 겁니다.

    반응형
    저작자표시 비영리 변경금지 (새창열림)

    'IDE & Framework > Android' 카테고리의 다른 글

    [Kotlin] Firebase Crashlytics 적용시키기 (간단!)  (0) 2022.01.09
    [JAVA] Base64 incoding, decoding  (0) 2022.01.06
    [Kotlin]ActivityForResult 및 checkSelfPerission 을 permissionLauncher로 대체하기  (0) 2021.12.19
    [Kotlin] Fragment (2) - 기초 예제  (0) 2021.12.19
    [Kotlin] Fragment (1) - Introduce  (0) 2021.12.12
      'IDE & Framework/Android' 카테고리의 다른 글
      • [Kotlin] Firebase Crashlytics 적용시키기 (간단!)
      • [JAVA] Base64 incoding, decoding
      • [Kotlin]ActivityForResult 및 checkSelfPerission 을 permissionLauncher로 대체하기
      • [Kotlin] Fragment (2) - 기초 예제
      Greensky0026
      Greensky0026
      점이 모여 선이 된다. 내 삽질도 언젠간 거대한 지하 도시가 되겠지!

      티스토리툴바