Tistory View

Android Note

안드로이드 전화통화상태 받기

God Dangchy What should I do? 2016. 7. 7. 06:31

앱을 만드는 과정에서 그만 전화송수신 상태에서도 음악이 흘러나오는 황당한 버그가 있었다.

자동으로 끊길거라 생각해던차라 전혀 이런 버그가 있을 거라는 상상을 하지 못했다.

게다가 테스트를 통화가 끊긴 스마트기기를 주로 쓰다보니 완전히 상상도 못했던 치명적인 버그를 그만 한달이상 두고 말았다


이번에는 통화상태를 처리하는 코드를 만들어 보자.


당연히 Android OS가 처리를 해줘야 하는 문제라 통화상태에 대한 통화상태정보는 Broadcast되는 것을 처리하기만 하면 된다.


Broadcast를 받기위해 어떤 Broadcast를 받을지를 지정한다.

AndroidManifest.xml파일에 다음의 코드를 추가한다.


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

<!--inside the application --> <receiver android:name=".CallReceiver" > <intent-filter> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.NEW_OUTGOING_CALL" /> </intent-filter> </receiver>


보면 바로 알 수 있게, 전화가 오는 것과 통화를 거는 것 두가지가 분리되어있다.


이제 Receiver에 지정한 CallReceiver class를 만들어 처리하는 코드를 만들어 넣어야 한다.

아래의 코드는 http://stackoverflow.com/questions/15563921/how-to-detect-incoming-calls-in-an-android-device 에서 가져온 코드를 수정 좀했다. 원본코드는 필자가 보기에 아주 잘 만들어진 코드로 솔직히 원본코드를 사용하는 것이 더 편하고 좋을 것이라는 생각이 든다.



intent-filter가 두 개이기 때문에, 따로 분리해서 만들수도 있지만, 비슷한 성격을 가진 것으로 보고 한 개의 class로 처리한다.

Receiver는 Broastcast를 받을때 마다 생성되기 때문에(자동으로 캐쉬되는 지는 잘 모르겠다. ), 정보를 저장할 변수를 사용할 때는 static을 쓰던지 다른 곳에 저장해두어야 한다.



원본코드

http://stackoverflow.com/questions/15563921/how-to-detect-incoming-calls-in-an-android-device


원본코드[줄 맞추고 주석 좀  추가하고..]

import java.util.Date;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;

public abstract class PhonecallReceiver extends BroadcastReceiver {

    // The receiver will be recreated whenever android feels like it.  We need a static variable to remember data
// between instantiations
// 이 Receiver의 인스턴스는 계속 재생성 되므로 값들을 유지하기위해 static를 사용한다.

    private static int lastState = TelephonyManager.CALL_STATE_IDLE;
    private static Date callStartTime;
    private static boolean isIncoming;
    private static String savedNumber;  //because the passed incoming is only valid in ringing
   

    @Override
    public void onReceive(Context context, Intent intent) {

        //We listen to two intents.  The new outgoing call only tells us of an outgoing call.  We use it to get the number.
        if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL"))
        {
            savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
        }
        else {
            String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
            String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
            int state = 0;
           
            if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE))
            {
          state = TelephonyManager.CALL_STATE_IDLE;
            }
            else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK))
            {
                state = TelephonyManager.CALL_STATE_OFFHOOK;
            }
            else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING) )
            {
                state = TelephonyManager.CALL_STATE_RINGING;
            }
   
            onCallStateChanged(context, state, number);
        }
 }

    //Derived classes should override these to respond to specific events of interest
    protected abstract void onIncomingCallReceived(Context ctx, String number, Date start);
    protected abstract void onIncomingCallAnswered(Context ctx, String number, Date start);
    protected abstract void onIncomingCallEnded(Context ctx, String number, Date start, Date end);
   
    protected abstract void onOutgoingCallStarted(Context ctx, String number, Date start);     
    protected abstract void onOutgoingCallEnded(Context ctx, String number, Date start, Date end);
   
    protected abstract void onMissedCall(Context ctx, String number, Date start);

    //Deals with actual events

    //Incoming call-  goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
    //Outgoing call-  goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up

    public void onCallStateChanged(Context context, int state, String number)
    {
        if(lastState == state){
            //No change, debounce extras
            return;
        }
 
        switch (state) {
        case TelephonyManager.CALL_STATE_RINGING:
            isIncoming = true;
            callStartTime = new Date();
            savedNumber = number;
            onIncomingCallReceived(context, number, callStartTime);
            break;
               
            case TelephonyManager.CALL_STATE_OFFHOOK:
            //Transition of ringing->offhook are pickups of incoming calls.  Nothing done on them
            if(lastState != TelephonyManager.CALL_STATE_RINGING)
{
                isIncoming = false;
                callStartTime = new Date();
                onOutgoingCallStarted(context, savedNumber, callStartTime);                    
            }
            else
            {
                isIncoming = true;
                callStartTime = new Date();
                onIncomingCallAnswered(context, savedNumber, callStartTime);
            }
            break;
                       
        case TelephonyManager.CALL_STATE_IDLE:
            //Went to idle-  this is the end of a call.  What type depends on previous state(s)
            if(lastState == TelephonyManager.CALL_STATE_RINGING)
            {
                //Ring but no pickup-  a miss
                onMissedCall(context, savedNumber, callStartTime);
            }
            else if(isIncoming) {
                onIncomingCallEnded(context, savedNumber, callStartTime, new Date());                      
            }
            else {
                onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());                                              
            }
            break;
        }
    }
    lastState = state;
}


이 클래스는 abstract니 상속을 받아서 onXXXXXX함수들만을 구현하면 되도록 만들어 졌다...( 참 잘 만들었네.. )

다른 방법으로, interface를 이용하는 방법도 좋은 방법일 것이다.

상속을 받아쓰면 파일단위로 관리되므로 찾기도 쉽고 알아보기 쉽다.

interface를 이용하면 굳이 추가적이 class를 만들 필요가 없다는 장점이 있다.

아... 그리고 보니 이 class instance를 내가 만드는 것이 아니고 자동으로 만들어 지는 거니 interface지정하는 코드를 Constructor에 넣던지 해야 되는 구나.....[코드가 알아보기 힘들어 지네...], 아... interface를 쓰는 것이 의미가 없을 수도 있다..... 그냥... 이거 써야 겠네...



뭐 어쨋든 원본코드에 따라 가야 하니 다음의 상속받은 class가 필요하다.

AndroidManifest.xml 파일에 지정한 CallReceiver class를 위의 class를 상속받아 만든다.

public class CallReceiver extends PhonecallReceiver {

    @Override
    protected abstract void onIncomingCallReceived(Context ctx, String number, Date start)
    {
        //
    }

    @Override
    protected abstract void onIncomingCallAnswered(Context ctx, String number, Date start)
    {
        //
    }

    @Override
    protected abstract void onIncomingCallEnded(Context ctx, String number, Date start, Date end)
    {
        //
    }

    @Override
    protected abstract void onOutgoingCallStarted(Context ctx, String number, Date start)
    {
        //
    } 

    @Override 
    protected abstract void onOutgoingCallEnded(Context ctx, String number, Date start, Date end)
    {
        //
    }

    @Override
    protected abstract void onMissedCall(Context ctx, String number, Date start)
    {
        //
    }

}


이제 필요한 작업을 각 함수의 쓰임새에 따라 알아서 넣으면 될 것이다.



Replies
Reply Write