반응형
SmsBroadcastReceiver.kt
package com.test.sms
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel
class SmsBroadcastReceiver : BroadcastReceiver() {
companion object {
private var binaryMessenger: BinaryMessenger? = null
private const val TAG = "smsTest"
fun setBinaryMessenger(messenger: BinaryMessenger) {
binaryMessenger = messenger
}
}
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "🎁🎁 수신 성공")
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val status: Status? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
extras?.getParcelable(SmsRetriever.EXTRA_STATUS, Status::class.java)
} else {
@Suppress("DEPRECATION")
extras?.getParcelable(SmsRetriever.EXTRA_STATUS)
}
Log.d(TAG, "받은: 🎁🎁 status: $status")
Log.d(TAG, "statusCode: ${status?.statusCode}")
when (status?.statusCode) {
CommonStatusCodes.SUCCESS -> {
Log.d(TAG, "onReceive: SMS Received")
val message = extras?.getString(SmsRetriever.EXTRA_SMS_MESSAGE)
Log.d(TAG, "message: $message")
binaryMessenger?.let {
MethodChannel(it, "sms_retriever").invokeMethod("onSmsReceived", message)
}
}
// SMS Retriever API가 타임아웃되었을 때
CommonStatusCodes.TIMEOUT -> {
Log.d(TAG, "onReceive: SMS Retriever timed out")
}
// 기타 상태 코드
else -> {
Log.d(TAG, "onReceive: Unknown status code: ${status?.statusCode}")
}
}
} else {
Log.d(TAG, "🚨🚨🚨 SMS Retriever failed")
}
}
}
MainActivity.kt
package com.test.sms
import AppSignatureHelper
import android.util.Log
import com.google.android.gms.auth.api.phone.SmsRetriever
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
// MainActivity 클래스는 FlutterActivity를 상속받아 Flutter 앱과 Android 플랫폼 간의 통신을 담당합니다.
class MainActivity: FlutterActivity() {
// MethodChannel을 통해 Flutter와 통신할 채널 이름을 정의합니다.
private val CHANNEL = "sms_retriever"
// Flutter 엔진을 구성하는 메서드입니다.
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// MethodChannel을 설정하고, 특정 메서드 호출을 처리합니다.
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
// Flutter에서 "startSmsRetriever" 메서드가 호출되었을 때 startSmsRetriever 메서드를 실행합니다.
if (call.method == "startSmsRetriever") {
startSmsRetriever(result)
val helper = AppSignatureHelper(this)
val hash = helper.getAppSignatures()?.get(0)
Log.d("smsTest", "Hash: $hash")
}
}
// SmsBroadcastReceiver 클래스에 Flutter 엔진의 바이너리 메신저를 설정합니다.
SmsBroadcastReceiver.setBinaryMessenger(flutterEngine.dartExecutor.binaryMessenger)
}
// SMS Retriever API를 시작하는 메서드입니다.
private fun startSmsRetriever(result: MethodChannel.Result) {
// SmsRetriever 클라이언트를 가져옵니다.
val client = SmsRetriever.getClient(this)
// SMS Retriever를 시작합니다.
client.startSmsRetriever()
.addOnSuccessListener {
// 성공적으로 시작되면 Flutter에 성공 메시지를 반환합니다.
result.success("SMS Retriever started")
Log.d("smsTest", "SMS Retriever started")
}
.addOnFailureListener {
// 시작 실패 시 Flutter에 에러 메시지를 반환합니다.
result.error("Error", "Failed to start SMS Retriever", null)
}
}
}
HashCheck.kt
어차피 해쉬코드는 모든 클라이언트단에서 동일하니 한 번 뽑고 안뽑아도 된다!
추출법
https://hooninha.tistory.com/114
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.util.Base64
import android.util.Log
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Arrays
class AppSignatureHelper(context: Context) : ContextWrapper(context) {
val TAG = AppSignatureHelper::class.java.simpleName
private val HASH_TYPE = "SHA-256"
val NUM_HASHED_BYTES = 9
val NUM_BASE64_CHAR = 11
init {
getAppSignatures()
}
fun getAppSignatures(): ArrayList<String>? {
val appCodes = ArrayList<String>()
try {
val packageName = packageName
val packageManager = packageManager
val signatures: Array<Signature> = packageManager.getPackageInfo(
packageName, PackageManager.GET_SIGNATURES
).signatures
for (signature in signatures) {
val hash = hash(packageName, signature.toCharsString())
if (hash != null) {
appCodes.add(String.format("%s", hash))
}
Log.e(TAG, "Hash $hash")
}
} catch (e: PackageManager.NameNotFoundException) {
Log.e(TAG, "Unable to find package to obtain hash.", e)
}
return appCodes
}
private fun hash(packageName: String, signature: String): String? {
val appInfo = "$packageName $signature"
try {
val messageDigest = MessageDigest.getInstance(HASH_TYPE)
messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
var hashSignature = messageDigest.digest()
hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES)
var base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP)
base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR)
Log.e(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash))
return base64Hash
} catch (e: NoSuchAlgorithmException) {
Log.e(TAG, "hash:NoSuchAlgorithm", e)
}
return null
}
}
이제 안드로이드 설정은 끝났다! 인증번호 체크해 주는 파일에 가자.
_handleSmsMessages 에서 SMS 인증번호 otp를 받아서 컨트롤러에 넣어주면 자동완성된다! (otp 필드가 다 채워지면 SMS 인증 Api 실행되게 만들자!) otp 추천 패키지로는 아래 패키지가 제일 좋았다.
https://pub.dev/packages/pinput
s_verification_check.dart
(믹싱으로 가져온 VerificationTimer은 따로 안하셔도 됩니다)
class _VerificationCheckScreenState
extends ConsumerState<VerificationCheckScreen>
with VerificationTimer {
static const platform = MethodChannel('sms_retriever');
final TextEditingController _controller = TextEditingController();
// sms 수신 대기 시작
Future<void> _startSmsRetriever() async {
try {
final String result = await platform.invokeMethod('startSmsRetriever');
if(result == 'SMS Retriever started'){
debugPrint('🏮🏮🏮SMS Retriever started successfully: $result');
}
} on PlatformException catch (e) {
debugPrint("🏮🏮🏮Failed to start SMS Retriever: '${e.message}'");
}
}
// 가져온 메시지 값을 otp 필드에 주입
Future<void> _handleSmsMessages(MethodCall call) async {
if (call.method == 'onSmsReceived') {
debugPrint("🎀🎀SMS received: ${call.arguments}");
final String message = call.arguments as String;
final String code = _extractCodeFromMessage(message);
setState(() {
_controller.text = code;
});
}
}
// 6자리 코드만 가져오깅
String _extractCodeFromMessage(String message) {
final RegExp codeRegExp = RegExp(r'\d{6}');
final match = codeRegExp.firstMatch(message);
return match?.group(0) ?? '';
}
@override
void initState() {
super.initState();
/// SMS 자동 읽기 시작
_startSmsRetriever();
/// 타이머 시작
startTimer();
/// SMS 자동 읽기 이벤트 핸들러 등록
platform.setMethodCallHandler(_handleSmsMessages);
}
@override
Widget build(BuildContext context) {
// ...........
'Flutter' 카테고리의 다른 글
[Flutter] 플러터 - 네트워크 상태 감지 connectivity_plus (2) | 2024.07.08 |
---|---|
[Flutter] 플러터 - IOS - Parse Issue (Xcode): Module 'app_links' not found (0) | 2024.06.29 |
[Flutter] 플러터 - 바텀시트 텍스트 필드 깨짐 (0) | 2024.06.04 |
[Flutter] 플러터 - PopScope 뒤로가기 막기, 제어 WillPopScope Deprecated (0) | 2024.05.19 |
[Flutter] 플러터 - ios 권한 isDenied (0) | 2024.05.10 |