Guía de soluciones de dispositivos exclusivos

Esta guía de soluciones ayuda a los integradores de sistemas y desarrolladores a mejorar su solución dedicada para dispositivos. Sigue nuestras instrucciones prácticas para encontrar soluciones a los comportamientos en dispositivos dedicados. Esta guía de soluciones funciona mejor para los desarrolladores que ya tienen una app dedicada para dispositivos. Si recién comienzas, consulta Descripción general de los dispositivos exclusivos.

Apps de casa personalizadas

Estas recetas son útiles si estás desarrollando una app para reemplazar la pantalla principal y el Selector de Android.

Sé la app de inicio

Puedes configurar tu app como la app de inicio del dispositivo para que se inicie automáticamente cuando se inicie el dispositivo. También puedes habilitar el botón de inicio que lleva la app incluida en la lista de entidades permitidas al primer plano en el modo de tareas bloqueadas.

Todas las apps de inicio controlan la categoría de intent CATEGORY_HOME. Así es como el sistema reconoce una app de inicio. Para establecer una de las actividades de tu app como el controlador de intents preferido, llama a DevicePolicyManager.addPersistentPreferredActivity() como se muestra en el siguiente ejemplo:

Kotlin

// Create an intent filter to specify the Home category.
val filter = IntentFilter(Intent.ACTION_MAIN)
filter.addCategory(Intent.CATEGORY_HOME)
filter.addCategory(Intent.CATEGORY_DEFAULT)

// Set the activity as the preferred option for the device.
val activity = ComponentName(context, KioskModeActivity::class.java)
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager
dpm.addPersistentPreferredActivity(adminName, filter, activity)

Java

// Create an intent filter to specify the Home category.
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);

// Set the activity as the preferred option for the device.
ComponentName activity = new ComponentName(context, KioskModeActivity.class);
DevicePolicyManager dpm =
    (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.addPersistentPreferredActivity(adminName, filter, activity);

De todos modos, debes declarar el filtro de intents en el archivo de manifiesto de tu app, como se muestra en el siguiente fragmento XML:

<activity
        android:name=".KioskModeActivity"
        android:label="@string/kiosk_mode"
        android:launchMode="singleInstance"
        android:excludeFromRecents="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.HOME"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

Por lo general, no es recomendable que la app de selector aparezca en la pantalla Recientes. Sin embargo, no necesitas agregar excludeFromRecents a la declaración de actividad porque el Launcher de Android oculta la actividad que se inició inicialmente cuando el sistema se ejecuta en el modo de tareas bloqueadas.

Mostrar tareas independientes

FLAG_ACTIVITY_NEW_TASK puede ser una marca útil para las apps de tipo selector, ya que cada tarea nueva aparece como un elemento separado en la pantalla Recientes. Para obtener más información sobre las tareas de la pantalla Recientes, lee Pantalla Recientes.

Kioscos públicos

Estas recetas son excelentes para los dispositivos sin supervisión en espacios públicos, pero también pueden ayudar a muchos usuarios de dispositivos dedicados a concentrarse en sus tareas.

Bloquea el dispositivo

Para asegurarte de que los dispositivos se usen para su uso previsto, puedes agregar las restricciones del usuario que se enumeran en la tabla 1.

Tabla 1: Restricciones para los usuarios de dispositivos de kiosco
Restricción de usuarios Descripción
DISALLOW_FACTORY_RESET Impide que el usuario restablezca la configuración predeterminada de fábrica. Los administradores de dispositivos completamente administrados y el usuario principal pueden establecer esta restricción.
DISALLOW_SAFE_BOOT Impide que un usuario del dispositivo inicie el dispositivo en modo seguro, donde el sistema no iniciará automáticamente tu app. Los administradores de dispositivos completamente administrados y el usuario principal pueden establecer esta restricción.
DISALLOW_MOUNT_PHYSICAL_MEDIA Impide que el usuario del dispositivo active cualquier volumen de almacenamiento que pueda conectar al dispositivo. Los administradores de dispositivos completamente administrados y el usuario principal pueden establecer esta restricción.
DISALLOW_ADJUST_VOLUME Silencia el dispositivo y evita que su usuario cambie la configuración de vibración y volumen del sonido. Verifica que el kiosco no necesite audio para la reproducción de contenido multimedia ni las funciones de accesibilidad. Los administradores de dispositivos completamente administrados, el usuario principal, los usuarios secundarios y los perfiles de trabajo pueden establecer esta restricción.
DISALLOW_ADD_USER Impide que el usuario del dispositivo agregue nuevos usuarios, como secundarios o restringidos. El sistema agrega automáticamente esta restricción de usuario a los dispositivos completamente administrados, pero es posible que se haya borrado. Los administradores de dispositivos completamente administrados y el usuario principal pueden establecer esta restricción.

En el siguiente fragmento, se muestra cómo puedes establecer las restricciones:

Kotlin

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
arrayOf(
        UserManager.DISALLOW_FACTORY_RESET,
        UserManager.DISALLOW_SAFE_BOOT,
        UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
        UserManager.DISALLOW_ADJUST_VOLUME,
        UserManager.DISALLOW_ADD_USER).forEach { dpm.addUserRestriction(adminName, it) }

Java

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
String[] restrictions = {
    UserManager.DISALLOW_FACTORY_RESET,
    UserManager.DISALLOW_SAFE_BOOT,
    UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
    UserManager.DISALLOW_ADJUST_VOLUME,
    UserManager.DISALLOW_ADD_USER};

for (String restriction: restrictions) dpm.addUserRestriction(adminName, restriction);

Es posible que quieras quitar estas restricciones cuando la app esté en modo de administrador para que un administrador de TI pueda seguir usando estas funciones para el mantenimiento del dispositivo. Para borrar la restricción, llama a DevicePolicyManager.clearUserRestriction().

Suprimir diálogos de error

En algunos entornos, como las demostraciones para minoristas o las pantallas de información pública, es posible que no quieras mostrar los diálogos de error a los usuarios. En Android 9.0 (nivel de API 28) o versiones posteriores, puedes suprimir los diálogos de error del sistema para las apps con fallas o que no responden si agregas la restricción de usuario DISALLOW_SYSTEM_ERROR_DIALOGS. El sistema reinicia las apps que no responden como si el usuario del dispositivo la cerrara desde el diálogo. En el siguiente ejemplo, se muestra cómo puedes hacerlo:

Kotlin

override fun onEnabled(context: Context, intent: Intent) {
    val dpm = getManager(context)
    val adminName = getWho(context)

    dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS)
}

