Cómo compilar una app de llamadas

Una app de llamadas permite a los usuarios usar su dispositivo para recibir o realizar llamadas de audio o videollamadas. Estas apps usan su propia interfaz de usuario para las llamadas en lugar de usar la interfaz predeterminada de la app de teléfono, como se muestra en la siguiente captura de pantalla.

Ejemplo de una app de llamadas
Ejemplo de una app de llamadas que usa su propia interfaz de usuario

El framework de Android incluye el paquete android.telecom, que contiene clases que te ayudan a crear una app de llamadas según el framework de las telecomunicaciones. Si creas tu app de acuerdo con el framework de telecomunicaciones, obtendrás los siguientes beneficios:

  • Tu app interoperará correctamente con el subsistema nativo de telecomunicaciones del dispositivo.
  • Tu app interoperará correctamente con otras apps de llamadas que también cumplan con las disposiciones del framework.
  • El framework ayuda a tu app a administrar el enrutamiento de audio y video.
  • El framework ayuda a tu app a determinar si sus llamadas tienen foco.

Permisos y declaraciones de manifiesto

En el manifiesto de la app, declara que usa el permiso MANAGE_OWN_CALLS, como se muestra en el siguiente ejemplo:

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

Puedes obtener más información para declarar permisos de apps en la sección Permisos.

Debes declarar un servicio que especifique la clase que implementa la clase ConnectionService en tu app. El subsistema de telecomunicaciones requiere que el servicio declare el permiso BIND_TELECOM_CONNECTION_SERVICE para poder vincularse a él. En el siguiente ejemplo, se muestra cómo declarar el servicio en el manifiesto de la app:

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

Puedes obtener más información para declarar los componentes de la app, incluidos los servicios, en la sección Componentes de la app.

Cómo implementar el servicio de conexión

Tu app de llamadas debe proporcionar una implementación de la clase ConnectionService a la que se pueda vincular el subsistema de telecomunicaciones. Tu implementación de ConnectionService debe anular los siguientes métodos:

onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)

El subsistema de telecomunicaciones llama a este método en respuesta a la llamada que hace tu app a placeCall(Uri, Bundle) para crear una nueva llamada saliente. Tu app muestra una instancia nueva de la implementación de la clase Connection (para obtener más información, consulta la sección Cómo implementar la conexión) para representar la nueva llamada saliente. Puedes personalizar aún más la conexión saliente si realizas las siguientes acciones:

onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

El subsistema de telecomunicaciones llama a este método cuando tu app llama al método placeCall(Uri, Bundle) y no se puede realizar la llamada saliente. En respuesta a esta situación, tu app debe informar al usuario (por ejemplo, mediante un cuadro de alerta o un aviso) que no se pudo realizar la llamada saliente. Es posible que tu app no pueda realizar llamadas si, antes de hacerlo, hay una llamada de emergencia o algún otro tipo de llamada en curso que no se puede poner en espera en otra app.

onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)

El subsistema de telecomunicaciones llama a este método cuando tu app llama al método addNewIncomingCall(PhoneAccountHandle, Bundle) para informar al sistema sobre una nueva llamada entrante en la app. Esta muestra una nueva instancia de tu implementación de Connection (para obtener más información, consulta la sección Cómo implementar la conexión) para representar la nueva llamada entrante. Puedes personalizar aún más la conexión entrante si realizas las siguientes acciones:

onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

El subsistema de telecomunicaciones llama a este método cuando tu app llama al método addNewIncomingCall(PhoneAccountHandle, Bundle) para informar al sistema de telecomunicaciones sobre una nueva llamada entrante, pero esta no está permitida (para obtener más información, consulta la sección Restricciones de llamadas). Tu app debe rechazar de manera silenciosa la llamada entrante y, opcionalmente, publicar una notificación para informar al usuario sobre la llamada perdida.

Cómo implementar la conexión

Tu app debe crear una subclase de Connection para representar las llamadas en ella. Debes anular los siguientes métodos en tu implementación:

onShowIncomingCallUi()

El subsistema de telecomunicaciones llama a este método cuando agregas una nueva llamada entrante, y tu app debería mostrar su IU de llamada entrante.

onCallAudioStateChanged(CallAudioState)

El subsistema de telecomunicaciones llama a este método para informar a tu app que la ruta o el modo de audio actual ha cambiado. Se lo llama en respuesta al cambio que hizo tu app en el modo audio con el método setAudioRoute(int). También se puede llamar a este método si el sistema cambia la ruta del audio (por ejemplo, cuando se desconectan los auriculares Bluetooth).

onHold()

El subsistema de telecomunicaciones llama a este método cuando quiere poner una llamada en espera. En respuesta a esta solicitud, tu app debe retener la llamada y, luego, invocar el método setOnHold() para informar al sistema que la llamada está en espera. El subsistema de telecomunicaciones puede llamar a este método cuando un servicio en llamada, como Android Auto, muestra que tu llamada desea retransmitir la solicitud de un usuario para poner la llamada en espera. El subsistema de telecomunicaciones también llama a este método si el usuario hace que una llamada esté activa en otra app. Para obtener más información sobre los servicios en llamada, consulta la información sobre InCallService.

