Pantalla de Recientes

La pantalla Recientes, también llamada pantalla de información general, lista de tareas recientes o apps recientes, es una IU a nivel del sistema en la que se enumeran las actividades y tareas a las que se accedió recientemente. El usuario puede navegar por la lista, seleccionar una tarea para reanudarla o quitar una tarea de la lista deslizándola hacia afuera.

La pantalla Recientes usa un modelo centrado en documentos, que se introdujo en Android 5.0 (nivel de API 21), en el que varias instancias de la misma actividad que contiene diferentes documentos pueden aparecer como tareas en la pantalla Recientes. Por ejemplo, Google Drive podría tener una tarea para cada uno de los documentos de Google. Cada documento aparece como una tarea en la pantalla Recientes:

En la pantalla Recientes, se muestran dos documentos de Google Drive y cada uno se representa como una tarea independiente.

Otro ejemplo común es cuando el usuario usa su navegador y presiona Compartir > Gmail. Aparece la pantalla Redactarde la app de Gmail. Si presionas el botón Recientes en ese momento, verás que Chrome y Gmail se ejecutan como tareas independientes:

La pantalla Recientes muestra Chrome y Gmail ejecutándose como tareas separadas.

Normalmente, permites que el sistema defina cómo se representan tus tareas y actividades en la pantalla Recientes. No es necesario que modifiques este comportamiento. Sin embargo, tu app puede determinar cómo y cuándo aparecen las actividades en la pantalla Recientes.

La clase ActivityManager.AppTask te permite administrar tareas, y las marcas de actividad de la clase Intent te permiten especificar cuándo se agrega o se quita una actividad de la pantalla Recientes. Además, los atributos <activity> te permiten configurar el comportamiento en el manifiesto.

Cómo agregar tareas a la pantalla Recientes

El uso de las marcas de la clase Intent para agregar una tarea te brinda un mayor control sobre cómo y cuándo se abre o se vuelve a abrir un documento en la pantalla Recientes. Cuando usas los atributos <activity>, puedes elegir entre abrir siempre el documento en una nueva tarea o reutilizar una tarea existente para el documento.

Cómo usar la marca de intent para agregar una tarea

Cuando creas un nuevo documento para tu actividad, puedes llamar al método startActivity() y pasarlo al intent que inicia la actividad. Si quieres insertar una interrupción lógica para que el sistema trate tu actividad como una nueva tarea en la pantalla Recientes, pasa la marca FLAG_ACTIVITY_NEW_DOCUMENT en el método addFlags() del Intent que inicia la actividad.

Si estableces la marca FLAG_ACTIVITY_MULTIPLE_TASK cuando creas el documento nuevo, el sistema siempre creará una nueva tarea con la actividad objetivo como raíz. Este parámetro de configuración permite que el mismo documento se abra en más de una tarea. En el siguiente código, se muestra cómo lo hace la actividad principal y cómo inicia la nueva actividad desde tu elemento componible:

private fun newDocumentIntent(context: Context): Intent =
    Intent(context, NewDocumentActivity::class.java).apply {
        addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)
        putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, documentCounter++)
    }

@Composable
fun CreateDocumentButton() {
    val context = LocalContext.current
    Button(
        onClick = {
            val intent = newDocumentIntent(context)
            // Add FLAG_ACTIVITY_MULTIPLE_TASK if needed based on state
            context.startActivity(intent)
        }
    ) {
        Text("Create New Document")
    }
}

Cuando la actividad principal inicia una nueva actividad, el sistema busca entre las tareas existentes una cuyo intent coincida con el nombre del componente del intent y los datos del intent de la actividad. Si no se encuentra la tarea o si el intent contiene la marca FLAG_ACTIVITY_MULTIPLE_TASK, se creará una nueva tarea con la actividad como su raíz.

Si el sistema encuentra una tarea cuyo intent coincide con el nombre del componente del intent y los datos del intent, trae esa tarea al primer plano y pasa el nuevo intent a onNewIntent(). La nueva actividad obtiene el intent y crea un nuevo documento en la pantalla Recientes, como se muestra en el siguiente ejemplo:

class DocumentCentricActivity : ComponentActivity() {
    private var documentState by mutableStateOf(
        DocumentState(
            count = 0,
            textResId = R.string.hello_new_document_counter
        )
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val initialCount = intent.getIntExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0)

        documentState = documentState.copy(count = initialCount)

        setContent {
            MaterialTheme {
                DocumentScreen(
                    count = documentState.count,
                    textResId = documentState.textResId
                )
            }
        }
    }

    override fun onNewIntent(newIntent: Intent) {
        super.onNewIntent(newIntent)
        // If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this Activity is reused.
        documentState = documentState.copy(
            textResId = R.string.reusing_document_counter
        )
    }

    data class DocumentState(val count: Int, @StringRes val textResId: Int)

    companion object {
        const val KEY_EXTRA_NEW_DOCUMENT_COUNTER = "KEY_EXTRA_NEW_DOCUMENT_COUNTER"
    }
}

@Composable
fun DocumentScreen(count: Int, @StringRes textResId: Int) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center
    ) {
        // UI reacts to whichever string resource ID was passed down
        Text(text = stringResource(id = textResId))
        Spacer(modifier = Modifier.height(8.dp))
        Text(text = "Counter: $count")
    }
}

