Seguridad de la actividad

Android protege a los usuarios de apps maliciosas y proporciona una experiencia de IU confiable. El framework de seguridad de actividades abarca reglas y restricciones de la plataforma. Estas reglas y restricciones evitan interrupciones no deseadas de la IU, el secuestro de tareas y otras amenazas de seguridad. Estas amenazas se relacionan con cuándo y cómo aparecen los componentes de la app en la pantalla. Un componente clave de este framework restringe el inicio de actividades en segundo plano.

Restricciones de inicio de actividades en segundo plano

El inicio de actividades en segundo plano (BAL) se produce cuando una app que no está en primer plano, sin actividades visibles o un PendingIntent recibido de una app diferente intenta iniciar una actividad nueva. Esto es un inicio de actividades en segundo plano (BAL). Si bien existen casos de uso legítimos, como cuando se inicia una app de reloj despertador, los BAL sin restricciones generan una experiencia del usuario deficiente y crean vulnerabilidades de seguridad.

¿Por qué están restringidos?

Desde Android 10 (nivel de API 29), la plataforma impuso restricciones sobre cuándo las apps pueden iniciar actividades en segundo plano. Estas protecciones ayudan a evitar el comportamiento malicioso de las apps y a mejorar la experiencia del usuario mitigando los abusos comunes, incluidos los siguientes:

  • Secuestro de la IU y anuncios emergentes: Una app en segundo plano inicia inesperadamente una actividad (a menudo, un anuncio) sobre la app con la que el usuario está interactuando, lo que secuestra su sesión.
  • Phishing y suplantación de identidad: Una app en segundo plano inicia una actividad que se hace pasar por otra app (por ejemplo, una pantalla de acceso falsa para una app legítima) para robar las credenciales del usuario. Esto suele lograrse a través de un ataque de "sándwich de actividad", en el que se inserta una actividad maliciosa en la pila de tareas de una app legítima.
  • Tapjacking: Una app en segundo plano muestra una actividad transparente u oculta sobre otra app para interceptar las acciones del usuario y engañarlo para que realice acciones no deseadas.
  • Despertar de la app: Un componente en segundo plano de una app despierta los componentes en primer plano de otra app para aumentar de forma ilegítima las métricas de usuarios activos diarios.

Servicios en primer plano (para tareas en curso)

Si tu app necesita realizar una tarea de larga duración en segundo plano de la que el usuario debe estar al tanto, como reproducir música o hacer un seguimiento de un entrenamiento, debes usar un servicio en primer plano. Un servicio en primer plano debe mostrar una notificación persistente que el usuario no pueda descartar. Esta notificación puede proporcionar controles interactivos (por ejemplo, botones de reproducción o pausa para una app de música). Esto mantiene al usuario informado y bajo control, pero no lo interrumpe con una actividad de pantalla completa.

Si sigues esta jerarquía, comenzando con las notificaciones estándar y solo escalando a opciones más intrusivas cuando sea necesario, crearás una experiencia mejor y más predecible para tus usuarios.

Cuándo se permiten los inicios de actividades en segundo plano (excepciones)

Una app puede iniciar una actividad en segundo plano si se cumple una de las siguientes condiciones:

  • La app tiene una ventana visible, como una actividad en primer plano.
  • La app es el editor de métodos de entrada (IME) actual.
  • La actividad se inicia desde un PendingIntent que envió el sistema (por ejemplo, desde un toque de notificación).
  • El usuario otorgó el permiso SYSTEM_ALERT_WINDOW a la app.
  • Se otorgó el permiso START_ACTIVITIES_FROM_BACKGROUND a la app.
  • La app está vinculada por un servicio al que se le otorgó permiso para iniciar actividades en segundo plano.
  • El inicio lo inicia la app de selector del dispositivo, como cuando un usuario presiona un ícono de la app o interactúa con un widget.
  • El inicio es desde una parte central del sistema operativo que debe ejecutarse en todo momento, como el servicio de telefonía que inicia la pantalla de llamada entrante.

Nuevas protecciones y habilitaciones obligatorias

Para mejorar aún más la seguridad, Android introdujo reglas más estrictas que requieren habilitaciones explícitas para las apps que usan PendingIntent y IntentSender para iniciar actividades. Solo se permite un inicio si la app que creó el PendingIntent o la que lo envía habilita el otorgamiento de sus privilegios de inicio en segundo plano.

