본문 바로가기
개발/APP

안드로이드 발신, 수신 통화 모니터링하기

by 카루딘 2018. 10. 11.
반응형

출처: [안드로이드/Android]전화수신시 전화번호 가져와서 팝업으로 띄우는 방법

        http://gun0912.tistory.com/46 [박상권의 삽질블로그]


저는 모르는 전화번호로 걸려오는 전화는 잘 받지 않습니다.

하지만 중고거래를 하기위해 중고물건을 여러 사이트에 올려두었을때는 혹시 구매자가 전화한것일수도 있기때문에 그 시기에는 걸려오는 전화를 꼭꼭 받습니다.


셀폰이라는 중고폰 거래중개서비스에서는 내가 올려둔 중고폰 판매글을 보고 구매자가 전화하는경우 아래 이미지처럼 정보를 알려줍니다.

이러한 경우 모르는 번호이더라도 내 판매글을 보고 연락한 사람임을 알 수 있기 때문에 안심하고 전화를 받을 수 있습니다.





요즘 많이 쓰고있는 후후나 후스콜 같은 경우도 이와 비슷하게 전화가 걸려오는경우를 감지해서 해당전화번호가 스팸인지 아닌지를 확인할 수 있습니다.

여기서 이용되는 안드로이드에서의 기능은 BroadcastReceiver, Service 입니다.



대부분 이러한 서비스들의 동작순서는 아래와 같습니다.


1. 전화가 오는경우 감지를 위한 BroadcastReceiver 생성 및 등록

2. 앱이 실행중이지 않은경우에도 기능을 실행하기위한 Service 생성 및 등록

3. 띄워줄 화면의 레이아웃 생성 및 추가



그럼 BroadcastReceiver, Service를 이용해서 전화가 걸려오는 경우 걸려온 전화번호를 팝업으로 띄워서 보여주는 예제를 만들어 보겠습니다.










BroadcastReceiver 


전화가 오는상황을 감지하기 위해 전화상태를 읽을수있는 권한을 Manifest에 추가해주어야 합니다.

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


또한, receiver도 추가해 줍니다.


<receiver android:name=".IncomingCallBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
</intent-filter>
</receiver>




BroadcastReceiver의 onReceive()에서 전화가 오는경우를 감지하고 Service를 실행하는 부분을 만들어 줍니다.

여기서는 통화벨이 울리는 경우 걸려온 전화번호를 알아낸뒤, 그 전화번호를 Service로 넘겨주면서 Service를 실행하도록 하겠습니다.


if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
final String phone_number = PhoneNumberUtils.formatNumber(incomingNumber);

Intent serviceIntent = new Intent(context, CallingService.class);
serviceIntent.putExtra(CallingService.EXTRA_CALL_NUMBER, phone_number);
context.startService(serviceIntent);

}


(참고)

TelephonyManager.EXTRA_STATE_IDLE: 통화종료 혹은 통화벨 종료

TelephonyManager.EXTRA_STATE_RINGING: 통화벨 울리는중

TelephonyManager.EXTRA_STATE_OFFHOOK: 통화중





#주의

정확한 이유는 알 수 없지만 실제 BroadcastReceiver가 여러번 호출되는 경우를 볼 수 있습니다.

구글링을 해보면 저뿐만 아니라 많은 사람들이 이러한 문제로 어려움을 겪고 있습니다.

이렇게되면 우리가 의도한대로 되지않고 서비스가 여러번 호출되는 상황을 발생시키기도 합니다.

이를 해결하기 위해 static 변수를 하나 두고나서 해당 변수의 상태가 변경될때에만 실제 변경됨을 감지하고 다음동작을 실행하도록 해주었습니다.

private static String mLastState;

if (state.equals(mLastState)) {
return;

} else {
mLastState = state;

}

(참고: http://mmarvick.github.io/blog/blog/lollipop-multiple-broadcastreceiver-call-state/ )




public class IncomingCallBroadcastReceiver extends BroadcastReceiver {
public static final String TAG = "PHONE STATE";
private static String mLastState;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
public void onReceive(final Context context, Intent intent) {
Log.d(TAG,"onReceive()");
/**
* http://mmarvick.github.io/blog/blog/lollipop-multiple-broadcastreceiver-call-state/
* 2번 호출되는 문제 해결
*/
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if (state.equals(mLastState)) {
return;
} else {
mLastState = state;
}
if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
final String phone_number = PhoneNumberUtils.formatNumber(incomingNumber);
Intent serviceIntent = new Intent(context, CallingService.class);
serviceIntent.putExtra(CallingService.EXTRA_CALL_NUMBER, phone_number);
context.startService(serviceIntent);
}
}
}









Layout


팝업으로 보여줄 layout xml을 만들어줍니다.

여기서는 간단하게 걸려온 전화번호를보여주고 팝업창을 종료할수 있는 X버튼을 만들어 두겠습니다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#f00"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:id="@+id/tv_call_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text=""
android:textColor="#fff"
android:textSize="20sp" />
<ImageButton
android:id="@+id/btn_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:src="@drawable/abc_ic_clear_mtrl_alpha" />
</LinearLayout>
view raw call_popup_top.xml hosted with ❤ by GitHub






