Cómo entregar casos de uso comunes con una visibilidad de paquetes limitada

En este documento, se presentan varios casos de uso comunes en los que una app interactúa con otras. En cada sección, se proporciona orientación para lograr la funcionalidad de la app con visibilidad limitada de paquetes, que debes tener en cuenta si tu app se orienta a Android 11 (nivel de API 30) o versiones posteriores.

Cuando una app para Android 11 o versiones posteriores usa un intent para iniciar una actividad en otra app, el enfoque más simple es invocar el intent y controlar la excepción ActivityNotFoundException si no hay ninguna app disponible.

Si para una parte de tu app es necesario saber si la llamada a startActivity() puede completarse correctamente, como cuando se muestra una IU, agrega un objeto al elemento <queries> del manifiesto de tu app. Por lo general, este elemento es un <intent>.

Cómo abrir URLs

En esta sección, se describen varias maneras para abrir URLs en una app para Android 11 o versiones posteriores.

Cómo abrir URLs en otra app o en un navegador

Para abrir una URL, usa un intent que contenga la acción de intent ACTION_VIEW, como se describe en la guía para cargar una URL web. Después de llamar a startActivity() con este intent, sucede una de las siguientes opciones:

  • Se abre la URL en una app de navegador web.
  • Se abre la URL en una app que admite la URL como un vínculo directo.
  • Aparece un diálogo de desambiguación que permite al usuario elegir qué app abre la URL.
  • Se produce una ActivityNotFoundException porque no hay ninguna app instalada en el dispositivo que pueda abrir la URL (este no es un caso común).

    Se recomienda que tu app detecte y procese la ActivityNotFoundException si esto ocurre.

Dado que el método startActivity() no requiere la visibilidad de paquetes para iniciar otra actividad de la aplicación, no es necesario que agregues un elemento <queries> al manifiesto de tu app ni que realices ningún cambio al elemento <queries> existente. Esto se aplica a los intents implícitos y explícitos que abren una URL.

Cómo comprobar si hay un navegador disponible

En algunos casos, es posible que, antes de intentar abrir una URL, tu app quiera verificar si hay al menos un navegador disponible en el dispositivo o si un navegador específico es el predeterminado. En esos casos, incluye el siguiente elemento <intent> como parte del elemento <queries> de tu manifiesto:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="https" />
</intent>

En algunos casos, cuando llamas a queryIntentActivities() y pasas un intent web como argumento, la lista que se muestra incluye las apps de navegador disponibles. La lista no incluye apps de navegador si el usuario configuró la URL para que se abra en una app sin navegador de forma predeterminada.

Cómo abrir URLs en pestañas personalizadas

Las pestañas personalizadas permiten que una app personalice el aspecto y la experiencia del navegador. Puedes abrir una URL en una pestaña personalizada sin necesidad de agregar ni cambiar el elemento <queries> en el manifiesto de tu app.

Sin embargo, tal vez quieras verificar si el dispositivo es compatible con las pestañas personalizadas o seleccionar un navegador específico que se inicie con pestañas personalizadas mediante CustomTabsClient.getPackageName(). En esos casos, incluye el siguiente elemento <intent> como parte del elemento <queries> de tu manifiesto:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>

Cómo permitir que las apps que no son de navegación controlen URLs

Incluso si tu app puede abrir URLs con pestañas personalizadas, se recomienda que permitas que una app que no es de navegación abra una URL determinada, si es posible. Para proporcionar esta función en tu app, llama a startActivity() usando un intent que establezca la marca de intent FLAG_ACTIVITY_REQUIRE_NON_BROWSER. Si el sistema muestra una ActivityNotFoundException, tu app puede abrir la URL en una pestaña personalizada.

Si un intent incluye esta marca, una llamada a startActivity() hace que se genere una ActivityNotFoundException en alguna de las siguientes condiciones:

  • La llamada habría iniciado directamente una app de navegador.
  • La llamada le habría mostrado al usuario un diálogo de desambiguación, en el que las únicas opciones son apps de navegador.

En el siguiente fragmento de código, se muestra cómo actualizar la lógica para usar la marca de intent FLAG_ACTIVITY_REQUIRE_NON_BROWSER:

Kotlin

try {
    val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply {
        // The URL should either launch directly in a non-browser app (if it's
        // the default) or in the disambiguation dialog.
        addCategory(CATEGORY_BROWSABLE)
        flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER
    }
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // Only browser apps are available, or a browser is the default.
    // So you can open the URL directly in your app, for example in a
    // Custom Tab.
    openInCustomTabs(url)
}