En la mayoría de los casos, la app que envía el PendingIntent debe ser la que habilite la opción, ya que suele ser la app con la que el usuario interactúa directamente (por ejemplo, presionar un botón).

Los emisores deben habilitar PendingIntent

Cuando tu app tiene como objetivo Android 14 (nivel de API 34) o versiones posteriores, ya no otorga sus privilegios de BAL de forma predeterminada cuando envía un PendingIntent. Si no habilitas la opción de forma explícita, es posible que se bloquee el inicio de la actividad, a menos que el creador del PendingIntent ya haya otorgado sus propios privilegios.

Para garantizar que un inicio se realice correctamente, el emisor debe habilitar el otorgamiento de sus privilegios llamando a ActivityOptions.setPendingIntentBackgroundActivityStartMode() y el modo recomendado es ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (agregado en el SDK 36).

Este es un modo más estricto y seguro. Otorga permiso solo si la app emisora está visible en la pantalla en el momento en que se envía el PendingIntent. Esto garantiza que el inicio de la actividad sea el resultado directo de la interacción de un usuario con tu app.

Tabla de intents pendientes
Figura 1: Flujo de decisiones para los inicios de actividades en segundo plano

Usa ActivityOptions.setPendingIntentBackgroundActivityStartMode() para otorgar privilegios.

// Sender Side
ActivityOptions options = ActivityOptions.makeBasic()
    .setPendingIntentBackgroundActivityStartMode(
        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);

try {
    myPendingIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
    Log.e(TAG, "The PendingIntent was canceled", e);
}
// Sender Side
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
}

try {
    myPendingIntent.send(options.toBundle())
} catch (e: PendingIntent.CanceledException) {
    Log.e(TAG, "The PendingIntent was canceled", e)
}

Los creadores deben habilitar PendingIntent

Cuando tu app tiene como objetivo Android 15 (nivel de API 35) o versiones posteriores, una app que crea un PendingIntent ya no otorga sus privilegios de inicio en segundo plano de forma predeterminada. Para permitir que el emisor use los privilegios de BAL de tu app, debes habilitar la opción de forma explícita.

Usa ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() para otorgar privilegios.

// Creator Side
Intent intent = new Intent(context, MyActivity.class);
ActivityOptions options = ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

PendingIntent pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());
// Creator Side
val intent = Intent(context, MyActivity::class.java)
val options = ActivityOptions.makeBasic().apply {
    pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent,
        PendingIntent.FLAG_IMMUTABLE, options.toBundle())

Cómo iniciar con IntentSender

Las mismas restricciones de BAL también se aplican cuando se inician actividades con un IntentSender. Como se obtiene un IntentSender a través de PendingIntent.getIntentSender, está sujeto a requisitos de habilitación similares.

  • A partir de Android 14 (API 34), el uso de Context.startIntentSender() requiere una habilitación del emisor. También debes proporcionar el paquete ActivityOptions aquí.
ActivityOptions options = ActivityOptions.makeBasic()
        .setPendingIntentBackgroundActivityStartMode(
            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
        flagsValues, extraFlags, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
        flagsValues, extraFlags, options.toBundle())
ActivityOptions options = ActivityOptions.makeBasic()
        .setPendingIntentBackgroundActivityStartMode(
            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);

myIntentSender.sendIntent(context, code, intent, onFinished, handler,
        requiredPermission, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
    pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}

myIntentSender.sendIntent(context, code, intent, onFinished, handler,
        requiredPermission, options.toBundle())

Diagrama de secuencia: Restricciones de BAL

Tabla de intents pendientes
Figura 2: El proceso para iniciar de forma segura una actividad con un PendingIntent

