세션 시작 프로토콜 개요

eSIM 및 SIM 카드 감지

카드 감지

SIM 카드 및 eSIM이 있는 Android 기기는 [`TelephonyManager`](/reference/android/telephony/TelephonyManager) 및 [`SubscriptionManager`](/reference/android/telephony/SubscriptionManager)를 비롯한 전화 통신 API에서 다음 ID를 사용합니다. * 구독 ID: 모바일 구독을 위한 고유 ID입니다. * 논리 슬롯 인덱스 또는 ID: 논리 SIM 슬롯을 참조하는 고유 인덱스. 논리 슬롯 ID는 0에서 시작하며 기기에서 지원되는 활성 슬롯 수에 따라 증가합니다. 예를 들어 듀얼 SIM 기기에는 일반적으로 슬롯 0과 슬롯 1이 있습니다. 기기에 여러 개의 실물 슬롯이 있지만 하나의 활성 슬롯만 지원한다면 논리 슬롯 ID는 0입니다. * 물리적 슬롯 인덱스 또는 ID: 실물 SIM 슬롯을 나타내는 고유 인덱스. 실제 슬롯 ID는 0에서 시작하여 기기의 실제 슬롯 수에 따라 증가합니다. 이는 기기에 있는 논리 슬롯의 수와는 다르며, 이는 기기에서 사용할 수 있는 활성 슬롯의 수에 해당합니다. 예를 들어 듀얼 SIM과 단일 SIM 모드 간에 전환하는 기기에는 항상 물리적인 슬롯이 두 개 있을 수 있지만, 단일 SIM 모드에서는 논리 슬롯이 하나만 있습니다. * 카드 ID: UiccCard를 식별하는 데 사용되는 고유 ID. ![논리 슬롯 2개와 물리적 슬롯 3개가 있는 케이스에서 ID가 사용되는 방식 다이어그램](/images/guide/topics/connectivity/tel-ids.png) 위 다이어그램에서는 다음과 같습니다. * 기기에는 두 개의 논리 슬롯이 있습니다. * 물리적 슬롯 0에는 활성 프로필이 있는 물리적 UICC 카드가 있습니다. * 물리적 슬롯 2는 활성 프로필이 있는 eUICC입니다. * 물리적 슬롯 1은 현재 사용되지 않습니다. ![논리 슬롯 3개와 물리적 슬롯 2개가 있는 케이스에서 ID가 사용되는 방식 다이어그램](/images/guide/topics/connectivity/tel-ids-2.png) 위 다이어그램에서는 다음과 같습니다. * 기기에는 3개의 논리 슬롯이 있습니다. * 물리적 슬롯 0에는 활성 프로필이 있는 물리적 UICC 카드가 있습니다. * 물리적 슬롯 1은 다운로드된 프로필이 두 개 있는 eUICC이며 둘 다 MEP (다중 지원 프로필)를 사용하여 활성 상태입니다.

세션 시작 프로토콜 개요

Android는 세션 시작 프로토콜(SIP)을 지원하는 API를 제공합니다. 이를 통해 SIP 기반 인터넷 전화 통신 기능을 애플리케이션에 추가할 수 있습니다. Android에는 전체 SIP 프로토콜 스택 및 통합 통화 관리 서비스가 포함되어 있어 애플리케이션이 세션, 전송 수준 통신, 오디오 녹음 또는 재생을 직접 관리할 필요 없이 발신 및 수신 음성 통화를 쉽게 설정할 수 있습니다.

SIP API를 사용할 수 있는 애플리케이션 유형의 예는 다음과 같습니다.

  • 화상 회의
  • 채팅

요구사항 및 제한사항

SIP 애플리케이션 개발을 위한 요구사항은 다음과 같습니다.

  • Android 2.3 이상을 실행하는 휴대기기가 있어야 합니다.
  • SIP는 무선 데이터 연결을 통해 실행되므로 기기에 데이터 연결 (모바일 데이터 서비스 또는 Wi-Fi 사용)이 있어야 합니다. 즉, AVD에서는 테스트할 수 없으며 실제 기기에서만 테스트할 수 있습니다. 자세한 내용은 SIP 애플리케이션 테스트를 참고하세요.
  • 애플리케이션 통신 세션의 각 참여자는 SIP 계정이 있어야 합니다. SIP 계정을 제공하는 여러 다양한 SIP 제공업체가 있습니다.

