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

Khung Android Telecom (còn gọi là "Telecom") quản lý cuộc gọi thoại và video trên thiết bị chạy Android. Trong đó bao gồm các lệnh gọi dựa trên SIM, chẳng hạn như các lệnh gọi sử dụng khung điện thoại và các lệnh gọi VoIP triển khai API ConnectionService.

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

Quá trình triển khai ConnectionService sử dụng các công nghệ như VoIP để kết nối các lệnh gọi đến 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 qua điện thoại. Ứng dụng 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 lệnh gọi do Viễn thông quản lý, đồng thời cho phép người dùng kiểm soát cũng như tương tác với các lệnh gọi này. Cách triển khai phổ biến nhất của InCallService là ứng dụng điện thoại đi kèm với thiết bị.

Viễn thông hoạt động như một tổng đài. Lớp này định tuyến các lệnh gọi mà phương thức triển khai ConnectionService cung cấp tới giao diện người dùng gọi mà phương thức triển khai InCallService cung cấp.

Bạn có thể muốn triển khai Telecom API 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. Phương thức triển khai của bạn phải đáp ứng các yêu cầu sau:

  • Lớp này không được có khả năng gọi và chỉ được bao gồm giao diện người dùng để gọi.
  • Phải xử lý tất cả cuộc gọi mà khung Viễn thông biết được và không được đưa ra giả định về bản chất của các cuộc gọi. Ví dụ: ứng dụng không được giả định các cuộc gọi là cuộc gọi qua điện thoại dựa trên SIM, cũng như không được triển khai các hạn chế về cuộc gọi dựa trên bất kỳ ConnectionService nào, chẳng hạn như việc thực thi các hạn chế về đ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 một 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 phương án sau:

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

    Khi sử dụng ConnectionService tự quản lý, bạn không chỉ giúp ứng dụng của mình tương tác với tính năng gọi điện thoại gốc trên thiết bị, mà còn với các ứng dụng gọi độc lập khác triển khai API này. API ConnectionService tự quản lý cũng quản lý việc định tuyến và tập trung âm thanh. Để biết thông tin chi tiết, hãy xem bài viết Tạo ứng dụng gọi.

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

    Riêng ConnectionService chỉ cung cấp phương tiện để kết nối cuộc gọi. Tính năng này không có giao diện người dùng được liên kết.

  • Triển khai cả InCallService và API ConnectionService: Đây là phương án 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 sử dụng phương pháp này, bạn 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ị trong quá trình triển khai InCallService. Ngoài ra, việc triển khai ConnectionService của bạn 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 lệnh gọi từ các số không có trong sổ địa chỉ của người dùng là các lệnh gọi có khả năng là cuộc gọi làm phiền. Người dùng có thể chọn tự động từ chối cuộc gọi làm phiền. Để mang lại thông tin rõ ràng hơn cho người dùng khi họ bỏ lỡ lệnh gọi, thông tin về các lệnh gọi bị chặn này sẽ được ghi lại trong nhật ký lệnh gọi. Việc sử dụng API Android 10 sẽ loại bỏ yêu cầu phải có được quyền READ_CALL_LOG của người dùng để cung cấp chức năng sàng lọc cuộc gọi và mã nhận dạng người gọi.

Bạn sẽ sử dụng phương thức triển khai CallScreeningService để sàng lọc lệnh gọi. Gọi hàm onScreenCall() cho mọi cuộc gọi mới hoặc cuộc gọi đi mới khi số đó 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 từ nhà cung cấp mạng về số khác. Nếu trạng thái xác minh không thành công, thì đây là một dấu hiệu tốt cho thấy cuộc gọi đến từ một số không hợp lệ hoặc cuộc gọi có khả năng 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() nhằm cho hệ thống biết cách phản hồi lệnh gọi mới. Hàm này lấy một tham số CallResponse mà bạn có thể dùng để yêu cầu hệ thống chặn, từ chối lệnh gọi như thể người dùng đã làm hoặc tắt tiếng. Bạn cũng có thể yêu cầu hệ thống bỏ qua việc thêm lệnh 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 chính xác phương thứ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 cuộc gọi 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 cho bạn sử dụng để sửa đổi các lệnh gọi đi do nền tảng Android thực hiện. Ví dụ: các ứng dụng bên thứ ba có thể huỷ các cuộc gọi và định tuyến lại các 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ụ đúng cách.

<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 của bạn 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 hay không khi người dùng chạy ứng dụng để có thể yêu cầu nếu cần. Bạn khởi 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);
            }
        }
    }
}