Java

public void onEnabled(Context context, Intent intent) {
  DevicePolicyManager dpm = getManager(context);
  ComponentName adminName = getWho(context);

  dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS);
}

Si un administrador del usuario principal o secundario establece esta restricción, el sistema elimina los diálogos de error solo para ese usuario. Si un administrador de un dispositivo completamente administrado establece esta restricción, el sistema elimina los diálogos para todos los usuarios.

Cómo mantener encendida la pantalla

Si estás creando un kiosco, puedes detener un dispositivo que se suspenda cuando ejecute la actividad de tu app. Agrega la marca de diseño FLAG_KEEP_SCREEN_ON a la ventana de tu app como se muestra en el siguiente ejemplo:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Keep the screen on and bright while this kiosk activity is running.
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // Keep the screen on and bright while this kiosk activity is running.
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

Verifica que el dispositivo esté conectado a un cargador de CA, USB o inalámbrico. Regístrate para las transmisiones de cambio de batería y usa los valores de BatteryManager a fin de descubrir el estado de carga. Incluso puedes enviar alertas remotas a un administrador de TI si el dispositivo se desconecta. Para obtener instrucciones paso a paso, consulta Cómo supervisar el nivel de batería y el estado de carga.

También puedes establecer el parámetro de configuración global STAY_ON_WHILE_PLUGGED_IN para mantener el dispositivo activo mientras está conectado a una fuente de alimentación. Los administradores de dispositivos completamente administrados, en Android 6.0 (nivel de API 23) o versiones posteriores, pueden llamar a DevicePolicyManager.setGlobalSetting() como se muestra en el siguiente ejemplo:

Kotlin

val pluggedInto = BatteryManager.BATTERY_PLUGGED_AC or
        BatteryManager.BATTERY_PLUGGED_USB or
        BatteryManager.BATTERY_PLUGGED_WIRELESS
dpm.setGlobalSetting(adminName,
        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, pluggedInto.toString())

Java

int pluggedInto = BatteryManager.BATTERY_PLUGGED_AC |
    BatteryManager.BATTERY_PLUGGED_USB |
    BatteryManager.BATTERY_PLUGGED_WIRELESS;
dpm.setGlobalSetting( adminName,
    Settings.Global.STAY_ON_WHILE_PLUGGED_IN, String.valueOf(pluggedInto));

Paquetes de aplicación

Esta sección contiene recetas para instalar de manera eficiente apps en dispositivos exclusivos.

Almacena en caché paquetes de app

Si todos los usuarios de un dispositivo compartido comparten un conjunto común de apps, tiene sentido evitar la descarga de apps siempre que sea posible. Para optimizar el aprovisionamiento de usuarios en dispositivos compartidos con un conjunto fijo de usuarios, como dispositivos para trabajadores de turno, en Android 9.0 (nivel de API 28) o versiones posteriores, puedes almacenar en caché los paquetes de apps (APK) que son necesarios para las sesiones multiusuario.