En este diagrama, se ilustra el proceso para iniciar de forma segura una actividad con un PendingIntent. Un inicio exitoso depende de una cadena de privilegios válida en la que al menos una de las apps participantes otorgue sus privilegios y tenga la capacidad de iniciar una actividad en segundo plano.

  1. Creación y delegación (App A: el creador)
    1. La app del creador compila el PendingIntent.
    2. Si se orienta al SDK 35 o versiones posteriores, el creador debe delegar de forma explícita sus privilegios de BAL con setPendingIntentCreatorBackgroundActivityStartMode() si desea que se usen sus privilegios. De forma predeterminada, no se delegan privilegios.
    3. Luego, el PendingIntent se entrega a otra app (App B).
  2. Inicio y contribución (App B: el emisor)
    1. Más adelante, la app del emisor inicia el inicio llamando a PendingIntent.send().
    2. Si se orienta al SDK 34 o versiones posteriores, el emisor debe contribuir de forma explícita sus propios privilegios con setPendingIntentBackgroundActivityStartMode() si desea que se usen sus privilegios. De forma predeterminada, no se delegan privilegios.
  3. Validación de seguridad del sistema Android
    1. El sistema Android intercepta la solicitud de inicio y realiza una verificación de seguridad.
    2. Evalúa dos condiciones:
    3. ¿El creador delegó sus privilegios? ¿La app del creador cumple actualmente con una de las excepciones generales de BAL?
    4. ¿El emisor contribuyó con sus privilegios? ¿La app del emisor cumple actualmente con una de las excepciones generales de BAL?
  4. Resultado
    1. PERMITIDO: Si se cumple al menos una de las dos condiciones del paso 3, se valida la cadena de privilegios. El sistema Android inicia la actividad de destino y el emisor recibe un resultado exitoso.
    2. BLOQUEADO: Si no se cumple ninguna condición, el sistema bloquea el inicio. La app del emisor no recibe un valor de retorno directo ni una excepción que indique la falla. En cambio, el sistema Android registra internamente un "Background activity launch blocked!" mensaje en Logcat, que los desarrolladores deben verificar para la depuración.

Prevención de secuestro de tareas

Para evitar ataques de secuestro en la tarea (como el "sándwich de actividad"), Android 15 introduce nuevas reglas para las apps que se orientan al nivel de API 37 o versiones posteriores.

  • Regla 1: Dentro de una sola tarea, una actividad solo puede iniciarse con otra actividad que pertenezca a la misma aplicación (es decir, que tenga el mismo UID) que la actividad superior actual en la tarea.
  • Regla 2: Solo una actividad dentro de una tarea en primer plano que coincida con el UID de la actividad superior puede crear una tarea nueva o llevar una tarea existente diferente al primer plano.

Habilitación para desarrolladores para protecciones en la tarea

Este comportamiento se puede habilitar a partir del SDK de destino 37. Debes habilitar la opción de forma explícita. Está diseñado para evitar el secuestro en la tarea (o sándwich de actividad), en el que una app maliciosa podría iniciar una actividad en la tarea de tu app para suplantarla y robar datos del usuario.

Cómo habilitar protecciones

Para habilitar ASM en tu aplicación, establece el atributo android:allowCrossUidActivitySwitchFromBelow en false en tu archivo AndroidManifest.xml. Esta es una configuración a nivel de la aplicación que protege todas las actividades de tu app de forma predeterminada.

Cómo crear excepciones para actividades específicas

Si la habilitaste para tu app, pero necesitas permitir que otras apps inicien una actividad específica y confiable, puedes crear una excepción segmentada. Para eximir una sola actividad de esta protección, llama a setAllowCrossUidActivitySwitchFromBelow(true) dentro del método onCreate() de esa actividad. Esto permite que se inicie esa actividad mientras el resto de tu app permanece protegida.

Solución de problemas

Filtra Logcat para encontrar mensajes relevantes con una expresión regular. A menudo, se usa la etiqueta ActivityTaskManager, y filtrar por ActivityTaskManager puede ayudar a aislar los registros.

Cómo comprender los mensajes de registro clave

Blocked Launch (Error): Este mensaje indica que se bloqueó el inicio de una actividad.

Cuando analices los registros, verifica estos campos:

  • realCallingPackage: La app que envió el PendingIntent. Este es el emisor.
  • callingPackage: La app que creó el PendingIntent. Este es el creador.

Modo estricto

A partir de Android 16, el desarrollador de apps puede habilitar el modo estricto para recibir notificaciones cuando se bloquea el inicio de una actividad (o cuando se corre el riesgo de que se bloquee cuando se eleva el SDK de destino de la app).

Código de ejemplo para habilitar desde el principio en el método Application.onCreate() de tu aplicación, actividad o cualquier otro componente de la aplicación:

override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     StrictMode.setVmPolicy(
         StrictMode.VmPolicy.Builder()
         .detectBlockedBackgroundActivityLaunch()
         .penaltyLog()
         .build());
     )
 }