Service


Manifest에 Service를 추가해줍니다.


<service android:name=".CallingService"/>


Service의 onCreate()에서 팝업의 크기와 각종 팝업과 관련된 설정들을 세팅해줍니다.

여기서는 팝업크기의 가로가 화면의 90%만 채우도록 설정해주었습니다.

그리고 위에서 만들어둔 popup layout xml을 가져옵니다.


@Override
public void onCreate() {
super.onCreate();


windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

Display display = windowManager.getDefaultDisplay();

int width = (int) (display.getWidth() * 0.9); //Display 사이즈의 90%


params = new WindowManager.LayoutParams(
width,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
PixelFormat.TRANSLUCENT);


LayoutInflater layoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
rootView = layoutInflater.inflate(R.layout.call_popup_top, null);
ButterKnife.inject(this, rootView);
setDraggable();


}



setDragable()함수에서는 팝업View에 setOnTouchListener를 추가해서 이동시킬 수 있도록 설정해줍니다.

팝업View를 누르고 움직일때마다 이동거리만큼 같이 움직여 줍니다.


private void setDraggable() {

rootView.setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;

@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
return true;
case MotionEvent.ACTION_UP:
return true;
case MotionEvent.ACTION_MOVE:
params.x = initialX + (int) (event.getRawX() - initialTouchX);
params.y = initialY + (int) (event.getRawY() - initialTouchY);

if (rootView != null)
windowManager.updateViewLayout(rootView, params);
return true;
}
return false;
}
});

}




onStartCommand()에서 세팅해놓음 팝업View를 WindowManager에 추가해줍니다.

넘겨받은 전화번호 정보를 가져와서 팝업View의 TextView에도 세팅해줍니다.

onStartCommand()에서 START_REDELIVER_INTENT로 설정하는경우 Service가 강제종료 되더라도 다시 시작해주고 이전에 넘겨받았던 intent를 그대로 넘겨받을 수 있습니다.

여기서는 STICKY로 해주어도 크게 상관없을것 같습니다.

(Android Service 사용법)

만약 intent가 null인경우엔 팝업View를 없애줍니다.


@Override
public int onStartCommand(Intent intent, int flags, int startId) {

windowManager.addView(rootView, params);
setExtra(intent);


if (!TextUtils.isEmpty(call_number)) {
tv_call_number.setText(call_number);
}


return START_REDELIVER_INTENT;
}


private void setExtra(Intent intent) {

if (intent == null) {
removePopup();
return;
}

call_number = intent.getStringExtra(EXTRA_CALL_NUMBER);


}





서비스가 종료되거나 팝업View의 X버튼이 눌리는경우 팝업View를 없애줍니다.



@Override
public void onDestroy() {
super.onDestroy();
removePopup();
}


@OnClick(R.id.btn_close)
public void removePopup() {
if (rootView != null && windowManager != null) windowManager.removeView(rootView);
}





public class CallingService extends Service {
public static final String EXTRA_CALL_NUMBER = "call_number";
protected View rootView;
@InjectView(R.id.tv_call_number)
TextView tv_call_number;
String call_number;
WindowManager.LayoutParams params;
private WindowManager windowManager;
@Override
public IBinder onBind(Intent intent) {
// Not used
return null;
}
@Override
public void onCreate() {
super.onCreate();
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
int width = (int) (display.getWidth() * 0.9); //Display 사이즈의 90%
params = new WindowManager.LayoutParams(
width,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
PixelFormat.TRANSLUCENT);
LayoutInflater layoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
rootView = layoutInflater.inflate(R.layout.call_popup_top, null);
ButterKnife.inject(this, rootView);
setDraggable();
}
private void setDraggable() {
rootView.setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
return true;
case MotionEvent.ACTION_UP:
return true;
case MotionEvent.ACTION_MOVE:
params.x = initialX + (int) (event.getRawX() - initialTouchX);
params.y = initialY + (int) (event.getRawY() - initialTouchY);
if (rootView != null)
windowManager.updateViewLayout(rootView, params);
return true;
}
return false;
}
});
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
windowManager.addView(rootView, params);
setExtra(intent);
if (!TextUtils.isEmpty(call_number)) {
tv_call_number.setText(call_number);
}
return START_REDELIVER_INTENT;
}
private void setExtra(Intent intent) {
if (intent == null) {
removePopup();
return;
}
call_number = intent.getStringExtra(EXTRA_CALL_NUMBER);
}
@Override
public void onDestroy() {
super.onDestroy();
removePopup();
}
@OnClick(R.id.btn_close)
public void removePopup() {
if (rootView != null && windowManager != null) windowManager.removeView(rootView);
}
}
view raw CallingService.java hosted with ❤ by GitHub




그렇게 완성된 화면은 아래와 같습니다.