Java

try {
    Intent intent = new Intent(ACTION_VIEW, Uri.parse(url));
    // The URL should either launch directly in a non-browser app (if it's the
    // default) or in the disambiguation dialog.
    intent.addCategory(CATEGORY_BROWSABLE);
    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // Only browser apps are available, or a browser is the default.
    // So you can open the URL directly in your app, for example in a
    // Custom Tab.
    openInCustomTabs(url);
}

Cómo evitar que se muestre un diálogo de desambiguación

Si no deseas que se muestre el diálogo de desambiguación que los usuarios podrían ver cuando abren una URL, y prefieres procesar la URL por tu cuenta, puedes usar un intent que establezca la marca de intent FLAG_ACTIVITY_REQUIRE_DEFAULT.

Si un intent incluye esta marca, una llamada a startActivity() hace que se genere una ActivityNotFoundException en el momento en el que la llamada le habría mostrado al usuario un diálogo de desambiguación.

Si un intent incluye esta marca y la marca de intent FLAG_ACTIVITY_REQUIRE_NON_BROWSER, una llamada a startActivity() hace que se arroje una ActivityNotFoundException si se cumple alguna de las siguientes condiciones:

  • La llamada habría iniciado directamente una app de navegador.
  • La llamada le habría mostrado al usuario un diálogo de desambiguación.

En el siguiente fragmento de código, se muestra cómo usar las marcas FLAG_ACTIVITY_REQUIRE_NON_BROWSER y FLAG_ACTIVITY_REQUIRE_DEFAULT juntas:

Kotlin

val url = URL_TO_LOAD
try {
    // For this intent to be invoked, the system must directly launch a
    // non-browser app.
    val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply {
        addCategory(CATEGORY_BROWSABLE)
        flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER or
                FLAG_ACTIVITY_REQUIRE_DEFAULT
    }
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // This code executes in one of the following cases:
    // 1. Only browser apps can handle the intent.
    // 2. The user has set a browser app as the default app.
    // 3. The user hasn't set any app as the default for handling this URL.
    openInCustomTabs(url)
}

Java

String url = URL_TO_LOAD;
try {
    // For this intent to be invoked, the system must directly launch a
    // non-browser app.
    Intent intent = new Intent(ACTION_VIEW, Uri.parse(url));
    intent.addCategory(CATEGORY_BROWSABLE);
    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REQUIRE_NON_BROWSER |
            FLAG_ACTIVITY_REQUIRE_DEFAULT);
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // This code executes in one of the following cases:
    // 1. Only browser apps can handle the intent.
    // 2. The user has set a browser app as the default app.
    // 3. The user hasn't set any app as the default for handling this URL.
    openInCustomTabs(url);
}

Cómo abrir un archivo

Si tu app controla archivos o archivos adjuntos, como cuando se verifica si un dispositivo puede abrir un archivo determinado, suele ser más fácil intentar iniciar una actividad que pueda controlar el archivo. Para hacerlo, usa un intent que incluya la acción de intent ACTION_VIEW y el URI que representa al archivo específico. Si no hay ninguna app disponible en el dispositivo, tu app puede detectar la ActivityNotFoundException. En tu lógica de control de excepciones, puedes mostrar un error o intentar controlar el archivo por tu cuenta.

Si tu app debe saber con anticipación si otra app puede abrir un archivo determinado, incluye el elemento <intent> en el siguiente fragmento de código como parte del elemento <queries> de tu manifiesto. Incluye el tipo de archivo si ya sabes cuál es durante la compilación.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
  <data android:mimeType="application/pdf" />
</intent>

Luego puedes llamar a resolveActivity() con tu intent para verificar si una app está disponible.

Cómo otorgar un acceso de URI

Nota: Es obligatorio declarar los permisos de acceso de URI como se describe en esta sección para las apps orientadas a Android 11 (nivel de API 30) o versiones posteriores, y se recomienda para todas las apps, independientemente del SDK de destino y si exportan sus proveedores de contenido.

A fin de que las apps que se orientan a Android 11 o versiones posteriores puedan acceder al URI de contenido, el intent de tu app debe declarar permisos de acceso al URI mediante la configuración de una de las siguientes marcas de intent o ambas: FLAG_GRANT_READ_URI_PERMISSION o FLAG_GRANT_WRITE_URI_PERMISSION.