참고: android.net.sip 라이브러리는 영상 통화를 지원하지 않습니다. android.net.sip와 같은 SIP 스택을 사용하여 VOIP 통화를 구현하려면 모든 VOIP 통화 구현의 기반으로 여러 최신 오픈소스 대안 중 하나를 살펴보세요. 또는 ConnectionService API를 구현하여 이러한 호출을 기기의 다이얼러 앱에 긴밀하게 통합할 수 있습니다.

SIP API 클래스 및 인터페이스

다음은 Android SIP API에 포함된 클래스와 하나의 인터페이스(SipRegistrationListener)에 관한 요약입니다.

클래스/인터페이스 설명
SipAudioCall SIP를 통한 인터넷 음성 통화를 처리합니다.
SipAudioCall.Listener 수신 중일 때 ('벨 울림 시') 또는 전화가 발신 중일 때 ('통화 시')와 같은 SIP 통화와 관련된 이벤트의 리스너입니다.
SipErrorCode SIP 작업 중에 반환되는 오류 코드를 정의합니다.
SipManager SIP 연결 시작과 같은 SIP 작업용 API를 제공하고 관련 SIP 서비스에 대한 액세스를 제공합니다.
SipProfile SIP 계정, 도메인 및 서버 정보를 포함한 SIP 프로필을 정의합니다.
SipProfile.Builder SipProfile을 생성하기 위한 도우미 클래스입니다.
SipSession SIP 대화상자와 연결된 SIP 세션 또는 대화상자 내부가 아닌 독립형 트랜잭션과 연결된 SIP 세션을 나타냅니다.
SipSession.Listener 세션이 등록될 때('등록 시') 또는 통화가 발신될 때 ('통화 시')와 같은 SIP 세션과 관련된 이벤트의 리스너입니다.
SipSession.State '등록 중', '전화 발신 중' 및 '통화 중'과 같은 SIP 세션 상태를 정의합니다.
SipRegistrationListener SIP 등록 이벤트의 리스너인 인터페이스입니다.

manifest 생성

SIP API를 사용하는 애플리케이션을 개발하는 경우 이 기능은 Android 2.3 (API 수준 9) 이상 버전의 플랫폼에서만 지원됩니다. 또한 Android 2.3 (API 수준 9) 이상을 실행하는 기기에서는 일부 기기에서 SIP를 지원하지 않습니다.

SIP를 사용하려면 애플리케이션의 manifest에 다음 권한을 추가하세요.

  • android.permission.USE_SIP
  • android.permission.INTERNET

SIP를 지원할 수 있는 기기에만 애플리케이션을 설치하려면 애플리케이션의 매니페스트에 다음을 추가하세요.

<uses-sdk android:minSdkVersion="9" />

이는 애플리케이션에 Android 2.3 이상이 필요함을 나타냅니다. 자세한 내용은 API 수준<uses-sdk> 요소 관련 문서를 참고하세요.

SIP를 지원하지 않는 기기 (예: Google Play)에서 애플리케이션을 필터링하는 방법을 제어하려면 애플리케이션의 매니페스트에 다음을 추가하세요.

<uses-feature android:name="android.software.sip.voip" />

이는 애플리케이션이 SIP API를 사용함을 명시한 것입니다. 선언에는 SIP 지원을 제공하지 않는 기기에서 애플리케이션을 필터링할지 여부를 나타내는 android:required 속성이 포함되어야 합니다. 구현에 따라 다른 <uses-feature> 선언이 필요할 수도 있습니다. 자세한 내용은 <uses-feature> 요소에 관한 문서를 참고하세요.

애플리케이션이 전화를 수신하도록 설계된 경우 애플리케이션의 매니페스트에 수신기 (BroadcastReceiver 서브클래스)도 정의해야 합니다.

<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />

다음은 SipDemo manifest에서 발췌한 내용입니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.android.sip">
  ...
     <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
  ...
  <uses-sdk android:minSdkVersion="9" />
  <uses-permission android:name="android.permission.USE_SIP" />
  <uses-permission android:name="android.permission.INTERNET" />
  ...
  <uses-feature android:name="android.software.sip.voip" android:required="true" />
  <uses-feature android:name="android.hardware.wifi" android:required="true" />
  <uses-feature android:name="android.hardware.microphone" android:required="true" />
</manifest>

SipManager 생성

SIP API를 사용하려면 애플리케이션에서 SipManager 객체를 생성해야 합니다. SipManager는 애플리케이션에서 다음 작업을 처리합니다.

  • SIP 세션 시작
  • 전화 시작 및 수신
  • SIP 제공업체 등록 및 등록 취소
  • 세션 연결 확인