onUnhold()

El subsistema de telecomunicaciones llama a este método cuando desea reanudar una llamada que se puso en espera. Una vez que tu app ha reanudado la llamada, debe invocar el método setActive() para informar al sistema que la llamada ya no está en espera. El subsistema de telecomunicaciones puede llamar a este método cuando un servicio en llamada, como Android Auto, muestra que tu llamada desea retransmitir una solicitud para reanudar la llamada. Para obtener más información sobre los servicios en llamada, consulta la información sobre InCallService.

onAnswer()

El subsistema de telecomunicaciones llama a este método para informarle a tu app que se debe contestar una llamada entrante. Una vez que la app respondió la llamada, debe invocar el método setActive() para informar al sistema que se contestó la llamada. El subsistema de telecomunicaciones puede llamar a este método cuando tu app agrega una nueva llamada entrante y ya hay una llamada saliente en curso en otra app que no se puede poner en espera. En estos casos, el subsistema de telecomunicaciones muestra la IU de la llamada entrante en nombre de tu app. El framework proporciona un método con sobrecarga que brinda compatibilidad para especificar el estado de video en el cual se contesta la llamada. Para obtener más información, consulta la información sobre onAnswer(int).

onReject()

El subsistema de telecomunicaciones llama a este método cuando quiere rechazar una llamada entrante. Una vez que la app ha rechazado la llamada, debe llamar a setDisconnected(DisconnectCause) y especificar REJECTED como el parámetro. Luego, tu app debe llamar al método destroy() para informar al sistema que la app procesó la llamada. El subsistema de telecomunicaciones llama a este método cuando el usuario rechaza una llamada entrante de tu app.

onDisconnect()

El subsistema de telecomunicaciones llama a este método cuando quiere desconectar una llamada. Una vez que la llamada ha finalizado, tu app debe llamar al método setDisconnected(DisconnectCause) y especificar LOCAL como el parámetro para indicar que una solicitud del usuario provocó que se desconecte la llamada. Entonces, tu app debe llamar al método destroy() para informar al subsistema de telecomunicaciones que la app procesó la llamada. El sistema puede llamar a este método después de que el usuario interrumpe una llamada a través de otro servicio en llamada, como Android Auto. El sistema también llama a este método cuando tu llamada debe interrumpirse para permitir que se realice otra llamada; por ejemplo, si el usuario desea realizar una llamada de emergencia. Para obtener más información sobre los servicios en llamada, consulta la información sobre InCallService.

Cómo controlar situaciones de llamadas comunes

Utilizar la API de ConnectionService en tu flujo de llamadas implica interactuar con las otras clases del paquete android.telecom. En las siguientes secciones, se describen situaciones de llamadas comunes y cómo tu app debe controlarlas usando las API.

Cómo contestar las llamadas entrantes

El flujo para controlar las llamadas entrantes cambia si hay llamadas en otras apps. El motivo de la diferencia en los flujos es que el framework de las telecomunicaciones debe establecer algunas restricciones cuando hay llamadas activas en otras apps a fin de garantizar un entorno estable para todas las apps de llamadas en el dispositivo. Para obtener más información, consulta la sección Restricciones de llamadas.

No hay llamadas activas en otras apps

Para contestar llamadas entrantes cuando no hay llamadas activas en otras apps, sigue estos pasos:

  1. Tu app recibe una nueva llamada entrante utilizando sus mecanismos habituales.
  2. Usa el método addNewIncomingCall(PhoneAccountHandle, Bundle) para informar al subsistema de telecomunicaciones sobre la nueva llamada entrante.
  3. El subsistema de telecomunicaciones se vincula a la implementación de ConnectionService de tu app y solicita una nueva instancia de la clase Connection, que representa la nueva llamada entrante a través del método onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest).
  4. El subsistema de telecomunicaciones informa a tu app que debe mostrar su interfaz de usuario de llamada entrante con el método onShowIncomingCallUi().
  5. Tu app muestra su IU de llamada entrante mediante una notificación con un intent asociado en pantalla completa. Para obtener más información, consulta la información sobre onShowIncomingCallUi().
  6. Llama al método setActive() si el usuario acepta la llamada entrante, o llama a setDisconnected(DisconnectCause) y especifica REJECTED como el parámetro seguido de una llamada al método destroy() si el usuario rechaza la llamada entrante.

Llamadas activas en otras apps que no se pueden poner en espera

Para contestar llamadas entrantes cuando hay llamadas activas en otras apps que no se pueden poner en espera, sigue estos pasos:

  1. Tu app recibe una nueva llamada entrante utilizando sus mecanismos habituales.
  2. Usa el método addNewIncomingCall(PhoneAccountHandle, Bundle) para informar al subsistema de telecomunicaciones sobre la nueva llamada entrante.
  3. El subsistema de telecomunicaciones se vincula a la implementación de ConnectionService de tu app y solicita una nueva instancia del objeto Connection, que representa la nueva llamada entrante a través del método onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest).
  4. El subsistema de telecomunicaciones muestra la IU correspondiente para tu llamada entrante.
  5. Si el usuario acepta la llamada, el subsistema de telecomunicaciones llama al método onAnswer(). Debes llamar al método setActive() para indicar al subsistema de telecomunicaciones que la llamada se estableció.
  6. Si el usuario rechaza la llamada, el subsistema de telecomunicaciones llama al método onReject(). Debes llamar al método setDisconnected(DisconnectCause) y definir REJECTED como el parámetro seguido de una llamada al método destroy().