La instalación de un APK almacenado en caché (que ya está instalado en el dispositivo) se realiza en dos etapas:

  1. El componente de administrador de un dispositivo completamente administrado (o un delegado, consulta a continuación) establece la lista de APK que se conservarán en el dispositivo.
  2. Los componentes administradores de los usuarios secundarios afiliados (o sus delegados) pueden instalar el APK almacenado en caché en nombre del usuario. Los administradores del dispositivo completamente administrado, el usuario principal o un perfil de trabajo afiliado (o sus delegados) también pueden instalar la app almacenada en caché si es necesario.

Para configurar la lista de APKs que se conservarán en el dispositivo, el administrador llama a DevicePolicyManager.setKeepUninstalledPackages(). Este método no verifica si el APK está instalado en el dispositivo. Es útil si quieres instalar una app justo antes de que la necesites para un usuario. Para obtener una lista de los paquetes configurados previamente, puedes llamar a DevicePolicyManager.getKeepUninstalledPackages(). Después de llamar a setKeepUninstalledPackages() con los cambios, o cuando se borra un usuario secundario, el sistema borra los APK almacenados en caché que ya no son necesarios.

Para instalar un APK almacenado en caché, llama a DevicePolicyManager.installExistingPackage(). Este método solo puede instalar una app que el sistema ya haya almacenado en caché; la solución de tu dispositivo dedicado (o el usuario de un dispositivo) primero debe instalar la app en el dispositivo para que puedas llamar a este método.

En el siguiente ejemplo, se muestra cómo podrías usar estas llamadas a la API en el administrador de un dispositivo completamente administrado y un usuario secundario:

Kotlin

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
val cachedAppPackageName = "com.example.android.myapp"
dpm.setKeepUninstalledPackages(adminName, listOf(cachedAppPackageName))

// ...

// The admin of a secondary user installs the app.
val success = dpm.installExistingPackage(adminName, cachedAppPackageName)

Java

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
String cachedAppPackageName = "com.example.android.myapp";
List<String> packages = new ArrayList<String>();
packages.add(cachedAppPackageName);
dpm.setKeepUninstalledPackages(adminName, packages);

// ...

// The admin of a secondary user installs the app.
boolean success = dpm.installExistingPackage(adminName, cachedAppPackageName);

Delegar apps

Puedes delegar otra app para que administre su almacenamiento en caché. Puedes hacer esto para separar las funciones de tu solución, o bien ofrecer la posibilidad de que los administradores de TI usen sus propias apps. La app delegada obtiene los mismos permisos que el componente de administración. Por ejemplo, un delegado de la app del administrador de un usuario secundario puede llamar a installExistingPackage(), pero no a setKeepUninstalledPackages().

Para hacer un delegado, llama a DevicePolicyManager.setDelegatedScopes() y, luego, incluye DELEGATION_KEEP_UNINSTALLED_PACKAGES en el argumento de alcances. En el siguiente ejemplo, se muestra cómo puedes convertir otra app en delegada:

Kotlin

var delegatePackageName = "com.example.tools.kept_app_assist"

// Check that the package is installed before delegating.
try {
    context.packageManager.getPackageInfo(delegatePackageName, 0)
    dpm.setDelegatedScopes(
            adminName,
            delegatePackageName,
            listOf(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES))
} catch (e: PackageManager.NameNotFoundException) {
    // The delegate app isn't installed. Send a report to the IT admin ...
}

Java

String delegatePackageName = "com.example.tools.kept_app_assist";

// Check that the package is installed before delegating.
try {
  context.getPackageManager().getPackageInfo(delegatePackageName, 0);
  dpm.setDelegatedScopes(
      adminName,
      delegatePackageName,
      Arrays.asList(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES));
} catch (PackageManager.NameNotFoundException e) {
  // The delegate app isn't installed. Send a report to the IT admin ...
}

Si todo sale bien, la app delegada recibe la transmisión ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED y se convierte en delegada. La app puede llamar a los métodos de esta guía como si fuera el propietario del dispositivo o del perfil. Cuando se llama a los métodos DevicePolicyManager, el delegado pasa null para el argumento del componente de administrador.

Instala paquetes de app

A veces, es útil instalar una app personalizada almacenada en caché local en un dispositivo dedicado. Por ejemplo, los dispositivos dedicados se implementan con frecuencia en entornos con ancho de banda limitado o áreas sin conexión a Internet. Tu solución de dispositivo dedicado debe tener en cuenta el ancho de banda de tus clientes. Tu app puede iniciar la instalación de otro paquete de app (APK) con las clases PackageInstaller.

