Tạo ứng dụng điện thoại mặc định

Ứng dụng điện thoại mặc định cho phép khung Android Telecom thông báo cho ứng dụng của bạn về trạng thái cuộc gọi bằng cách sử dụng trình quản lý vai trò và dịch vụ trong cuộc gọi để tạo ứng dụng thay thế cho ứng dụng điện thoại mặc định trên thiết bị Android, triển khai InCallService API. Việ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. Dịch vụ này 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 cuộc gọi. Ví dụ: không được giả định rằng 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ế gọi điện dựa trên bất kỳ một 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.

Ứng dụng gọi điện cho phép người dùng nhận hoặc thực hiện cuộc gọi thoại/video trên thiết bị của họ. Ứng dụng gọi điện dùng giao diện người dùng riêng cho cuộc gọi thay vì dùng giao diện mặc định của ứng dụng Điện thoại, như minh hoạ trong ảnh chụp màn hình sau đây.

Ví dụ về ứng dụng gọi điện
Ví dụ về một ứng dụng gọi điện sử dụng giao diện người dùng riêng

Khung Android bao gồm gói android.telecom, chứa các lớp giúp bạn tạo ứng dụng gọi theo khung viễn thông. Việc xây dựng ứng dụng theo khung viễn thông sẽ mang lại các lợi ích sau:

  • Ứng dụng của bạn tương tác chính xác với hệ thống con viễn thông gốc trong thiết bị.
  • Ứng dụng của bạn tương tác chính xác với các ứng dụng gọi khác cũng tuân thủ khung này.
  • Khung này giúp ứng dụng của bạn quản lý việc định tuyến âm thanh và video.
  • Khung này giúp ứng dụng của bạn xác định xem các lệnh gọi của ứng dụng có tâm điểm hay không.

Quyền và nội dung khai báo trong tệp kê khai

Trong tệp kê khai ứng dụng, hãy khai báo rằng ứng dụng của bạn sử dụng quyền MANAGE_OWN_CALLS, như trong ví dụ sau:

<manifest … >
    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
</manifest>

Để biết thêm thông tin về việc khai báo quyền cho ứng dụng, hãy xem bài viết Quyền.

Bạn phải khai báo một dịch vụ chỉ định lớp triển khai lớp ConnectionService trong ứng dụng của bạn. Hệ thống viễn thông phụ yêu cầu dịch vụ khai báo quyền BIND_TELECOM_CONNECTION_SERVICE để có thể liên kết với lớp đó. Ví dụ sau cho biết cách khai báo dịch vụ trong tệp kê khai ứng dụng:

<service android:name="com.example.MyConnectionService"
    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.ConnectionService" />
    </intent-filter>
</service>

Để biết thêm thông tin về cách khai báo các thành phần ứng dụng, bao gồm cả dịch vụ, hãy xem phần Thành phần ứng dụng.

Triển khai dịch vụ kết nối

Ứng dụng gọi của bạn phải cung cấp phương thức triển khai lớp ConnectionService mà hệ thống viễn thông phụ có thể liên kết. Quá trình triển khai ConnectionService của bạn nên ghi đè các phương thức sau:

onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)

Hệ thống viễn thông phụ gọi phương thức này để phản hồi việc ứng dụng của bạn gọi placeCall(Uri, Bundle) để tạo một cuộc gọi đi mới. Ứng dụng của bạn trả về một thực thể mới của quá trình triển khai lớp Connection (để biết thêm thông tin, hãy xem bài viết Triển khai kết nối) để biểu thị lệnh gọi đi mới. Bạn có thể tuỳ chỉnh thêm kết nối đi bằng cách thực hiện các thao tác sau:

onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