Cómo realizar llamadas salientes

En el flujo para realizar una llamada saliente, existe la posibilidad de que no se pueda realizar debido a restricciones que impone el framework de las telecomunicaciones. Para obtener más información, consulta la sección Restricciones de llamadas.

Para realizar una llamada saliente, sigue estos pasos:

  1. El usuario inicia una llamada saliente dentro de tu app.
  2. Usa el método placeCall(Uri, Bundle) para informar al subsistema de telecomunicaciones sobre la nueva llamada saliente. Ten en cuenta las siguientes consideraciones para los parámetros del método:
    • El parámetro Uri representa la dirección a la que se realiza la llamada. Para números de teléfono normales, usa el esquema de URI tel:.
    • El parámetro Bundle te permite brindar información sobre tu app de llamadas si agregas el objeto PhoneAccountHandle de la app al EXTRA_PHONE_ACCOUNT_HANDLE adicional. Tu app debe proporcionar el objeto PhoneAccountHandle a cada llamada saliente.
    • El parámetro Bundle también te permite especificar si la llamada saliente incluye video; establece el valor STATE_BIDIRECTIONAL en el EXTRA_START_CALL_WITH_VIDEO_STATE adicional. Ten en cuenta que, de forma predeterminada, el subsistema de telecomunicaciones enruta las videollamadas al altavoz.
  3. El subsistema de telecomunicaciones se vincula con la implementación de ConnectionService de tu app.
  4. Si esta no puede realizar una llamada saliente, el subsistema de telecomunicaciones llama al método onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) para informar a la app que no es posible hacer la llamada en ese momento. Tu app debe informar al usuario que no se puede realizar la llamada.
  5. Si tu app puede realizar la llamada saliente, el subsistema de telecomunicaciones llama al método onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest). La app debe mostrar una instancia de tu clase Connection para representar la nueva llamada saliente. Si quieres obtener más información sobre las propiedades que debes establecer en la conexión, consulta Cómo implementar el servicio de conexión.
  6. Cuando se haya establecido la llamada saliente, llama al método setActive() para informar al subsistema de telecomunicaciones que la llamada está activa.

Finalizar una llamada

Para finalizar una llamada, sigue estos pasos:

  1. Llama a setDisconnected(DisconnectCause) enviando LOCAL como parámetro si el usuario finalizó la llamada o REMOTE si la otra parte finalizó la llamada.
  2. Llama al método destroy().

Restricciones de llamadas

A fin de garantizar una experiencia de llamada consistente y sencilla para tus usuarios, el framework de las telecomunicaciones aplica algunas restricciones a la hora de administrar las llamadas en el dispositivo. Supongamos que el usuario instaló dos apps de llamadas que implementan la API autoadministrada de ConnectionService, FooTalk y BarTalk. En este caso, se aplican las siguientes restricciones:

  • En dispositivos que ejecutan el nivel de API 27 o versiones anteriores, solo una app puede mantener una llamada en curso en cualquier momento. Esta restricción significa que, mientras un usuario está en una llamada mediante la app FooTalk, la app BarTalk no puede iniciar ni recibir llamadas.

    En los dispositivos que ejecutan el nivel de API 28 o versiones posteriores, si FooTalk y BarTalk declaran los permisos de CAPABILITY_SUPPORT_HOLD y CAPABILITY_HOLD, el usuario puede mantener más de una llamada en curso si alterna entre las apps para iniciar o responder otra llamada.

  • Si el usuario participa en llamadas administradas regulares (por ejemplo, con la app de Teléfono integrada), no puede llevar a cabo llamadas que se originaron a partir de apps de llamadas. Esto significa que, si el usuario está en una llamada normal usando su proveedor de telefonía celular, tampoco puede estar en una llamada de FooTalk o BarTalk de forma simultánea.

  • El subsistema de telecomunicaciones desconecta las llamadas de tu app si el usuario realiza una llamada de emergencia.

  • Tu app no puede recibir ni realizar llamadas mientras el usuario está en una llamada de emergencia.

  • Si contestas una llamada entrante en tu app mientras hay una llamada en curso en la otra app de llamadas, la llamada en curso finalizará. Tu app no debería mostrar su interfaz de usuario común de llamada entrante. El framework de las telecomunicaciones muestra la interfaz de usuario de llamada entrante y comunica al usuario que contestar la nueva llamada finalizará la que está en curso. Esto significa que, si el usuario está en una llamada de FooTalk, y la app de BarTalk recibe una llamada entrante, el framework de las telecomunicaciones informará al usuario que tiene una nueva llamada entrante de BarTalk y que responder la llamada de BarTalk finalizará su llamada de FooTalk.