Si bien cualquier app puede instalar APK, los administradores de dispositivos completamente administrados pueden instalar (o desinstalar) paquetes sin interacción del usuario. El administrador puede gestionar el dispositivo, un usuario secundario afiliado o un perfil de trabajo afiliado. Después de finalizar la instalación, el sistema publica una notificación que ven todos los usuarios del dispositivo. La notificación informa a los usuarios del dispositivo que su administrador instaló (o actualizó) la app.

Tabla 2: Versiones de Android que admiten la instalación de paquetes sin interacción del usuario
Versión de Android Componente de administrador para instalar y desinstalar
Android 9.0 (nivel de API 28) o una versión posterior Usuarios secundarios afiliados y perfiles de trabajo, ambos en dispositivos completamente administrados
Android 6.0 (nivel de API 23) o una versión posterior Dispositivos completamente administrados

La forma en que distribuyas una o más copias del APK a dispositivos dedicados dependerá de qué tan remotos estén los dispositivos y, posiblemente, de qué tan alejados estén entre sí. Tu solución debe seguir las prácticas recomendadas de seguridad antes de instalar APKs en dispositivos dedicados.

Puedes usar PackageInstaller.Session a fin de crear una sesión que ponga en cola uno o más APK para la instalación. En el siguiente ejemplo, recibimos comentarios sobre el estado en nuestra actividad (modo singleTop), pero puedes usar un servicio o un receptor de emisión:

Kotlin

// First, create a package installer session.
val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(
        PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
// The I/O streams can't be open when installation begins.
session.openWrite("apk", 0, -1).use { output ->
    getContext().resources.openRawResource(R.raw.app).use { input ->
        input.copyTo(output, 2048)
    }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
val intent = Intent(context, activity.javaClass)
intent.action = "com.android.example.APK_INSTALLATION_ACTION"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val statusReceiver = pendingIntent.intentSender

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver)

Java

// First, create a package installer session.
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
try (
    // These I/O streams can't be open when installation begins.
    OutputStream output = session.openWrite("apk", 0, -1);
    InputStream input = getContext().getResources().openRawResource(R.raw.app);
) {
  byte[] buffer = new byte[2048];
  int n;
  while ((n = input.read(buffer)) >= 0) {
    output.write(buffer, 0, n);
  }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
Intent intent = new Intent(context, getActivity().getClass());
intent.setAction("com.android.example.APK_INSTALLATION_ACTION");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.getIntentSender();

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver);

La sesión envía comentarios sobre el estado de la instalación mediante intents. Verifica el campo EXTRA_STATUS de cada intent para obtener el estado. Recuerda que los administradores no reciben la actualización del estado STATUS_PENDING_USER_ACTION porque el usuario del dispositivo no necesita aprobar la instalación.

Para desinstalar apps, puedes llamar a PackageInstaller.uninstall. Los administradores de dispositivos, usuarios y perfiles de trabajo completamente administrados pueden desinstalar paquetes sin interacción del usuario con versiones de Android compatibles (consulta la Tabla 2).

Bloquear las actualizaciones del sistema

Los dispositivos Android reciben actualizaciones inalámbricas (OTA) del software del sistema y de la aplicación. Para inmovilizar la versión del SO durante períodos críticos, como días festivos u otras horas de mayor actividad, los dispositivos dedicados pueden suspender las actualizaciones inalámbricas del sistema por hasta 90 días. Para obtener más información, consulta Cómo administrar actualizaciones del sistema.

Remote Config

Las configuraciones administradas de Android permiten que los administradores de TI configuren tu app de forma remota. Te recomendamos exponer parámetros de configuración, como listas de entidades permitidas, hosts de red o URLs de contenido, para que tu app sea más útil para los administradores de TI.

Si tu app expone su configuración, recuerda incluirla en tu documentación. Si quieres obtener más información para exponer la configuración de tu app y reaccionar a los cambios de configuración, consulta Cómo establecer configuraciones administradas.

Configuración de desarrollo

Cuando desarrollas una solución para dispositivos dedicados, a veces resulta útil configurar tu app como administrador de un dispositivo completamente administrado sin restablecer la configuración de fábrica. Para configurar el administrador de un dispositivo completamente administrado, sigue estos pasos:

  1. Compila y, luego, instala tu app de controlador de política de dispositivo (DPC) en el dispositivo.
  2. Verifica que no haya cuentas en el dispositivo.
  3. Ejecuta el siguiente comando en el shell de Android Debug Bridge (adb). Debes reemplazar com.example.dpc/.MyDeviceAdminReceiver en el ejemplo por el nombre del componente de administrador de tu app:

    adb shell dpm set-device-owner com.example.dpc/.MyDeviceAdminReceiver

Para ayudar a los clientes a implementar tu solución, deberás consultar otros métodos de inscripción. Recomendamos la inscripción con código QR para dispositivos dedicados.

Recursos adicionales

Para obtener más información sobre los dispositivos exclusivos, consulta los siguientes documentos: