원본출처: 티스토리 바로가기
Telephony
우리가 사용하는 스마트 폰의 기능 중에서 SMS (short message service)와 LMS (Long message Service)의 수신에 대한 이야기를 해 볼까 합니다.
https://support.google.com/googleplay/android-developer/answer/9888170
구글은 사용자의 개인정보보호등의 사유 등을 들어 SMS 기본 처리 앱으로 허가되는 경우를 제외하고는 SMS의 수신에 대한 허가를 얻어야만 그 권한을 사용하는 앱의 playstore 등재를 허락하고 있습니다. 그런 사유로 해서 SMS을 수신하는 앱을 playstore에 게시하는 것은 개인 개발자가 하기에는 어려운 사항이 생기고 있습니다.
SMS 읽기
그래도 playstore 에 등록하는 경우가 아닌 개인적으로 사용하고자 하는 경우에는 이 기능에 대한 제한을 할 수 없을 듯합니다. 그래서 이번에는 SMS 수신하는 앱을 하나 만들어 볼까 합니다.
먼저 권한 획득을 해 보겠습니다.
<uses-permission android:name="android.permission.RECEIVE_SMS" />
PERMISSION 얻기
SMS 등 민간한 권한의 경우는 manifest에 선언을 하는 것뿐만 아니라 코드 내부에서도 권한을 다시 허가를 받아야 한 하도록 google에서 제한하고 있습니다. 이렇게 허가를 받는 다고 해도 playstore에 게시할 때도 다시 제한을 하고 있으니 (2022.11.21 현재에는...) playstore에 게시를 하고 싶다면 다시 생각을 해 보아야 합니다.
이제 코드를 살펴 보도록 하겠습니다.
@OptIn(ExperimentalPermissionsApi::class) @Composable private fun FeatureThatRequiresReceiveSmsPermission( doResult:(ty:Boolean) -> Unit ) { // Camera permission state val receiveSmsPermissionState = rememberPermissionState( Manifest.permission.RECEIVE_SMS ) when (receiveSmsPermissionState.status) { // If the camera permission is granted, then show screen with the feature enabled PermissionStatus.Granted -> { doResult(true) } is PermissionStatus.Denied -> { Column( modifier = Modifier.padding(3.dp), horizontalAlignment = Alignment.End ) { val textToShow = if ((receiveSmsPermissionState.status as PermissionStatus.Denied).shouldShowRationale) { // If the user has denied the permission but the rationale can be shown, // then gently explain why the app requires this permission stringResource(id = R.string.msgGetPermissonSms) } else { // If it's the first time the user lands on this feature, or the user // doesn't want to be asked again for this permission, explain that the // permission is required stringResource(id = R.string.msgGetPermissonSms) } IconButton(onClick = { receiveSmsPermissionState.launchPermissionRequest() doResult(false) }) { Icon( imageVector = Icons.Outlined.Sms, contentDescription = "Sms", tint = softBlue ) } Text(textToShow) } } } }
코드는 google 에서 검색해서 찾은 코드를 일부 수정했습니다. java 코드로 구현할 때 보다 훨씬 수월하게 구현이 됩니다. 간단하게 구현이 되네요.
코드 인용
https://google.github.io/accompanist/permissions/
참고한 사이트의 내용을 보면 저 코드를 구현하기 위해서는 gradle 에 설정이 들어가야 합니다. 다음과 같습니다.
// 권한 획득 implementation "com.google.accompanist:accompanist-permissions:0.27.1"
이걸 이제 호출해서 잘 동작 하는 지 봐야 할 것 같아서 구분의 구현에 대한 코드를 보여 드립니다.
setContent { var isGrantCamera by remember { mutableStateOf(false) } var isGrantPhone by remember { mutableStateOf(false) } SmsReceiver1113Theme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background ) { Column(modifier = Modifier .fillMaxSize() .padding(3.dp), ) { FeatureThatRequiresReceiveSmsPermission( doResult = { isGrantCamera = it } ) ... ScreenView( appVersion, ... ) } } } }
위 코드와 같이 mainactivity 에서 호출을 하는 것만으로 권한이 획득됩니다. 실행된 모습은 아래 그림과 같습니다.
이제 저 버튼을 클릭 하면 저 문자 이미지 버튼이 사라집니다. 그것으로 권한 획득이 이루어집니다.
SMS 수신기 만들기
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 com.billcoreatech.smsreceiver1113.BuildConfig import com.billcoreatech.smsreceiver1113.retrofit.RetrofitService import com.billcoreatech.smsreceiver1113.retrofit.SmsInfoBean import java.text.SimpleDateFormat import java.util.* class MySmsReceiver : BroadcastReceiver() { private var TAG = "MySmsReceiver" private lateinit var context: Context var contentBp = mutableListOf<String>() override fun onReceive(context: Context, intent: Intent) { Log.e("", "onReceive ...") this.context = context if(intent?.action.equals("android.provider.Telephony.SMS_RECEIVED")){ val bundle = intent?.extras val messages = smsMessageParse(bundle!!) var content = "" if(messages?.size!! > 0){ // LMS 수신을 위해서 contentBp.clear() for(message in messages) { Log.e(TAG, "message=${message?.messageBody}") contentBp.add(message?.messageBody.toString()) } content = contentBp.toString() // 수신 문자 내용 전체 var number = messages[0]?.originatingAddress.toString() // 전송한 전화번호 var currentTime = messages[0]?.timestampMillis // 메시지 수신시간 Log.e("TAG ... ","get ${number} ${content} ${certNumber}") var sdf = SimpleDateFormat("yyyy-MM-dd kk:mm:ss", Locale("ko", "KR")) // val currentTime = Date(System.currentTimeMillis()) Log.e("", "date time = ${sdf.format(currentTime)} ${content}") var sp = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) if ("debug".equals(BuildConfig.BUILD_TYPE)) { ..... } else { ..... } } } } fun smsMessageParse(bundle: Bundle): Array<SmsMessage?>? { val objs = bundle["pdus"] as Array<Any>? val messages: Array<SmsMessage?> = arrayOfNulls<SmsMessage>(objs!!.size) for (i in objs!!.indices) { messages[i] = SmsMessage.createFromPdu(objs[i] as ByteArray) } return messages } }
이 코드는 문자가 수신되면 안드로이드가 알려 주는 broadcasting receiver 입니다. 여기서 기억을 하고 가야 할 부분은 다음 부분입니다. 문자가 수신되면 bundle.extra 에서 sms messages 가져옵니다. 그중에서 messageBody 부분은 array로 구성되어 긴 문자가 오게 되면 각각의 array 공간에 쌓이게 됩니다. 이걸 다 가지고 올 건가, 아니면 처음 (index 가 0 인)만 가지고 올 건가 에 따라서 수신하는 메시지가 전체인지 아닌지 알게 된다는 것입니다.
// LMS 수신을 위해서 contentBp.clear() for(message in messages) { Log.e(TAG, "message=${message?.messageBody}") contentBp.add(message?.messageBody.toString()) }
이제 manifest 에 수신기를 등록해 보겠습니다.
<receiver android:name=".MySmsReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver>
이렇게 receiver 을 등록하는 것으로 코드 구현은 다 되었습니다. 이제 잘 사용해 보는 것만 남았습니다.
댓글
댓글 쓰기