Hệ thống viễn thông phụ gọi phương thức này khi ứng dụng của bạn gọi phương thức placeCall(Uri, Bundle) và không thể thực hiện cuộc gọi đi. Để đối phó với tình huống này, ứng dụng của bạn nên thông báo cho người dùng (ví dụ: sử dụng hộp cảnh báo hoặc thông báo ngắn) rằng không thể thực hiện cuộc gọi đi. Ứng dụng của bạn có thể không thực hiện được cuộc gọi nếu đang diễn ra một cuộc gọi khẩn cấp, hoặc nếu có một cuộc gọi đang diễn ra trong một ứng dụng khác nên không thể giữ máy trước khi thực hiện cuộc gọi.

onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)

Hệ thống con của ngành viễn thông sẽ gọi phương thức này khi ứng dụng của bạn gọi phương thức addNewIncomingCall(PhoneAccountHandle, Bundle) để thông báo cho hệ thống về cuộc gọi đến mới trong ứng dụng. Ứng dụng sẽ trả về một phiên bản mới của quá trình triển khai Connection (để biết thêm thông tin, hãy xem phần Triển khai kết nối) để biểu thị cuộc gọi đến mới. Bạn có thể tuỳ chỉnh thêm kết nối đến bằng cách thực hiện các thao tác sau:

onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

Hệ thống con của viễn thông gọi phương thức này khi ứng dụng của bạn gọi phương thức addNewIncomingCall(PhoneAccountHandle, Bundle) để thông báo cho Telecom về cuộc gọi đến mới, nhưng cuộc gọi đến không được cho phép (để biết thêm thông tin, hãy xem phần các giới hạn đối với việc gọi). Ứng dụng của bạn sẽ tự động từ chối cuộc gọi đến, tuỳ ý đăng thông báo để thông báo cho người dùng về cuộc gọi nhỡ.

Triển khai kết nối

Ứng dụng nên tạo một lớp con của Connection để đại diện cho các lệnh gọi trong ứng dụng. Bạn nên ghi đè các phương thức sau trong quá trình triển khai:

onShowIncomingCallUi()

Hệ thống viễn thông phụ gọi phương thức này khi bạn thêm cuộc gọi đến mới và ứng dụng sẽ hiển thị giao diện người dùng cho cuộc gọi đến.

onCallAudioStateChanged(CallAudioState)

Hệ thống viễn thông phụ gọi phương thức này để thông báo cho ứng dụng của bạn rằng tuyến hoặc chế độ âm thanh hiện tại đã thay đổi. Lệnh này được gọi để phản hồi khi ứng dụng của bạn thay đổi chế độ âm thanh bằng phương thức setAudioRoute(int). Phương thức này cũng có thể được gọi nếu hệ thống thay đổi tuyến âm thanh (ví dụ: khi tai nghe Bluetooth ngắt kết nối).

onHold()

Hệ thống viễn thông phụ gọi phương thức này khi muốn giữ cuộc gọi. Để phản hồi yêu cầu này, ứng dụng của bạn cần giữ lệnh gọi và sau đó gọi phương thức setOnHold() để thông báo cho hệ thống rằng lệnh gọi đang được giữ. Hệ thống viễn thông con có thể gọi phương thức này khi một dịch vụ trong cuộc gọi (chẳng hạn như Android Auto) cho biết cuộc gọi của bạn muốn chuyển tiếp yêu cầu của người dùng để chuyển cuộc gọi sang trạng thái chờ. Hệ thống viễn thông phụ cũng gọi phương thức này nếu người dùng thực hiện cuộc gọi đang hoạt động trong một ứng dụng khác. Để biết thêm thông tin về dịch vụ trong cuộc gọi, hãy xem InCallService.

onUnhold()

Hệ thống viễn thông phụ sẽ gọi phương thức này khi muốn tiếp tục một cuộc gọi đã bị tạm ngưng. Sau khi tiếp tục cuộc gọi, ứng dụng của bạn sẽ gọi phương thức setActive() để thông báo cho hệ thống rằng cuộc gọi không còn ở trạng thái chờ nữa. Hệ thống viễn thông phụ có thể gọi phương thức này khi một dịch vụ trong cuộc gọi (chẳng hạn như Android Auto) đang cho thấy cuộc gọi của bạn muốn chuyển tiếp yêu cầu tiếp tục cuộc gọi. Để biết thêm thông tin về dịch vụ trong cuộc gọi, hãy xem InCallService.

