Tổng quan về khung viễn thông

Khung Android Telecom (còn gọi là "Telecom") quản lý các cuộc gọi âm thanh và video trên thiết bị chạy Android. Số liệu này bao gồm cả các cuộc gọi dựa trên SIM, chẳng hạn như các cuộc gọi sử dụng khung điện thoại và các cuộc gọi VoIP triển khai thư viện Jetpack Core-Telecom.

Các thành phần chính mà Telecom quản lý là ConnectionServiceInCallService.

Việc triển khai ConnectionService dựa trên các công nghệ như PSTN để kết nối các cuộc gọi với các bên khác. Cách triển khai ConnectionService phổ biến nhất trên điện thoại là ConnectionService điện thoại. Lớp này kết nối các cuộc gọi của nhà mạng.

Việc triển khai InCallService cung cấp giao diện người dùng cho các cuộc gọi do Telecom quản lý và cho phép người dùng kiểm soát cũng như tương tác với các cuộc gọi này. Cách triển khai InCallService phổ biến nhất là ứng dụng điện thoại đi kèm với thiết bị.

Viễn thông đóng vai trò là bảng điều khiển. Lớp này định tuyến các lệnh gọi mà hoạt động triển khai ConnectionService cung cấp đến các giao diện người dùng gọi mà hoạt động triển khai InCallService cung cấp.

Bạn nên triển khai API viễn thông vì những lý do sau:

Tạo ứng dụng điện thoại thay thế

Để tạo một ứng dụng thay thế cho ứng dụng điện thoại mặc định trên thiết bị Android, hãy triển khai API InCallService. Cách triển khai của bạn phải đáp ứng các yêu cầu sau:

  • Ứng dụng này không được có bất kỳ chức năng gọi nào và chỉ được bao gồm giao diện người dùng để gọi.
  • Lớp này phải xử lý tất cả các lệnh gọi mà khung Điện thoại biết và không được giả định về bản chất của các lệnh gọi. Ví dụ: ứng dụng không được giả định rằng các cuộc gọi là cuộc gọi điện thoại dựa trên SIM, cũng như không được triển khai các quy định hạn chế về cuộc gọi dựa trên một ConnectionService bất kỳ, chẳng hạn như thực thi các quy định hạn chế về cuộc gọi điện thoại đối với cuộc gọi video.

Để biết thêm thông tin, hãy xem InCallService.

Tích hợp giải pháp gọi điện

Để tích hợp giải pháp gọi vào Android, bạn có các lựa chọn sau:

  • Triển khai thư viện Core-Telecom Jetpack tự quản lý: Tuỳ chọn này phù hợp với các nhà phát triển ứng dụng gọi độc lập không muốn hiển thị cuộc gọi trong ứng dụng điện thoại mặc định cũng như không muốn hiển thị các cuộc gọi khác trong giao diện người dùng.

    Khi tích hợp với thư viện Jetpack Core-Telecom, bạn giúp ứng dụng của mình không chỉ tương tác với tính năng gọi điện qua hệ thống trên thiết bị mà còn với các ứng dụng gọi điện độc lập khác tích hợp với Telecom. Thư viện Core-Telecom cũng quản lý việc định tuyến và tiêu điểm âm thanh. Để biết thông tin chi tiết, hãy xem phần Tạo ứng dụng gọi điện.

  • Triển khai API ConnectionService được quản lý: Tuỳ chọn này hỗ trợ việc phát triển giải pháp gọi điện dựa trên ứng dụng điện thoại hiện có của thiết bị để cung cấp giao diện người dùng cho các cuộc gọi. Ví dụ: bên thứ ba triển khai dịch vụ gọi qua SIP và VoIP. Để biết thêm thông tin, hãy xem getDefaultDialerPackage().

    ConnectionService chỉ cung cấp phương thức kết nối các lệnh gọi. Lớp này không có giao diện người dùng liên kết.

  • Triển khai cả API InCallService và ConnectionService: Lựa chọn này là lý tưởng nếu bạn muốn tạo giải pháp gọi dựa trên ConnectionService của riêng mình, hoàn chỉnh với giao diện người dùng riêng và cũng hiển thị tất cả các lệnh gọi Android khác trong cùng một giao diện người dùng. Khi bạn sử dụng phương pháp này, việc triển khai InCallService không được đưa ra bất kỳ giả định nào về nguồn của các lệnh gọi mà phương thức này hiển thị. Ngoài ra, việc triển khai ConnectionService phải tiếp tục hoạt động mà không cần đặt ứng dụng điện thoại mặc định thành InCallService tuỳ chỉnh.

