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

Cách phát hiện cuộc gọi đến trên thiết bị Android

Giới thiệu

Bài viết này hướng dẫn phát hiện trạng thái cuộc gọi (đang reo, nghe máy, kết thúc) bằng BroadcastReceiver lắng nghe action android.intent.action.PHONE_STATE. Yêu cầu quyền READ_PHONE_STATE (dangerous permission – cần xin runtime).

Các bước thực hiện

Bước 1: Tạo dự án mới

Mở Android Studio, chọn File → New Project và tạo dự án.

Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="App chạy ngầm phát hiện cuộc gọi"
        android:textStyle="bold"
        android:textColor="@color/colorPrimary" />

</RelativeLayout>

Bước 3: Code MainActivity – xin quyền (src/MainActivity.java)

import android.Manifest;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private static final int PERMISSION_REQUEST_READ_PHONE_STATE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        requestPhoneStatePermission();
    }

    private void requestPhoneStatePermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.READ_PHONE_STATE},
                PERMISSION_REQUEST_READ_PHONE_STATE);
        } else {
            Toast.makeText(this, "Đã có quyền READ_PHONE_STATE", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_READ_PHONE_STATE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "Quyền đã được cấp", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "Quyền bị từ chối - không thể phát hiện cuộc gọi", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Bước 4: Tạo BroadcastReceiver (src/CallDetector.java)

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.widget.Toast;

public class CallDetector extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
            String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);

            if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
                // Đang reo
                String msg = "Cuộc gọi đến từ: " + (incomingNumber != null ? incomingNumber : "Số ẩn");
                Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
                // TODO: Xử lý logic tại đây (lưu log, hiển thị UI, chặn cuộc gọi...)
            } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {
                // Đang nghe máy
                Toast.makeText(context, "Đang 통 hóa", Toast.LENGTH_SHORT).show();
            } else if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {
                // Kết thúc/treo máy
                Toast.makeText(context, "Cuộc gọi kết thúc", Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Bước 5: Đăng ký Receiver trong AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <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">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Đăng ký BroadcastReceiver cho PHONE_STATE -->
        <receiver android:name=".CallDetector" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE" />
            </intent-filter>
        </receiver>

    </application>
</manifest>

Chạy ứng dụng

Cài đặt app, cấp quyền READ_PHONE_STATE. Gọi điện thoại đến thiết bị -> Toast hiển thị số điện thoại đang gọi đến. Receiver hoạt động ngay cả khi app đóng (do khai báo trong Manifest).

⚠️ Thay đổi quan trọng từ Android 10 (API 29)

  • READ_PHONE_STATE không còn cho phép đọc số điện thoại gọi đến (EXTRA_INCOMING_NUMBER trả về null) trừ khi app là **Default Dialer** hoặc có quyền đặc biệt READ_CALL_LOG
  • Android 11+ hạn chế BroadcastReceiver trong Manifest cho PHONE_STATE – nên đăng ký động (registerReceiver) khi app chạy foreground
  • Để chặn cuộc gọi: dùng CallScreeningService (API 26+) hoặc TelecomManager

Cách hiện đại (API 26+): CallScreeningService

// Manifest
<service android:name=".CallScreeningService"
    android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallScreeningService" />
    </intent-filter>
</service>

// Service
public class CallScreeningService extends CallScreeningService {
    @Override
    public void onScreenCall(Call.Details details) {
        // Xử lý: chặn, cho phép, chuyển voicemail...
        CallResponse.Builder response = new CallResponse.Builder();
        if (shouldBlock(details.getHandle())) {
            response.setRejectCall(true).setSilenceRinger(true);
        }
        respondToCall(details.getCallId(), response.build());
    }
}

Lưu ý

  • Toast trong BroadcastReceiver có thể không hiển thị nếu app bị kill – nên dùng Notification hoặc log
  • Luôn kiểm tra intent.getAction() trước khi xử lý
  • Test trên thiết bị thật – emulator không mô phỏng cuộc gọi thật tốt