onAnswer()

Hệ thống viễn thông phụ gọi phương thức này để thông báo cho ứng dụng rằng cần trả lời cuộc gọi đến. Sau khi trả lời lệnh gọi, ứng dụng của bạn sẽ gọi phương thức setActive() để thông báo cho hệ thống rằng lệnh gọi đã được trả lời. Hệ thống viễn thông phụ có thể gọi phương thức này khi ứng dụng của bạn thêm cuộc gọi đến mới và có một cuộc gọi đang diễn ra trong một ứng dụng khác mà không thể đặt ở chế độ chờ. Hệ thống con của ngành viễn thông sẽ thay mặt ứng dụng hiển thị giao diện người dùng của cuộc gọi đến trong những trường hợp này. Khung này cung cấp một phương thức nạp chồng hỗ trợ chỉ định trạng thái video để trả lời lệnh gọi. Để biết thêm thông tin, hãy xem onAnswer(int).

onReject()

Hệ thống con của viễn thông sẽ gọi phương thức này khi muốn từ chối một cuộc gọi đến. Sau khi từ chối lệnh gọi, ứng dụng của bạn sẽ gọi setDisconnected(DisconnectCause) và chỉ định REJECTED làm tham số. Sau đó, ứng dụng nên gọi phương thức destroy() để thông báo cho hệ thống biết rằng ứng dụng đã xử lý lệnh gọi. Hệ thống viễn thông phụ gọi phương thức này khi người dùng từ chối cuộc gọi đến từ ứng dụng của bạn.

onDisconnect()

Hệ thống viễn thông phụ gọi phương thức này khi muốn ngắt kết nối một cuộc gọi. Sau khi lệnh gọi kết thúc, ứng dụng phải gọi phương thức setDisconnected(DisconnectCause) và chỉ định LOCAL làm tham số để cho biết rằng yêu cầu của người dùng khiến cuộc gọi bị ngắt kết nối. Sau đó, ứng dụng nên gọi phương thức destroy() để thông báo cho hệ thống viễn thông phụ rằng ứng dụng đã xử lý cuộc gọi. Hệ thống có thể gọi phương thức này khi người dùng ngắt kết nối một cuộc gọi thông qua một dịch vụ trong lệnh gọi khác, chẳng hạn như Android Auto. Hệ thống cũng gọi phương thức này khi bạn phải ngắt kết nối cuộc gọi để cho phép thực hiện cuộc gọi khác, chẳng hạn như nếu người dùng muốn thực hiện cuộc gọi khẩn cấp. Để biết thêm thông tin về dịch vụ trong cuộc gọi, hãy xem InCallService.

Xử lý các tình huống gọi phổ biến

Để sử dụng API ConnectionService trong quy trình gọi, bạn cần tương tác với các lớp khác trong gói android.telecom. Các phần sau đây mô tả các trường hợp gọi phổ biến và cách ứng dụng của bạn nên dùng API để xử lý các trường hợp đó.

Trả lời cuộc gọi đến

Luồng xử lý các cuộc gọi đến sẽ thay đổi dù có lệnh gọi trong ứng dụng khác hay không. Lý do cho sự khác biệt về các luồng là khung viễn thông phải thiết lập một số quy tắc ràng buộc khi có các lệnh gọi đang hoạt động trong các ứng dụng khác để đảm bảo môi trường ổn định cho tất cả ứng dụng gọi trên thiết bị. Để biết thêm thông tin, hãy xem bài viết Các điều kiện ràng buộc đối với lệnh gọi.

Không có cuộc gọi nào đang diễn ra trong các ứng dụng khác