En el código anterior, la actividad controla el enrutamiento a nivel del SO (onCreate y onNewIntent), mientras que la función @Composable solo es responsable de renderizar la IU según el estado proporcionado.

Cómo usar el atributo de la actividad para agregar una tarea

Una actividad también puede especificar en su manifiesto que siempre se iniciará en una nueva tarea con el atributo <activity> android:documentLaunchMode. Este atributo tiene cuatro valores que producen los siguientes efectos cuando el usuario abre un documento con la aplicación:

intoExisting
La actividad reutiliza una tarea existente para el documento, Esto equivale a configurar la marca FLAG_ACTIVITY_NEW_DOCUMENT sin establecer la marca FLAG_ACTIVITY_MULTIPLE_TASK, como se describe en la sección Cómo usar la marca del intent para agregar una tarea.
always
La actividad crea una nueva tarea para el documento, incluso si este ya está abierto. Usar este valor equivale a configurar las marcas FLAG_ACTIVITY_NEW_DOCUMENT y FLAG_ACTIVITY_MULTIPLE_TASK.
none
La actividad no crea una nueva tarea para el documento. La pantalla Recientes trata la actividad como lo haría de manera predeterminada. Muestra una sola tarea para la app, que se reanuda desde la última actividad que el usuario invocó.
never
La actividad no crea una nueva tarea para el documento. Si se establece este valor, se anula el comportamiento de las marcas FLAG_ACTIVITY_NEW_DOCUMENT y FLAG_ACTIVITY_MULTIPLE_TASK. Si cualquiera de estos se establece en el intent, y en la pantalla Recientes se muestra una sola tarea para la app, esta se reanuda desde la última actividad que el usuario invocó.

Cómo quitar tareas

De forma predeterminada, la tarea de un documento sale automáticamente de la pantalla Recientes cuando finaliza la actividad. Puedes anular este comportamiento con la clase ActivityManager.AppTask, con una marca Intent o con un atributo <activity>.

Para excluir completamente una tarea de la pantalla Recientes, configura el atributo <activity> android:excludeFromRecents en true.

Para configurar la cantidad máxima de tareas que tu app puede incluir en la pantalla Recientes, establece el atributo <activity> android:maxRecents en un valor de número entero. Cuando se alcanza la cantidad máxima de tareas, la tarea que se usó con mayor anterioridad desaparece de la pantalla de Recientes. El valor predeterminado es 16 y el valor máximo es 50 (25 en dispositivos con poca memoria). Los valores inferiores a 1 no son válidos.

Cómo usar la clase AppTask para quitar tareas

En la actividad que crea una nueva tarea en la pantalla Recientes, puedes especificar cuándo quitar la tarea y finalizar todas las actividades asociadas con ella llamando al método finishAndRemoveTask():

@Composable
fun RemoveTaskButton() {
    val context = LocalContext.current
    Button(
        onClick = {
            // It is good practice to remove a document from the overview stack if not needed anymore.
            (context as? Activity)?.finishAndRemoveTask()
        }
    ) {
        Text("Remove from Recents")
    }
}

Cómo retener las tareas finalizadas

Si quieres retener una tarea en la pantalla Recientes, incluso si la actividad ya finalizó, pasa la marca FLAG_ACTIVITY_RETAIN_IN_RECENTS en el método addFlags() del intent que inicia la actividad.

private fun newDocumentIntent() =
        Intent(this, NewDocumentActivity::class.java).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
                    android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)
            putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, getAndIncrement())
        }

Para lograr el mismo efecto, configura el atributo <activity> android:autoRemoveFromRecents en false. El valor predeterminado es true para las actividades de documentos y false para las actividades comunes. El uso de este atributo anula la marca FLAG_ACTIVITY_RETAIN_IN_RECENTS.

Habilita el uso compartido de URLs recientes (solo para Pixel)

En dispositivos Pixel que ejecutan Android 12 o versiones posteriores, los usuarios pueden compartir vínculos al contenido web que vieron hace poco directamente desde la pantalla Recientes. Después de visitar el contenido de una app, el usuario puede deslizar el dedo para ir a la pantalla Recientes, buscar la app en la que vio el contenido y, luego, presionar el botón de vínculo para copiar o compartir la URL.

La pantalla Recientes con un vínculo para compartir contenido web visto recientemente.

Cualquier app puede habilitar la vinculación de Recientes para los usuarios proporcionando una IU web y anulando onProvideAssistContent(), como se muestra en el siguiente ejemplo:

class MainActivity : ComponentActivity() {

    // Track the current URL as state so the UI can update it during navigation
    private var currentWebUri by mutableStateOf("https://example.com/home")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            AppTheme {
                // Pass a lambda to your Compose UI so it can update the URL state
                // as the user navigates through your app.
                MainScreen(
                    onPageChanged = { newUrl -> currentWebUri = newUrl }
                )
            }
        }
    }

    override fun onProvideAssistContent(outContent: AssistContent) {
        super.onProvideAssistContent(outContent)

        // The system calls this when the user enters the Recents screen.
        // Provide the active URI tracked by the Compose state.
        outContent.webUri = Uri.parse(currentWebUri)
    }
}