Hiện màn hình cuộc gọi

Các thiết bị chạy Android 10 (API cấp 29) trở lên cho phép ứng dụng của bạn xác định các cuộc gọi từ những số không có trong sổ địa chỉ của người dùng là các cuộc gọi rác tiềm ẩn. Người dùng có thể chọn từ chối thầm những cuộc gọi rác. Để cung cấp thêm tính minh bạch cho người dùng khi họ bỏ lỡ cuộc gọi, thông tin về các cuộc gọi bị chặn này sẽ được ghi lại trong nhật ký cuộc gọi. Việc sử dụng API Android 10 sẽ loại bỏ yêu cầu người dùng cấp quyền READ_CALL_LOG để cung cấp chức năng sàng lọc cuộc gọi và nhận dạng người gọi.

Bạn sử dụng phương thức triển khai CallScreeningService để sàng lọc cuộc gọi. Gọi hàm onScreenCall() cho mọi cuộc gọi đến hoặc đi mới khi số điện thoại không có trong danh bạ của người dùng. Bạn có thể kiểm tra đối tượng Call.Details để biết thông tin về lệnh gọi. Cụ thể, hàm getCallerNumberVerificationStatus() bao gồm thông tin của nhà cung cấp mạng về số điện thoại kia. Nếu trạng thái xác minh không thành công, thì đây là dấu hiệu cho thấy cuộc gọi đến từ một số không hợp lệ hoặc có thể là cuộc gọi làm phiền.

Kotlin

class ScreeningService : CallScreeningService() {
    // This function is called when an ingoing or outgoing call
    // is from a number not in the user's contacts list
    override fun onScreenCall(callDetails: Call.Details) {
        // Can check the direction of the call
        val isIncoming = callDetails.callDirection == Call.Details.DIRECTION_INCOMING

        if (isIncoming) {
            // the handle (e.g. phone number) that the Call is currently connected to
            val handle: Uri = callDetails.handle

            // determine if you want to allow or reject the call
            when (callDetails.callerNumberVerificationStatus) {
                Connection.VERIFICATION_STATUS_FAILED -> {
                    // Network verification failed, likely an invalid/spam call.
                }
                Connection.VERIFICATION_STATUS_PASSED -> {
                    // Network verification passed, likely a valid call.
                }
                else -> {
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED.
                }
            }
        }
    }
}

Java

class ScreeningService extends CallScreeningService {
    @Override
    public void onScreenCall(@NonNull Call.Details callDetails) {
        boolean isIncoming = callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING;

        if (isIncoming) {
            Uri handle = callDetails.getHandle();

            switch (callDetails.getCallerNumberVerificationStatus()) {
                case Connection.VERIFICATION_STATUS_FAILED:
                    // Network verification failed, likely an invalid/spam call.
                    break;
                case Connection.VERIFICATION_STATUS_PASSED:
                    // Network verification passed, likely a valid call.
                    break;
                default:
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED
            }
        }
    }
}

Đặt hàm onScreenCall() để gọi respondToCall() cho hệ thống biết cách phản hồi lệnh gọi mới. Hàm này nhận một tham số CallResponse mà bạn có thể dùng để yêu cầu hệ thống chặn cuộc gọi, từ chối cuộc gọi như thể người dùng đã từ chối hoặc tắt tiếng cuộc gọi. Bạn cũng có thể yêu cầu hệ thống bỏ qua việc thêm cuộc gọi này vào nhật ký cuộc gọi của thiết bị.

Kotlin

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
val response = CallResponse.Builder()
    // Sets whether the incoming call should be blocked.
    .setDisallowCall(false)
    // Sets whether the incoming call should be rejected as if the user did so manually.
    .setRejectCall(false)
    // Sets whether ringing should be silenced for the incoming call.
    .setSilenceCall(false)
    // Sets whether the incoming call should not be displayed in the call log.
    .setSkipCallLog(false)
    // Sets whether a missed call notification should not be shown for the incoming call.
    .setSkipNotification(false)
    .build()

// Call this function to provide your screening response.
respondToCall(callDetails, response)