Để trả lời cuộc gọi đến khi không có cuộc gọi nào đang diễn ra trong các ứng dụng khác, hãy làm theo các bước sau:

  1. Ứng dụng của bạn nhận cuộc gọi đến mới bằng các cơ chế thông thường của ứng dụng.
  2. Sử dụng phương thức addNewIncomingCall(PhoneAccountHandle, Bundle) để thông báo cho hệ thống viễn thông phụ về cuộc gọi đến mới.
  3. Hệ thống viễn thông phụ liên kết với phương thức triển khai ConnectionService của ứng dụng và yêu cầu một thực thể mới của lớp Connection đại diện cho lệnh gọi đến mới bằng phương thức onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest).
  4. Hệ thống viễn thông phụ thông báo cho ứng dụng của bạn rằng ứng dụng cần hiển thị giao diện người dùng có cuộc gọi đến bằng phương thức onShowIncomingCallUi().
  5. Ứng dụng của bạn hiển thị giao diện người dùng đến bằng cách sử dụng một thông báo có ý định toàn màn hình được liên kết. Để biết thêm thông tin, hãy xem onShowIncomingCallUi().
  6. Gọi phương thức setActive() nếu người dùng chấp nhận lệnh gọi đến hoặc setDisconnected(DisconnectCause) chỉ định REJECTED làm tham số, theo sau là lệnh gọi đến phương thức destroy() nếu người dùng từ chối lệnh gọi đến.

Các cuộc gọi đang diễn ra trong các ứng dụng khác mà không thể đặt ở chế độ chờ

Để trả lời cuộc gọi đến khi có cuộc gọi đang diễn ra trong các ứng dụng khác không thể tạm ngưng, hãy làm theo các bước sau:

  1. Ứng dụng của bạn nhận cuộc gọi đến mới bằng các cơ chế thông thường của ứng dụng.
  2. Sử dụng phương thức addNewIncomingCall(PhoneAccountHandle, Bundle) để thông báo cho hệ thống viễn thông phụ về cuộc gọi đến mới.
  3. Hệ thống viễn thông phụ liên kết với phương thức triển khai ConnectionService của ứng dụng và yêu cầu một thực thể mới của đối tượng Connection đại diện cho lệnh gọi đến mới bằng phương thức onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest).
  4. Hệ thống viễn thông phụ hiển thị giao diện người dùng của cuộc gọi đến cho cuộc gọi đến.
  5. Nếu người dùng chấp nhận cuộc gọi, hệ thống viễn thông phụ sẽ gọi phương thức onAnswer(). Bạn nên gọi phương thức setActive() để cho hệ thống viễn thông phụ biết rằng cuộc gọi hiện đã được kết nối.
  6. Nếu người dùng từ chối cuộc gọi, hệ thống viễn thông phụ sẽ gọi phương thức onReject(). Bạn nên gọi phương thức setDisconnected(DisconnectCause) chỉ định REJECTED làm tham số, theo sau là một lệnh gọi đến phương thức destroy().

Thực hiện cuộc gọi đi

Quy trình thực hiện cuộc gọi đi liên quan đến việc xử lý khả năng không thể thực hiện cuộc gọi do những hạn chế do khung viễn thông áp đặt. Để biết thêm thông tin, hãy xem bài viết Các điều kiện ràng buộc đối với lệnh gọi.