전화가 걸려올경우 상대방의 전화번호를 넘겨받아서 팝업으로 보여주게 됩니다.





이를 잘 활용해서 가져온 전화번호를 운영중인 서비스의 서버에 요청해서 각종 정보를 가져오는 기능을 만들 수도 있습니다.

여러가지 응용된 방식으로 사용자에게 의미있는 정보를 노출해주길 바랍니다.




이것저것 다 귀찮으신분들을 위해 Github에 프로젝트를 올려두었습니다.

Github에서 다운받으셔서 테스트프로젝트 실행해보셔도 좋습니다.

Github에서 전체프로젝트 보기


(내용이 유용하셨다면 Github 오른쪽위의 [Star]버튼을 눌러주시면 감사하겠습니다

저에게는 별풍선 주시는 효과가 있어요!)





출처: http://blog.chopestory.net/97


BroadcastReceiver 를 이용하면 수신 전화를 모니터링할 수 있습니다. 이것은 네이버나 구글이나 어디서 검색하든지 쉽게 얻을 수 있는 정보 입니다.
하지만 발신 정보를 얻기란 정말 힘듭니다. 전화 상태로 발신을 구분을 할수 있습니다.

발신 : IDLE -> OFFHOOK -> IDLE
수신 : IDLE -> RINGING -> OFFHOOK -> IDLE

하지만 수신 번호가 아닌 발신 번호는 알 수 없습니다.
그래서 몇일 동안 검색을 통해 알아내었습니다.

AndroidManifest.xml

1
2
3
4
5
6
<receiver android:name="yhg.tac.receiver.PhoneStateReceiver">
    <intent-filter>
        <action android:name="android.intent.action.NEW_OUTGOING_CALL"></action>
        <action android:name="android.intent.action.PHONE_STATE"></action>
    </intent-filter>
</receiver>

Log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Log {
    private long id;
    private String number;
    private LogKind kind;
     
    public Log(String number, LogKind kind){
        this.number = number;
        this.kind = kind;
    }
 
    public void setId(long id) {
        this.id = id;
    }
     
    public long getId() {
        return id;
    }
     
    public String getNumber() {
        return number;
    }
     
    public LogKind getKind() {
        return kind;
    }
     
    protected String getTimeString(long time){
        return DateFormat.format("yyyy-mm-dd hh:mm:ss",time).toString();
    }
}

CallLog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class CallLog extends Log {
    private String ringingDate;
    private String startDate;
    private String endDate;
     
    public CallLog(String number, LogKind kind){
        super(number, kind);
    }
     
    public void setRingingDate(long time) {
        this.ringingDate = getTimeString(time);
    }
 
    public void setStartDate(long time) {
        this.startDate = getTimeString(time);
    }
 
    public void setEndDate(long time) {
        this.endDate = getTimeString(time);
    }
 
    public String getRingingDate() {
        return ringingDate;
    }
     
    public String getStartDate() {
        return startDate;
    }
     
    public String getEndDate() {
        return endDate;
    }
     
    public String toString(){
        StringBuilder buffer = new StringBuilder();
        buffer.append(getKind()).append(" / ")
            .append(getNumber()).append(" / ")
            .append(getId()).append(" / ")
            .append(getRingingDate()).append(" / ")
            .append(getStartDate()).append(" / ")
            .append(getEndDate());
         
        return buffer.toString();
    }
}

PhoneStateReceiver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class PhoneStateReceiver extends BroadcastReceiver {
    private static int pState = TelephonyManager.CALL_STATE_IDLE;
    private static CallLog cLog;
     
    public void onReceive(Context context, final Intent intent) {
        TelephonyManager telManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
 
        telManager.listen(new PhoneStateListener(){
            public void onCallStateChanged(int state, String incomingNumber){
                if(state != pState){
                    if(state == TelephonyManager.CALL_STATE_IDLE){
                        Log.i("Phone","IDLE");
                        if(cLog != null){
                            cLog.setEndDate(System.currentTimeMillis());
                            Log.i("Phone",cLog.toString());
                            cLog = null;
                        }
                    }
                    else if(state == TelephonyManager.CALL_STATE_RINGING){
                        Log.i("Phone","RINGING");
                        cLog = new CallLog(incomingNumber, LogKind.KIND_RECEIVE);
                        cLog.setRingingDate(System.currentTimeMillis());
                    }
                    else if(state == TelephonyManager.CALL_STATE_OFFHOOK){
                        Log.i("Phone","OFFHOOK");
                        cLog.setStartDate(System.currentTimeMillis());
                    }
                     
                    pState = state;
                }
            }
        }, PhoneStateListener.LISTEN_CALL_STATE);
         
        if(intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)){
            Log.i("Phone","out");
            cLog = new CallLog(intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER), LogKind.KIND_SEND);
            cLog.setRingingDate(System.currentTimeMillis());
        }
    }
}


Logcat Log

발신시


수신시


제가 쓰려고 만들어 보았습니다. 허접하지만 이해하시는데 문제 없을거에요~ ^^ 


반응형