다음과 같이 새 SipManager를 인스턴스화합니다.

Kotlin

val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
    SipManager.newInstance(this)
}

Java

public SipManager sipManager = null;
...
if (sipManager == null) {
    sipManager = SipManager.newInstance(this);
}

SIP 서버에 등록

일반적인 Android SIP 애플리케이션에는 각각 SIP 계정을 가진 한 명 이상의 사용자가 포함됩니다. Android SIP 애플리케이션에서 각 SIP 계정은 SipProfile 객체로 표시됩니다.

SipProfile는 도메인 및 서버 정보와 SIP 계정을 포함한 SIP 프로필을 정의합니다. 애플리케이션을 실행하는 기기의 SIP 계정과 연결된 프로필을 로컬 프로필이라고 합니다. 세션이 연결된 프로필을 피어 프로필이라고 합니다. SIP 애플리케이션이 로컬 SipProfile를 사용하여 SIP 서버에 로그인하면 SIP 통화를 전송할 위치로 기기가 SIP 주소로 등록됩니다.

이 섹션에서는 SipProfile를 만들고 SIP 서버에 등록하고 등록 이벤트를 추적하는 방법을 설명합니다.

다음과 같이 SipProfile 객체를 만듭니다.

Kotlin

private var sipProfile: SipProfile? = null
...

val builder = SipProfile.Builder(username, domain)
        .setPassword(password)
sipProfile = builder.build()

Java

public SipProfile sipProfile = null;
...

SipProfile.Builder builder = new SipProfile.Builder(username, domain);
builder.setPassword(password);
sipProfile = builder.build();

다음 코드 발췌 부분은 전화를 걸거나 일반 SIP 통화를 받기 위한 로컬 프로필을 엽니다. 호출자는 mSipManager.makeAudioCall를 통해 후속 전화를 걸 수 있습니다. 이 발췌문은 기기가 전화를 받을 때 인텐트 필터에서 사용할 android.SipDemo.INCOMING_CALL 작업도 설정합니다 (인텐트 필터를 설정하여 전화 수신 참고). 등록 단계는 다음과 같습니다.

Kotlin

val intent = Intent("android.SipDemo.INCOMING_CALL")
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
sipManager?.open(sipProfile, pendingIntent, null)

Java

Intent intent = new Intent();
intent.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
sipManager.open(sipProfile, pendingIntent, null);

마지막으로 이 코드는 SipManagerSipRegistrationListener를 설정합니다. 이는 SipProfile가 SIP 서비스 제공업체에 성공적으로 등록되었는지 추적합니다.

Kotlin

sipManager?.setRegistrationListener(sipProfile?.uriString, object : SipRegistrationListener {

    override fun onRegistering(localProfileUri: String) {
        updateStatus("Registering with SIP Server...")
    }

    override fun onRegistrationDone(localProfileUri: String, expiryTime: Long) {
        updateStatus("Ready")
    }

    override fun onRegistrationFailed(
            localProfileUri: String,
            errorCode: Int,
            errorMessage: String
    ) {
        updateStatus("Registration failed. Please check settings.")
    }
})

Java

sipManager.setRegistrationListener(sipProfile.getUriString(), new SipRegistrationListener() {

    public void onRegistering(String localProfileUri) {
        updateStatus("Registering with SIP Server...");
    }

    public void onRegistrationDone(String localProfileUri, long expiryTime) {
        updateStatus("Ready");
    }

    public void onRegistrationFailed(String localProfileUri, int errorCode,
        String errorMessage) {
        updateStatus("Registration failed.  Please check settings.");
    }
}

애플리케이션 사용을 완료한 애플리케이션은 프로필을 닫아 관련 객체를 메모리로 해제하고 기기에서 기기를 등록 취소해야 합니다. 예를 들면 다음과 같습니다.

Kotlin

fun closeLocalProfile() {
    try {
        sipManager?.close(sipProfile?.uriString)
    } catch (ee: Exception) {
        Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee)
    }
}

Java

public void closeLocalProfile() {
    if (sipManager == null) {
       return;
    }
    try {
       if (sipProfile != null) {
          sipManager.close(sipProfile.getUriString());
       }
     } catch (Exception ee) {
       Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee);
     }
}

음성 통화하기

음성 통화를 하려면 다음 사항이 준비되어야 합니다.

  • 전화를 거는 SipProfile('로컬 프로필') 및 전화를 받을 유효한 SIP 주소('피어 프로필')
  • SipManager 객체

음성 통화를 하려면 SipAudioCall.Listener를 설정해야 합니다. 클라이언트와 SIP 스택의 상호작용 대부분은 리스너를 통해 이루어집니다. 이 스니펫에서는 호출이 설정된 후 SipAudioCall.Listener가 설정하는 방법을 보여줍니다.

Kotlin

var listener: SipAudioCall.Listener = object : SipAudioCall.Listener() {

    override fun onCallEstablished(call: SipAudioCall) {
        call.apply {
            startAudio()
            setSpeakerMode(true)
            toggleMute()
        }
    }

    override fun onCallEnded(call: SipAudioCall) {
        // Do something.
    }
}

Java

SipAudioCall.Listener listener = new SipAudioCall.Listener() {

   @Override
   public void onCallEstablished(SipAudioCall call) {
      call.startAudio();
      call.setSpeakerMode(true);
      call.toggleMute();
         ...
   }

   @Override

   public void onCallEnded(SipAudioCall call) {
      // Do something.
   }
};

SipAudioCall.Listener를 설정한 후에는 호출할 수 있습니다. SipManager 메서드 makeAudioCall는 다음 매개변수를 사용합니다.

  • 로컬 SIP 프로필(발신자)
  • 피어 SIP 프로필(통화가 연결될 사용자)
  • SipAudioCall의 통화 이벤트를 수신 대기하는 SipAudioCall.Listener. 이 매개변수는 null일 수 있지만 위에서 볼 수 있듯이 호출이 완료되면 리스너가 설정을 설정하는 데 사용됩니다.
  • 시간 제한 값(초)

예:

Kotlin

val call: SipAudioCall? = sipManager?.makeAudioCall(
        sipProfile?.uriString,
        sipAddress,
        listener,
        30
)

Java

call = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, listener, 30);

전화 받기

전화를 받으려면 수신 전화가 있음을 나타내는 인텐트에 응답할 수 있는 BroadcastReceiver의 서브클래스가 SIP 애플리케이션에 포함되어야 합니다. 따라서 애플리케이션에서 다음을 실행해야 합니다.

  • AndroidManifest.xml에서 <receiver>을 선언합니다. SipDemo에서 이는 <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />입니다.
  • BroadcastReceiver의 서브클래스인 수신자를 구현합니다. SipDemo에서 이는 IncomingCallReceiver입니다.
  • 누군가 로컬 프로필로 전화를 걸 때 수신자를 실행하는 대기 중인 인텐트로 로컬 프로필 (SipProfile)을 초기화합니다.
  • 수신 전화를 나타내는 작업을 기준으로 필터링하는 인텐트 필터를 설정합니다. SipDemo에서 이 작업은 android.SipDemo.INCOMING_CALL입니다.

BroadcastReceiver 서브클래스 생성

전화를 받으려면 SIP 애플리케이션에서 BroadcastReceiver 서브클래스를 생성해야 합니다. Android 시스템은 수신 SIP 통화를 처리하고 전화를 받을 때 애플리케이션에서 정의한 대로 '수신 전화' 인텐트를 브로드캐스트합니다. 다음은 SipDemo 샘플에서 가져온 서브클래스화된 BroadcastReceiver 코드입니다.

Kotlin

/**
 * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
 */
class IncomingCallReceiver : BroadcastReceiver() {

    /**
     * Processes the incoming call, answers it, and hands it over to the
     * WalkieTalkieActivity.
     * @param context The context under which the receiver is running.
     * @param intent The intent being received.
     */
    override fun onReceive(context: Context, intent: Intent) {
        val wtActivity = context as WalkieTalkieActivity

        var incomingCall: SipAudioCall? = null
        try {
            incomingCall = wtActivity.sipManager?.takeAudioCall(intent, listener)
            incomingCall?.apply {
                answerCall(30)
                startAudio()
                setSpeakerMode(true)
                if (isMuted) {
                    toggleMute()
                }
                wtActivity.call = this
                wtActivity.updateStatus(this)
            }
        } catch (e: Exception) {
            incomingCall?.close()
        }
    }