Để thực hiện cuộc gọi đi, hãy làm theo các bước sau:

  1. Người dùng bắt đầu một cuộc gọi đi trong ứng dụng của bạn.
  2. Sử dụng phương thức placeCall(Uri, Bundle) để thông báo cho hệ thống viễn thông phụ về cuộc gọi đi mới. Hãy xem xét những điểm sau đây đối với các tham số của phương thức:
    • Tham số Uri biểu thị địa chỉ nơi thực hiện lệnh gọi. Đối với các số điện thoại thông thường, hãy sử dụng lược đồ URI tel:.
    • Tham số Bundle cho phép bạn cung cấp thông tin về ứng dụng gọi bằng cách thêm đối tượng PhoneAccountHandle của ứng dụng vào phần bổ sung EXTRA_PHONE_ACCOUNT_HANDLE. Ứng dụng của bạn phải cung cấp đối tượng PhoneAccountHandle cho mọi lệnh gọi đi.
    • Tham số Bundle cũng cho phép bạn chỉ định xem cuộc gọi đi có chứa video hay không bằng cách chỉ định giá trị STATE_BIDIRECTIONAL trong phần bổ sung EXTRA_START_CALL_WITH_VIDEO_STATE. Hãy xem xét điều đó theo mặc định, hệ thống viễn thông phụ sẽ định tuyến cuộc gọi video đến loa ngoài.
  3. Hệ thống viễn thông phụ liên kết với cách triển khai ConnectionService của ứng dụng.
  4. Nếu ứng dụng của bạn không thể thực hiện cuộc gọi đi, thì hệ thống viễn thông phụ sẽ gọi phương thức onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) để thông báo cho ứng dụng rằng không thể thực hiện cuộc gọi tại thời điểm hiện tại. Ứng dụng của bạn phải thông báo cho người dùng rằng không thể thực hiện lệnh gọi.
  5. Nếu ứng dụng của bạn có thể thực hiện cuộc gọi đi, thì hệ thống viễn thông phụ sẽ gọi phương thức onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest). Ứng dụng của bạn sẽ trả về một thực thể của lớp Connection để đại diện cho cuộc gọi đi mới. Để biết thêm thông tin về các thuộc tính mà bạn nên thiết lập trong kết nối, hãy xem bài viết Triển khai dịch vụ kết nối.
  6. Khi cuộc gọi đi đã được kết nối, hãy gọi phương thức setActive() để thông báo cho hệ thống viễn thông phụ rằng cuộc gọi đang hoạt động.

Kết thúc cuộc gọi

Để kết thúc cuộc gọi, hãy làm theo các bước sau:

  1. Gọi setDisconnected(DisconnectCause) gửi LOCAL dưới dạng tham số nếu người dùng đã kết thúc lệnh gọi hoặc gửi REMOTE dưới dạng tham số nếu bên kia đã chấm dứt lệnh gọi.
  2. Gọi phương thức destroy().

Các quy tắc ràng buộc đối với lệnh gọi

Để đảm bảo người dùng có trải nghiệm gọi đơn giản và nhất quán, khung viễn thông sẽ thực thi một số quy tắc ràng buộc để quản lý các lệnh gọi trên thiết bị. Ví dụ: hãy xem xét việc người dùng đã cài đặt 2 ứng dụng gọi điện để triển khai API ConnectionService tự quản lý, FooTalk và BarTalk. Trong trường hợp này, những hạn chế sau sẽ được áp dụng:

  • Trên các thiết bị chạy ở API cấp 27 trở xuống, chỉ một ứng dụng có thể duy trì lệnh gọi đang diễn ra tại một thời điểm bất kỳ. Quy tắc ràng buộc này có nghĩa là trong khi người dùng có một cuộc gọi đang diễn ra bằng ứng dụng FooTalk, ứng dụng BarTalk không thể bắt đầu hoặc nhận cuộc gọi mới.

    Trên các thiết bị chạy trên API cấp 28 trở lên, nếu cả FooTalk và BarTalk đều khai báo quyền CAPABILITY_SUPPORT_HOLD và quyền CAPABILITY_HOLD, thì người dùng có thể duy trì nhiều lệnh gọi đang diễn ra bằng cách chuyển đổi giữa các ứng dụng để bắt đầu hoặc trả lời một lệnh gọi khác.

  • Nếu người dùng tham gia vào các cuộc gọi được quản lý thông thường (ví dụ: dùng ứng dụng Điện thoại hoặc Trình quay số tích hợp sẵn), thì người dùng không thể tham gia các cuộc gọi bắt nguồn từ các ứng dụng gọi. Điều này có nghĩa là nếu người dùng đang thực hiện cuộc gọi thông thường bằng nhà mạng di động, thì họ cũng không thể đồng thời tham gia cuộc gọi FooTalk hoặc BarTalk.

  • Hệ thống viễn thông phụ sẽ ngắt kết nối các cuộc gọi của ứng dụng nếu người dùng gọi điện thoại khẩn cấp.

  • Ứng dụng của bạn không thể nhận hoặc thực hiện cuộc gọi trong khi người dùng đang thực hiện cuộc gọi khẩn cấp.

  • Nếu có một cuộc gọi đang diễn ra trong ứng dụng gọi khác khi ứng dụng của bạn nhận được một cuộc gọi đến, thì việc trả lời cuộc gọi đến đó sẽ kết thúc mọi cuộc gọi đang diễn ra trong ứng dụng đó. Ứng dụng của bạn không nên hiển thị giao diện người dùng có cuộc gọi đến thông thường. Khung viễn thông hiển thị giao diện người dùng của cuộc gọi đến và thông báo cho người dùng rằng việc trả lời cuộc gọi mới sẽ kết thúc(các) cuộc gọi đang diễn ra. Điều này nghĩa là nếu người dùng đang tham gia cuộc gọi FooTalk và ứng dụng BarTalk nhận được cuộc gọi đến, khung viễn thông sẽ thông báo cho người dùng rằng họ có cuộc gọi BarTalk mới đến và việc trả lời cuộc gọi BarTalk sẽ kết thúc cuộc gọi FooTalk của họ.

