안드로이드 SMS 문자 읽기 - andeuloideu SMS munja ilg-gi

git : //github.com/ManSung-Kim/SMSForwardMS/blob/master/src/com/jactlab/smsforwardms/service/SMSReceiverMS.java

Device로 전송된 SMS를 읽으려면 BroadcastReceiver를 이용하여 "android.provider.Telephony.SMS_RECEIVED" 엑션을 받으면 된다.

% BroadcastReceiver를 사용하는 방법을 정리 해놓은 글이 있는줄 알았는데 찾아보니 없는것같다. 나중에 시간되면 따로 글 빼놔야겠다. 퇴근하고 벌써 22시 44분이라 작성할 시간이 없다..

% 나중에 쓴다는게 깔끔하지 못해서 이 글 작성 미루고 Broadcast 관련 기초 문서 작성

//mantdu.tistory.com/871

# Reciver에서 sms received Action 받기

 regisetReciver 하기 전에 Intent에 IntentFilter로 "android.provider.Telephony.SMS_RECEIVED";를 추가한다. sms가 날라오면 해당 Intent가 날라온다.

# 문자 string 읽기

 Android SMS는 PDUS(Protocol data unit, //en.wikipedia.org/wiki/Protocol_data_unit)이라는 형태로 날라온다. PDU는 프로토콜이라 정확한 기술사항을 들여다 볼 시간이 없어서 분석은 못했다. 단순히 우리는 메시지만 사용할 것이기 때문에 아래와 같이 내용을 갖고오면 된다.

(//developer.android.com/reference/android/telephony/SmsMessage.html#createFromPdu(byte[]))

Bundle bundle = intent.getExtras(); Object[] msg = (Object[]) bundle.get("pdus"); SmsMessage[] smsMsg = new SmsMessage[msg.length]; for(int i=0; i< msg.length; i++) { smsMsg[i] = SmsMessage.createFromPdu((byte[])msg[i]); }

Broadcast Receiver(방송수신자)란?

안드로이드 시스템에서는 전화가 오거나 문자메시지가 오는 등 특정 상황에서 방송을 내보낸다.

이런 시스템에서 방송을 해주는 이벤트들을 Global Event라고 하며,

이를 앱에서 받아 처리하려면 Broadcast Receiver에 등록해주어야 한다.

SMS 수신 과정

Broadcast Receiver에 등록된 종류의 방송이 오면 이를 수신해 intent를 통해 전달받게 된다.

또한 시스템의 방송을 수신하는 것 뿐만 아니라 앱에서 방송을 보낼 수도 있다.

기본적인 사용법

생성은 마찬가지로 Project Explorer에서 app 우클릭 후 New - Other - Broadcast Receiver를 클릭하면 된다.

클래스 이름을 설정해주고 Finish를 눌러주면 자동으로 클래스 파일이 생성되고 Manifest에 등록된다.

package com.juhy.myapplication; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class SMSReceiver extends BroadcastReceiver { private static final String TAG = "SMSReceiver"; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive() called"); } }

만들어준 클래스에서는 일단 log만 찍도록 구현했다.

그리고 그동안 activity나 service는 MainActivity에서 intent를 통해 실행시켜 주었지만,

Broadcast Receiver는 manifest에 등록해주기만 하면 자동으로 수신할 수 있다.

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="//schemas.android.com/apk/res/android" package="com.juhy.myapplication"> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <receiver android:name=".SMSReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

그 다음으로는 Manifest에서 여러 가지 설정을 해주어야 한다.

먼저 생성된 receiver 태그 안에 intent-filter 태그를 추가하여 우리가 받고싶은 방송의 종류를 지정해야 한다.

시스템에서 방송하는 내용이 많은데 이를 모두 수신할 필요는 없기 때문이다.

이번에는 문자가 왔을 때를 보기 위해 android.provider.Telephony.SMS_RECEIVED를 추가해주었다.

그리고 앱에서 문자를 수신하기 위해서는 권한이 필요하므로 application 태그 위에 권한 요청도 추가해주었다.

※ Marshmallow 이후 버전부터는 SMS 수신이 위험 권한으로 설정되어 사용자에게 권한 요청을 한 후 동의를 얻어야 해당 기능을 사용할 수 있다. 따라서 이 코드는 Target SDK가 Marshmallow 이하인 경우에만 작동한다. 이후 버전에서 작동하게 하려면 권한 요청 코드를 추가해주어야 한다.

private void requirePerms(){ String[] permissions = {Manifest.permission.RECEIVE_SMS}; int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS); if(permissionCheck == PackageManager.PERMISSION_DENIED){ ActivityCompat.requestPermissions(this, permissions, 1); } }

일단은 간단하게 권한이 없을 경우 요청하도록 하는 함수를 MainActivity에 추가해준다.

그리고 에뮬레이터(가상머신)에서 SMS 수신을 테스트하고 싶다면 에뮬레이터 우측 메뉴에서 ··· 클릭 후 Phone 탭에서 전송이 가능하다.

메세지 전송을 누르면 에뮬레이터에 정상적으로 수신된 것을 볼 수 있고

로그에서도 onReceive() 메소드가 정상적으로 호출된 것을 볼 수 있다.

(뒤로가기 버튼을 눌러 앱을 종료해도 Broadcast Receiver는 작동한다)

SMS 내용 분석하기

package com.juhy.myapplication; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.telephony.SmsMessage; import android.util.Log; import java.util.Date; public class SMSReceiver extends BroadcastReceiver { private static final String TAG = "SMSReceiver"; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive() called"); Bundle bundle = intent.getExtras(); SmsMessage[] messages = parseSmsMessage(bundle); if(messages.length > 0){ String sender = messages[0].getOriginatingAddress(); String content = messages[0].getMessageBody().toString(); Date date = new Date(messages[0].getTimestampMillis()); Log.d(TAG, "sender: " + sender); Log.d(TAG, "content: " + content); Log.d(TAG, "date: " + date); } } private SmsMessage[] parseSmsMessage(Bundle bundle){ // PDU: Protocol Data Units Object[] objs = (Object[]) bundle.get("pdus"); SmsMessage[] messages = new SmsMessage[objs.length]; for(int i=0; i<objs.length; i++){ messages[i] = SmsMessage.createFromPdu((byte[])objs[i]); } return messages; } }

기본적으로 방송은 intent를 통해 전달되는데 이 때 extra 데이터에 bundle의 형태로 방송의 정보가 들어오게 된다.

먼저 이 bundle을 SmsMessage 배열로 변환하는 함수를 작성해보자.

bundle 안에는 "pdus"라는 key 값으로 SMS 정보가 들어가 있다.

안드로이드에서는 SMS가 들어오면 이를 PDU(Protocol Data Units) 형태로 전달해주기 때문이다.

bundle로부터 메시지들을 받아왔다면 SmsMessage 배열을 그 크기만큼 만들어 준 뒤,

반복문을 통해 하나씩 PDU를 SmsMessage 객체로 변환해주어야 하는데 이는 createFromPdu() 함수를 사용하면 된다.

SmsMessage로 변환이 완료되었다면

발신자는 getOriginatingAddress(),

메시지 내용은 getMessageBody(),

시간은 getTimestampMillis() 함수를 통해 받아올 수 있다.

실행 후 문자를 보내보면 정상적으로 SMS 정보들이 수신된 것을 볼 수 있다.

이제는 Broadcast Receiver에서 수신한 정보를 activity로 보내 띄워보자.

private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private void sendToActivity(Context context, String sender, String content, Date date){ Intent intent = new Intent(context, SmsActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_ACTIVITY_SINGLE_TOP |Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.putExtra("sender", sender); intent.putExtra("content", content); intent.putExtra("date", format.format(date)); context.startActivity(intent); }

date를 String으로 변환하기 위한 format 정보를 먼저 만들어주고,

intent에 새로운 task 생성을 위한 flag를 설정해주고,

extra에 수신한 데이터를 넣어준 뒤 context 객체의 startActivity() 함수를 통해 intent를 보내보자.

(이 때 context 객체는 onReceive() 메소드의 파라미터에 있으므로 이를 다시 파라미터로 전달해주자)

package com.juhy.myapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; import android.widget.TextView; public class SmsActivity extends AppCompatActivity { TextView tv_sender; TextView tv_date; TextView tv_content; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sms); tv_sender = findViewById(R.id.textView_sender); tv_date = findViewById(R.id.textView_date); tv_content = findViewById(R.id.textView_content); Intent intent = getIntent(); processCommand(intent); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); processCommand(intent); } private void processCommand(Intent intent){ if(intent != null){ String sender = intent.getStringExtra("sender"); String date = intent.getStringExtra("date"); String content = intent.getStringExtra("content"); tv_sender.setText(sender); tv_date.setText(date); tv_content.setText(content); } } }

액티비티에서는 intent가 들어오면 extra 정보를 추출하여 TextView에 설정해주도록 했다.

실행해보면 문자가 수신되면 정상적으로 액티비티가 뜨고 정보가 표시되는 것을 볼 수 있다.

Reference

[부스트코스]안드로이드 프로그래밍

//www.edwith.org/boostcourse-android

Toplist

최신 우편물

태그