En Android 11 y versiones posteriores, los permisos de acceso al URI proporcionan las siguientes capacidades a la app que recibe el intent:

  • Leer los datos que representa el URI de contenido o escribir en ellos, según los permisos de URI proporcionados
  • Obtener visibilidad de la app que contiene el proveedor de contenido que coincide con la autoridad del URI (la app que incluye el proveedor de contenido puede ser diferente de la que envía el intent)

En el siguiente fragmento de código, se muestra cómo agregar una marca de intent para permisos de URI, de modo que otra app orientada a Android 11 o versiones posteriores pueda ver los datos del URI de contenido:

Kotlin

val shareIntent = Intent(Intent.ACTION_VIEW).apply {
    flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    data = CONTENT_URI_TO_SHARE_WITH_OTHER_APP
}

Java

Intent shareIntent = new Intent(Intent.ACTION_VIEW);
shareIntent.setFlags(FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.setData(CONTENT_URI_TO_SHARE_WITH_OTHER_APP);

Cómo conectarse a los servicios

Si la app necesita interactuar con un servicio que no es visible automáticamente, puedes declarar la acción de intent correcta dentro de un elemento <queries>. En las siguientes secciones, se brindan ejemplos mediante servicios de acceso común.

Cómo conectarse a un motor de texto a voz

Si tu app interactúa con un motor de texto a voz (TTS), incluye el siguiente elemento <intent> como parte del elemento <queries> de tu manifiesto:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.TTS_SERVICE" />
</intent>

Cómo conectarse a un servicio de reconocimiento de voz

Si tu app interactúa con un servicio de reconocimiento de voz, incluye el siguiente elemento <intent> como parte del elemento <queries> de tu manifiesto:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.speech.RecognitionService" />
</intent>

Cómo conectarse a los servicios del navegador multimedia

En tu app de navegador multimedia cliente, incluye el siguiente elemento <intent> como parte del elemento <queries> de tu manifiesto:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.media.browse.MediaBrowserService" />
</intent>

Cómo brindar una funcionalidad personalizada

Si necesitas realizar acciones personalizables o mostrar información personalizable en función de interacciones con otras apps, puedes representar ese comportamiento personalizado mediante las firmas de filtro de intents como parte del elemento <queries> en tu manifiesto. En las siguientes secciones, se brinda orientación detallada para varias situaciones comunes.

Cómo buscar apps de SMS

Si tu app necesita información sobre el conjunto de apps de SMS instaladas en un dispositivo, por ejemplo, para verificar qué app es el controlador de SMS predeterminado del dispositivo, incluye el siguiente elemento <intent> como parte del <queries> en tu manifiesto:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.SENDTO"/>
  <data android:scheme="smsto" android:host="*" />
</intent>

Cómo crear una hoja compartida

Siempre que sea posible, usa una hoja de Sharesheet proporcionada por el sistema. Como alternativa, puedes incluir el siguiente elemento <intent> como parte del elemento <queries> de tu manifiesto:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.SEND" />
  <!-- Replace with the MIME type that your app works with, if needed. -->
  <data android:mimeType="image/jpeg" />
</intent>

El proceso de compilación de la hoja compartida en la lógica de tu app, como la llamada a queryIntentActivities(), permanece sin cambios en comparación con las versiones anteriores a Android 11.

Cómo mostrar acciones de selección de texto personalizadas

Cuando los usuarios seleccionan texto en la app, una barra de herramientas de selección de texto muestra el conjunto de operaciones que pueden realizar en él. Si esta barra de herramientas muestra acciones personalizadas de otras apps, incluye el siguiente elemento <intent> como parte del elemento <queries> en tu manifiesto:

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.PROCESS_TEXT" />
  <data android:mimeType="text/plain" />
</intent>

Cómo mostrar filas de datos personalizadas para un contacto

Las apps pueden agregar filas de datos personalizadas al Proveedor de contactos. Para que una app de contactos muestre estos datos personalizados, debe ser capaz de realizar lo siguiente:

  1. Leer el archivo contacts.xml de las otras apps
  2. Cargar un ícono correspondiente al tipo de MIME personalizado

Si tu app es una app de contactos, incluye los siguientes elementos <intent> como parte del elemento <queries> de tu manifiesto:

<!-- Place inside the <queries> element. -->
<!-- Lets the app read the contacts.xml file from other apps. -->
<intent>
  <action android:name="android.accounts.AccountAuthenticator" />
</intent>
<!-- Lets the app load an icon corresponding to the custom MIME type. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <data android:scheme="content" android:host="com.android.contacts"
        android:mimeType="vnd.android.cursor.item/*" />
</intent>