Trở thành ứng dụng Điện thoại mặc định

Trình quay số/ứng dụng điện thoại mặc định là ứng dụng cung cấp giao diện người dùng trong cuộc gọi khi thiết bị đang tham gia cuộc gọi. Ngoài ra, tính năng này cũng cung cấp cho người dùng một phương thức để bắt đầu cuộc gọi và xem nhật ký cuộc gọi trên thiết bị của họ. Thiết bị đi kèm với ứng dụng điện thoại/trình quay số mặc định do hệ thống cung cấp. Người dùng có thể chọn một ứng dụng duy nhất để đảm nhận vai trò này từ ứng dụng hệ thống. Một ứng dụng muốn thực hiện vai trò này sử dụng RoleManager để yêu cầu họ thực hiện vai trò RoleManager.ROLE_DIALER.

Ứng dụng điện thoại mặc định cung cấp giao diện người dùng khi thiết bị đang gọi điện và thiết bị không ở chế độ trên ô tô (tức là UiModeManager#getCurrentModeType() không phải là Configuration.UI_MODE_TYPE_CAR).

Để thực hiện vai trò RoleManager.ROLE_DIALER, ứng dụng phải đáp ứng một số yêu cầu:

  • Hàm này phải xử lý ý định Intent#ACTION_DIAL. Tức là ứng dụng phải cung cấp giao diện người dùng bàn phím số để người dùng bắt đầu cuộc gọi đi.
  • Ứng dụng này phải triển khai đầy đủ API InCallService, đồng thời cung cấp cả giao diện người dùng cho cuộc gọi đến cũng như giao diện người dùng cho cuộc gọi đang diễn ra.

Lưu ý: Nếu ứng dụng điền RoleManager.ROLE_DIALER trả về một null InCallService trong quá trình liên kết, khung Viễn thông sẽ tự động quay lại sử dụng ứng dụng trình quay số được tải trước trên thiết bị. Hệ thống sẽ hiển thị thông báo cho người dùng để cho họ biết rằng cuộc gọi của họ vẫn đang tiếp tục bằng ứng dụng quay số được tải trước. Ứng dụng của bạn không được trả về một liên kết null; làm như vậy có nghĩa là ứng dụng không đáp ứng được các yêu cầu của RoleManager.ROLE_DIALER.

Lưu ý: Nếu ứng dụng của bạn điền RoleManager.ROLE_DIALER và thực hiện các thay đổi trong thời gian chạy khiến ứng dụng không còn đáp ứng các yêu cầu của vai trò này nữa, thì RoleManager sẽ tự động xoá ứng dụng khỏi vai trò này và đóng ứng dụng của bạn. Ví dụ: nếu bạn sử dụng PackageManager.setComponentEnabledSetting(ComponentName, int, int) để vô hiệu hoá InCallService mà ứng dụng khai báo trong tệp kê khai theo phương thức lập trình, thì ứng dụng của bạn sẽ không còn đáp ứng các yêu cầu dự kiến của RoleManager.ROLE_DIALER nữa.

Trình quay số được tải trước sẽ LUÔN được sử dụng khi người dùng thực hiện cuộc gọi khẩn cấp, ngay cả khi ứng dụng của bạn thực hiện vai trò RoleManager.ROLE_DIALER. Để đảm bảo có được trải nghiệm tối ưu khi thực hiện cuộc gọi khẩn cấp, trình quay số mặc định phải LUÔN sử dụng TelecomManager.placeCall(Uri, Bundle) để thực hiện cuộc gọi (bao gồm cả cuộc gọi khẩn cấp). Điều này đảm bảo nền tảng có thể xác minh rằng yêu cầu đến từ trình quay số mặc định. Nếu một ứng dụng trình quay số không tải trước sử dụng Intent#ACTION_CALL để thực hiện cuộc gọi khẩn cấp, thì cuộc gọi đó sẽ được chuyển lên ứng dụng quay số tải trước bằng Intent#ACTION_DIAL để xác nhận; đây là trải nghiệm người dùng chưa tối ưu.

Dưới đây là ví dụ về quy trình đăng ký tệp kê khai cho InCallService. Siêu dữ liệu TelecomManager#METADATA_IN_CALL_SERVICE_UI cho biết rằng phương thức triển khai InCallService cụ thể này nhằm thay thế giao diện người dùng tích hợp sẵn trong lệnh gọi. Siêu dữ liệu TelecomManager#METADATA_IN_CALL_SERVICE_RINGING cho biết rằng InCallService này sẽ phát nhạc chuông khi có cuộc gọi đến. Xem phần bên dưới để biết thêm thông tin về cách hiển thị giao diện người dùng của cuộc gọi đến và phát nhạc chuông trong ứng dụng của bạn.

 <service android:name="your.package.YourInCallServiceImplementation"
          android:permission="android.permission.BIND_INCALL_SERVICE"
          android:exported="true">
      <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
      <meta-data android:name="android.telecom.IN_CALL_SERVICE_RINGING"
          android:value="true" />
      <intent-filter>
          <action android:name="android.telecom.InCallService"/>
      </intent-filter>
 </service>

Lưu ý: Bạn KHÔNG nên đánh dấu InCallService bằng thuộc tính android:exported="false" vì làm như vậy có thể dẫn đến lỗi liên kết với phương thức triển khai của bạn trong các lệnh gọi.

Ngoài việc triển khai API InCallService, bạn cũng phải khai báo một hoạt động trong tệp kê khai giúp xử lý ý định Intent#ACTION_DIAL. Ví dụ dưới đây minh hoạ cách thực hiện việc này:

 <activity android:name="your.package.YourDialerActivity"
           android:label="@string/yourDialerActivityLabel">
      <intent-filter>
           <action android:name="android.intent.action.DIAL" />
           <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <intent-filter>
           <action android:name="android.intent.action.DIAL" />
           <category android:name="android.intent.category.DEFAULT" />
           <data android:scheme="tel" />
      </intent-filter>
 </activity>

Khi người dùng cài đặt và chạy ứng dụng đó lần đầu tiên, bạn nên sử dụng RoleManager để nhắc người dùng xem họ có muốn đặt ứng dụng của bạn làm ứng dụng điện thoại mặc định mới hay không.

Mã dưới đây cho thấy cách ứng dụng của bạn có thể yêu cầu trở thành ứng dụng gọi điện/trình quay số mặc định:

 private static final int REQUEST_ID = 1;

 public void requestRole() {
     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
     Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
     startActivityForResult(intent, REQUEST_ID);
 }

 public void onActivityResult(int requestCode, int resultCode, Intent data) {
     if (requestCode == REQUEST_ID) {
         if (resultCode == android.app.Activity.RESULT_OK) {
             // Your app is now the default dialer app
         } else {
             // Your app is not the default dialer app
         }
     }
 }

Quyền truy cập vào InCallService cho thiết bị đeo

    Nếu ứng dụng của bạn là ứng dụng đồng hành bên thứ ba và muốn truy cập vào các API InCallService, thì ứng dụng có thể làm những việc sau:

    1. Khai báo quyền MANAGE_ONGOING_CALLS trong tệp kê khai
    2. Liên kết với một thiết bị đeo thực thông qua API CompanionDeviceManager dưới dạng ứng dụng đồng hành. Hãy xem: https://developer.android.com/guide/topics/connectivity/companion-device-pairing
    3. Triển khai InCallService này bằng quyền BIND_INCALL_SERVICE

Hiện thông báo cuộc gọi đến

Khi nhận được cuộc gọi đến mới qua InCallService#onCallAdded(Call), ứng dụng của bạn sẽ chịu trách nhiệm hiển thị giao diện người dùng của cuộc gọi đến cho cuộc gọi đến. Ứng dụng nên thực hiện việc này bằng cách sử dụng các API NotificationManager để đăng thông báo cuộc gọi đến mới.

Khi ứng dụng khai báo siêu dữ liệu TelecomManager#METADATA_IN_CALL_SERVICE_RINGING, ứng dụng đó chịu trách nhiệm phát nhạc chuông cho các cuộc gọi đến. Ứng dụng của bạn nên tạo một NotificationChannel để chỉ định nhạc chuông mong muốn. Ví dụ:

 NotificationChannel channel = new NotificationChannel(YOUR_CHANNEL_ID, "Incoming Calls",
          NotificationManager.IMPORTANCE_MAX);
 // other channel setup stuff goes here.

 // We'll use the default system ringtone for our incoming call notification channel.  You can
 // use your own audio resource here.
 Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
 channel.setSound(ringtoneUri, new AudioAttributes.Builder()
          // Setting the AudioAttributes is important as it identifies the purpose of your
          // notification sound.
          .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
          .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
      .build());

 NotificationManager mgr = getSystemService(NotificationManager.class);
 mgr.createNotificationChannel(channel);

Khi nhận được cuộc gọi đến mới, ứng dụng của bạn sẽ tạo một Notification cho cuộc gọi đến và liên kết kênh này với kênh thông báo cuộc gọi đến. Bạn có thể chỉ định PendingIntent trên thông báo. Thao tác này sẽ mở giao diện người dùng của cuộc gọi đến trên toàn màn hình. Khung trình quản lý thông báo sẽ hiển thị thông báo của bạn dưới dạng thông báo quan trọng nếu người dùng đang sử dụng điện thoại. Khi người dùng không sử dụng điện thoại, giao diện người dùng của cuộc gọi đến toàn màn hình sẽ được sử dụng. Ví dụ:

 // Create an intent which triggers your fullscreen incoming call user interface.
 Intent intent = new Intent(Intent.ACTION_MAIN, null);
 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
 intent.setClass(context, YourIncomingCallActivity.class);
 PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 // Build the notification as an ongoing high priority item; this ensures it will show as
 // a heads up notification which slides down over top of the current content.
 final Notification.Builder builder = new Notification.Builder(context);
 builder.setOngoing(true);
 builder.setPriority(Notification.PRIORITY_HIGH);
 // Set notification content intent to take user to the fullscreen UI if user taps on the
 // notification body.
 builder.setContentIntent(pendingIntent);
 // Set full screen intent to trigger display of the fullscreen UI when the notification
 // manager deems it appropriate.
 builder.setFullScreenIntent(pendingIntent, true);
 // Setup notification content.
 builder.setSmallIcon( yourIconResourceId );
 builder.setContentTitle("Your notification title");
 builder.setContentText("Your notification content.");
 // Use builder.addAction(..) to add buttons to answer or reject the call.
 NotificationManager notificationManager = mContext.getSystemService(
     NotificationManager.class);
 notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build());
```