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 을 등록하는 것으로 코드 구현은 다 되었습니다. 이제 잘 사용해 보는 것만 남았습니다.
'모바일 앱(안드로이드)' 카테고리의 다른 글
안드로이드 앱 만들기 : GoogleMap(구글맵) 활용시 주의 할 점 하나 (0) | 2022.11.30 |
---|---|
안드로이드 앱 만들기 : MMS 의 정보 확인에 대한 이야기 (0) | 2022.11.24 |
개발일기 # 번외편3 : 앱의 디자인에 대한 평가 받아 보기 (0) | 2022.11.16 |
개발일기 # 번외편2 : 난 서버가 없는데(Serverless) cloud function 사용해 보기 (0) | 2022.11.12 |
개발일기 # 번외편 : 안드로이드 앱도 웹 서버가 될까 ? (0) | 2022.11.07 |