Java

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
CallResponse.Builder response = new CallResponse.Builder();
// Sets whether the incoming call should be blocked.
response.setDisallowCall(false);
// Sets whether the incoming call should be rejected as if the user did so manually.
response.setRejectCall(false);
// Sets whether ringing should be silenced for the incoming call.
response.setSilenceCall(false);
// Sets whether the incoming call should not be displayed in the call log.
response.setSkipCallLog(false);
// Sets whether a missed call notification should not be shown for the incoming call.
response.setSkipNotification(false);

// Call this function to provide your screening response.
respondToCall(callDetails, response.build());

Bạn phải đăng ký phương thức triển khai CallScreeningService trong tệp kê khai bằng bộ lọc ý định và quyền thích hợp để hệ thống có thể kích hoạt phương thức này một cách chính xác.

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

Chuyển hướng cuộc gọi

Các thiết bị chạy Android 10 trở lên quản lý ý định gọi theo cách khác với các thiết bị chạy Android 9 trở xuống. Trên Android 10 trở lên, thông báo truyền tin ACTION_NEW_OUTGOING_CALL không còn được dùng nữa và được thay thế bằng API CallRedirectionService. CallRedirectionService cung cấp các giao diện để bạn sử dụng nhằm chỉnh sửa các cuộc gọi đi do nền tảng Android thực hiện. Ví dụ: ứng dụng của bên thứ ba có thể huỷ cuộc gọi và định tuyến lại cuộc gọi qua VoIP.

Kotlin

class RedirectionService : CallRedirectionService() {
    override fun onPlaceCall(
        handle: Uri,
        initialPhoneAccount: PhoneAccountHandle,
        allowInteractiveResponse: Boolean
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        val callShouldProceed = true
        val callShouldRedirect = false
        when {
            callShouldProceed -> {
                placeCallUnmodified()
            }
            callShouldRedirect -> {
                // Update the URI to point to a different phone number or modify the
                // PhoneAccountHandle and redirect.
                redirectCall(handle, initialPhoneAccount, true)
            }
            else -> {
                cancelCall()
            }
        }
    }
}

Java

class RedirectionService extends CallRedirectionService {
    @Override
    public void onPlaceCall(
            @NonNull Uri handle,
            @NonNull PhoneAccountHandle initialPhoneAccount,
            boolean allowInteractiveResponse
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        // Your app should implement this logic to determine the redirection.
        boolean callShouldProceed = true;
        boolean callShouldRedirect = false;
        if (callShouldProceed) {
            placeCallUnmodified();
        } else if (callShouldRedirect) {
            // Update the URI to point to a different phone number or modify the
            // PhoneAccountHandle and redirect.
            redirectCall(handle, initialPhoneAccount, true);
        } else {
            cancelCall();
        }
    }
}

Bạn phải đăng ký dịch vụ này trong tệp kê khai để hệ thống có thể khởi động dịch vụ một cách chính xác.

<service
    android:name=".RedirectionService"
    android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallRedirectionService"/>
    </intent-filter>
</service>

Để sử dụng dịch vụ chuyển hướng, ứng dụng của bạn phải yêu cầu vai trò chuyển hướng cuộc gọi từ RoleManager. Thao tác này sẽ hỏi người dùng xem họ có muốn cho phép ứng dụng của bạn xử lý lệnh chuyển hướng cuộc gọi hay không. Nếu ứng dụng của bạn không được cấp vai trò này, thì dịch vụ chuyển hướng sẽ không được sử dụng.

Bạn nên kiểm tra xem ứng dụng của mình có vai trò này khi người dùng chạy ứng dụng hay không để có thể yêu cầu vai trò đó nếu cần. Bạn chạy một ý định do RoleManager tạo, vì vậy, hãy đảm bảo bạn ghi đè hàm onActivityResult() để xử lý lựa chọn của người dùng.

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
            // Check if the app needs to register call redirection role.
            val shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION)
            if (shouldRequestRole) {
                val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION)
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE)
            }
        }
    }

    companion object {
        private const val REDIRECT_ROLE_REQUEST_CODE = 1
    }
}

Java

class MainActivity extends AppCompatActivity {
    private static final int REDIRECT_ROLE_REQUEST_CODE = 0;

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

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);
            // Check if the app needs to register call redirection role.
            boolean shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION);
            if (shouldRequestRole) {
                Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION);
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE);
            }
        }
    }
}