    private val listener = object : SipAudioCall.Listener() {

        override fun onRinging(call: SipAudioCall, caller: SipProfile) {
            try {
                call.answerCall(30)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

Java

/**
 * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
 */
public class IncomingCallReceiver extends BroadcastReceiver {
    /**
     * Processes the incoming call, answers it, and hands it over to the
     * WalkieTalkieActivity.
     * @param context The context under which the receiver is running.
     * @param intent The intent being received.
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        SipAudioCall incomingCall = null;
        try {
            SipAudioCall.Listener listener = new SipAudioCall.Listener() {
                @Override
                public void onRinging(SipAudioCall call, SipProfile caller) {
                    try {
                        call.answerCall(30);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;
            incomingCall = wtActivity.sipManager.takeAudioCall(intent, listener);
            incomingCall.answerCall(30);
            incomingCall.startAudio();
            incomingCall.setSpeakerMode(true);
            if(incomingCall.isMuted()) {
                incomingCall.toggleMute();
            }
            wtActivity.call = incomingCall;
            wtActivity.updateStatus(incomingCall);
        } catch (Exception e) {
            if (incomingCall != null) {
                incomingCall.close();
            }
        }
    }
}

인텐트 필터를 설정하여 전화 수신

SIP 서비스가 새 전화를 받으면 애플리케이션에서 제공한 작업 문자열로 인텐트를 전송합니다. SipDemo에서 이 작업 문자열은 android.SipDemo.INCOMING_CALL입니다.

SipDemo에서 발췌한 이 코드는 작업 문자열 android.SipDemo.INCOMING_CALL를 기반으로 대기 중인 인텐트로 SipProfile 객체가 생성되는 방법을 보여줍니다. PendingIntent 객체는 SipProfile가 호출을 수신하면 브로드캐스트를 실행합니다.

Kotlin

val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
    SipManager.newInstance(this)
}

var sipProfile: SipProfile? = null
...

val intent = Intent("android.SipDemo.INCOMING_CALL")
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
sipManager?.open (sipProfile, pendingIntent, null)

Java

public SipManager sipManager = null;
public SipProfile sipProfile = null;
...

Intent intent = new Intent();
intent.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
sipManager.open(sipProfile, pendingIntent, null);

브로드캐스트를 인텐트 필터가 가로채고 이후 수신기 (IncomingCallReceiver)가 실행됩니다. 애플리케이션의 매니페스트 파일에서 인텐트 필터를 지정하거나 애플리케이션 ActivitySipDemo 샘플 애플리케이션 onCreate() 메서드에서처럼 코드에서 인텐트 필터를 지정할 수 있습니다.

Kotlin

class WalkieTalkieActivity : Activity(), View.OnTouchListener {
    ...
    lateinit var callReceiver: IncomingCallReceiver
    ...

    override fun onCreate(savedInstanceState: Bundle) {
        val filter = IntentFilter().apply {
            addAction("android.SipDemo.INCOMING_CALL")
        }
        callReceiver = IncomingCallReceiver()
        this.registerReceiver(callReceiver, filter)
        ...
    }
    ...
}

Java

public class WalkieTalkieActivity extends Activity implements View.OnTouchListener {
...
    public IncomingCallReceiver callReceiver;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {

       IntentFilter filter = new IntentFilter();
       filter.addAction("android.SipDemo.INCOMING_CALL");
       callReceiver = new IncomingCallReceiver();
       this.registerReceiver(callReceiver, filter);
       ...
    }
    ...
}

SIP 애플리케이션 테스트

SIP 애플리케이션을 테스트하려면 다음이 필요합니다.

  • Android 2.3 이상을 실행하는 휴대기기 - SIP는 무선으로 실행되므로 실제 기기에서 테스트해야 합니다. AVD 테스트는 작동하지 않습니다.
  • SIP 계정 - SIP 계정을 제공하는 여러 다양한 SIP 제공업체가 있습니다.
  • 전화를 걸려면 유효한 SIP 계정이 있어야 합니다.

SIP 애플리케이션을 테스트하려면 다음 단계를 따르세요.

  1. 기기에서 무선에 연결합니다 (설정 > 무선 및 네트워크 > Wi-Fi > Wi-Fi 설정).
  2. 기기에서 개발에 설명된 대로 테스트할 휴대기기를 설정합니다.
  3. 기기에서 개발에 설명된 대로 휴대기기에서 애플리케이션을 실행합니다.
  4. Android 스튜디오를 사용하는 경우 이벤트 로그 콘솔 (View > Tool Windows > Event Log)을 열어 애플리케이션 로그 출력을 볼 수 있습니다.
  5. 애플리케이션이 실행될 때 Logcat을 자동으로 실행하도록 구성되어 있는지 확인합니다.
    1. Run > Edit Configurations를 선택합니다.
    2. Run/Debug Configurations 창에서 Miscellaneous 탭을 선택합니다.
    3. Logcat에서 Show logcat automatically, OK를 차례로 선택합니다.