Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách lấy số điện thoại thiết bị Android (Hạn chế từ Android 10)

Giới thiệu

Lấy số điện thoại (MSISDN) của thiết bị qua TelephonyManager.getLine1Number() **không còn hoạt động** cho app thông thường từ **Android 10 (API 29)** do chính sách quyền riêng tư. Bài viết giải thích hạn chế và các giải pháp thay thế.

Hạn chế theo phiên bản Android

Android VersionAPI LevelgetLine1Number()Quyền cần
< 6.0 (Marshmallow)< 23Thường hoạt độngREAD_PHONE_STATE
6.0 - 9.023 - 28Có thể hoạt độngREAD_PHONE_STATE (runtime permission)
10 (Q) - 1129 - 30Trả về null/emptyREAD_PRIVILEGED_PHONE_STATE (system only)
12+31+Trả về null/emptyREAD_PRIVILEGED_PHONE_STATE (system only)

Kết luận: App thông thường (non-system) **không thể lấy số điện thoại** trên Android 10+.

Code tham khảo (Chỉ hoạt động Android < 10 hoặc System App)

AndroidManifest.xml

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- Chỉ system app được cấp -->
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />

MainActivity.kt

package com.example.getphonenumber

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.telephony.TelephonyManager
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.getphonenumber.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val REQUEST_CODE = 101

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btnGetPhoneNumber.setOnClickListener {
            requestPhoneNumber()
        }
    }

    private fun requestPhoneNumber() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), REQUEST_CODE)
        } else {
            fetchPhoneNumber()
        }
    }

    private fun fetchPhoneNumber() {
        val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        
        try {
            val phoneNumber: String?
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // API 29+
                // getLine1Number() deprecated, dùng getLine1Number() vẫn compile nhưng trả về null
                phoneNumber = telephonyManager.line1Number
            } else {
                @Suppress("DEPRECATION")
                phoneNumber = telephonyManager.line1Number
            }

            if (phoneNumber.isNullOrBlank()) {
                binding.tvPhoneNumber.text = "Số điện thoại: Không thể truy cập (Android 10+ hạn chế)"
                showLimitationDialog()
            } else {
                binding.tvPhoneNumber.text = "Số điện thoại: $phoneNumber"
            }
        } catch (e: SecurityException) {
            binding.tvPhoneNumber.text = "Lỗi bảo mật: ${e.message}"
        }
    }

    private fun showLimitationDialog() {
        androidx.appcompat.app.AlertDialog.Builder(this)
            .setTitle("Hạn chế Android 10+")
            .setMessage("""
                Google đã hạn chế truy cập số điện thoại (IMEI, IMSI, MSISDN) cho app thông thường từ Android 10.
                
                Giải pháp thay thế:
                1. Nhập tay bởi người dùng (OTP verification)
                2. Firebase Phone Auth (gửi OTP SMS)
                3. Sử dụng AccountManager (Google account)
                4. Custom UUID lưu trong SharedPreferences
            """.trimIndent())
            .setPositiveButton("OK", null)
            .show()
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CODE && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            fetchPhoneNumber()
        } else {
            Toast.makeText(this, "Cần quyền READ_PHONE_STATE", Toast.LENGTH_SHORT).show()
        }
    }
}

Giải pháp thay thế cho App hiện đại

1. Firebase Phone Authentication (Khuyến nghị nhất)

  • Gửi OTP SMS đến số điện thoại user nhập.
  • Xác thực OTP → có số điện thoại verified.
  • Hỗ trợ mọi Android version, không cần permission đặc biệt.

// build.gradle.kts
implementation("com.google.firebase:firebase-auth:22.3.0")
implementation("com.google.firebase:firebase-auth-ktx:22.3.0")

// Gửi OTP
FirebaseAuth.getInstance().verifyPhoneNumber(
    phoneNumber,        // User nhập: "+84901234567"
    60, TimeUnit.SECONDS,
    this,
    object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
        override fun onCodeSent(verificationId: String, token: PhoneAuthProvider.ForceResendingToken) {
            // Lưu verificationId, chuyển màn hình nhập OTP
        }
        override fun onVerificationCompleted(credential: PhoneAuthCredential) {
            // Auto-verify (một số thiết bị)
            signInWithCredential(credential)
        }
        override fun onVerificationFailed(e: FirebaseException) {
            // Xử lý lỗi
        }
    }
)

2. Hint Request (Google Play Services) - Deprecated

Credentials API và HintRequest đã deprecated. Thay bằng Credential Manager API (Android 14+) hoặc Firebase Phone Auth.

3. Tự nhập tay + OTP verification (Backend tự build)

  • UI nhập số điện thoại → gửi OTP qua SMS gateway (Twilio, Vonage, local provider).
  • Verify OTP → lưu số điện thoại vào server/user profile.

4. AccountManager (Google Account trên thiết bị)


// Lấy email Google account chính (không phải số điện thoại)
val accountManager = AccountManager.get(this)
val accounts = accountManager.getAccountsByType("com.google")
accounts.firstOrNull()?.name?.let { email ->
    // email: user@gmail.com
}

5. Custom UUID (Identifier app install)

private fun getOrCreateDeviceId(): String {
    val prefs = getSharedPreferences("device_id", MODE_PRIVATE)
    return prefs.getString("device_id", "").let {
        if (it.isNullOrBlank()) {
            val newId = java.util.UUID.randomUUID().toString()
            prefs.edit().putString("device_id", newId).apply()
            newId
        } else it
    }
}

Best Practices

  • Không假设 (assume) có thể lấy được số điện thoại - luôn có fallback.
  • UI: Cho user **nhập số điện thoại** + **verify OTP** - cách chắc chắn nhất.
  • Đừng dùng IMEI/Android ID/Phone number để track user cross-app (vi phạm Play Policy).
  • Dùng **Firebase Installation ID** hoặc **Custom UUID** để identify app install.
  • Nếu cần số điện thoại cho 2FA: dùng Firebase Phone Auth hoặc tự build OTP flow.