API de clúster de instrumentos

Usa la API de Instrument Cluster (una API de Android) para mostrar apps de navegación, como Google Maps, en una pantalla secundaria de un vehículo, por ejemplo, detrás del volante en el panel de instrumentos. En esta página, se describe cómo crear un servicio para controlar esa pantalla secundaria y cómo integrar el servicio con CarService para que las apps de navegación puedan mostrar una interfaz de usuario.

Terminología

En esta página, se usan los siguientes términos.

CarInstrumentClusterManager
Es una instancia de CarManager que permite que las apps externas inicien una actividad en el clúster de instrumentos y reciban devoluciones de llamada cuando el clúster de instrumentos esté listo para mostrar actividades.
CarManager
Es la clase base de todos los administradores que usan las apps externas para interactuar con los servicios específicos del vehículo que implementa CarService.
CarService
Es un servicio de la plataforma de Android que proporciona comunicación entre apps externas (incluido Google Maps) y funciones específicas del vehículo, como el acceso al clúster de instrumentos.
Destino
Es el destino final al que navegará el vehículo.
Hora estimada de llegada (ETA)
Es la hora estimada de llegada a un destino.
Consola central (HU)
Es la unidad de procesamiento principal integrada en un vehículo. La HU ejecuta todo el código de Android y está conectada a la pantalla central del vehículo.
Clúster de instrumentos
Es la pantalla secundaria ubicada detrás del volante y entre los instrumentos del vehículo. Puede ser una unidad de procesamiento independiente conectada a la HU a través de la red interna del vehículo (bus CAN) o una pantalla secundaria conectada a la HU.
InstrumentClusterRenderingService
Es la clase base del servicio que se usa para interactuar con la pantalla del clúster de instrumentos. Los OEMs deben proporcionar una extensión de esta clase que interactúe con el hardware específico del OEM.
aplicación KitchenSink
Es una app de prueba incluida en Android Automotive.
Ruta
Es una ruta específica por la que navega un vehículo para llegar a un destino.
Servicio singleton
Es un servicio de Android con el atributo android:singleUser. En cualquier momento, se ejecuta como máximo una instancia del servicio en el sistema Android.

Requisitos previos

Antes de continuar, asegúrate de tener los siguientes elementos:

  • Entorno de desarrollo de Android. Para configurar el entorno de desarrollo de Android, consulta Requisitos de compilación.
  • Descarga el código fuente de Android. Obtén la versión más reciente de el código fuente de Android de la rama pi-car-release (o posterior) en https://android.googlesource.com.
  • Consola central (HU). Un dispositivo Android capaz de ejecutar Android 9 (o versiones posteriores). Este dispositivo debe tener su propia pantalla y ser capaz de actualizarla con compilaciones nuevas de Android.
  • El clúster de instrumentos es uno de los siguientes:
    • Pantalla secundaria física conectada a la HU. Si el hardware y el kernel del dispositivo admiten la administración de varias pantallas.
    • Unidad independiente. Cualquier unidad de procesamiento conectada a la HU a través de una conexión de red, capaz de recibir y mostrar una transmisión de video en su propia pantalla.
    • Pantalla emulada. Durante el desarrollo, puedes usar uno de estos entornos emulados:
      • Pantallas secundarias simuladas. Para habilitar una pantalla secundaria simulada en cualquier distribución de Android AOSP, ve a la configuración de Opciones para desarrolladores en la app del sistema Configuración y, luego, selecciona Simular pantallas secundarias. Esta configuración equivale a conectar una pantalla secundaria física, con la limitación de que esta pantalla se superpone a la pantalla principal.
      • Clúster de instrumentos emulado. El emulador de Android incluido en AAOS proporciona una opción para mostrar un clúster de instrumentos con ClusterRenderingService.

Arquitectura de la integración

Componentes de integración

Cualquier integración de la API de Instrument Cluster consta de estos tres componentes:

  • CarService
  • Apps de navegación
  • Servicio de clúster de instrumentos del OEM

Componentes de integración

CarService

CarService media entre las apps de navegación y el vehículo, lo que garantiza que solo una app de navegación esté activa en un momento determinado y que solo las apps con el permiso android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL puedan enviar datos al vehículo.

CarService inicia todos los servicios específicos del vehículo y proporciona acceso a ellos a través de una serie de administradores. Para interactuar con los servicios, las apps que se ejecutan en el vehículo pueden acceder a estos administradores.

Para la implementación del clúster de instrumentos, los OEMs automotrices deben crear una implementación personalizada de InstrumentClusterRendererService y actualizar el ClusterRenderingService.

Cuando se renderiza un clúster de instrumentos, durante el proceso de arranque, el CarService lee la clave InstrumentClusterRendererService del ClusterRenderingService para ubicar una implementación de InstrumentClusterService. En AOSP, esta entrada apunta al servicio de renderización de la implementación de clúster de muestra de la API de Navigation State:

<string name="instrumentClusterRendererService">
android.car.cluster/.ClusterRenderingService
</string>

El servicio al que se hace referencia en esta entrada se inicializa y se vincula a CarService. Cuando las apps de navegación, como Google Maps, solicitan un CarInstrumentClusterManager, CarService proporciona un administrador que actualiza el estado del clúster de instrumentos desde el InstrumentClusterRenderingService vinculado. (En este caso, vinculado se refiere a los servicios de Android).

Servicio de clúster de instrumentos

Los OEMs deben crear un paquete de Android (APK) que contenga una subclase de ClusterRenderingService.

Esta clase tiene dos propósitos:

  • Proporciona una interfaz de Android y el dispositivo de renderización del clúster de instrumentos (el propósito de esta página).
  • Recibe y renderiza actualizaciones de estado de navegación, como instrucciones de navegación paso a paso.

Para el primer propósito, las implementaciones de InstrumentClusterRendererService del OEM deben inicializar la pantalla secundaria que se usa para renderizar información en las pantallas de la cabina del vehículo y comunicar esta información a CarService llamando a los métodos InstrumentClusterRendererService.setClusterActivityOptions() y InstrumentClusterRendererService.setClusterActivityState().

Para la segunda función, el servicio de clúster de instrumentos debe proporcionar una implementación de la interfaz ClusterRenderingService que reciba eventos de actualización de estado de navegación, que se codifican como un eventType y datos de eventos codificados en un paquete.

Secuencia de integración

En el siguiente diagrama, se ilustra la implementación de un estado de navegación que renderiza actualizaciones:

Secuencia de integración

En esta ilustración, los colores indican lo siguiente:

  • Amarillo. CarService y CarNavigationStatusManager proporcionados por la plataforma de Android. Para obtener más información, consulta Car y CAR_NAVIGATION_SERVICE.
  • Cian. InstrumentClusterRendererService implementado por el OEM.
  • Morado. La app de navegación implementada por Google y desarrolladores externos.
  • Verde. CarAppFocusManager. Para obtener más información, consulta Cómo usar la API de CarAppFocusManager más abajo y CarAppFocusManager.

El flujo de información del estado de navegación sigue esta secuencia:

  1. CarService inicializa InstrumentClusterRenderingService.
  2. Durante la inicialización, el InstrumentClusterRenderingService actualiza CarService con:
    1. Propiedades de la pantalla del clúster de instrumentos, como límites no oscuros (consulta más detalles sobre los límites no oscuros más adelante).
    2. Opciones de actividad necesarias para iniciar actividades dentro de la pantalla del clúster de instrumentos. Para obtener más información, consulta ActivityOptions.
  3. Una app de navegación (como Google Maps para Android Automotive o cualquier app de mapas con los permisos necesarios):
    1. Obtiene un CarAppFocusManager con la clase Car de car-lib.
    2. Antes de que comiencen las instrucciones paso a paso, llama a CarAppFocusManager.requestFocus() para pasar CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION como el appType parámetro.
  4. CarAppFocusManager comunica esta solicitud a CarService. Si se otorga, CarService inspecciona el paquete de la app de navegación y ubica una actividad marcada con la categoría android.car.cluster.NAVIGATION.
  5. Si se encuentra, la app de navegación usa el ActivityOptions informado por el InstrumentClusterRenderingService para iniciar la actividad e incluye las propiedades de la pantalla del clúster de instrumentos como extras en el intent.

Integra la API

La implementación de InstrumentClusterRenderingService debe hacer lo siguiente:

  • Se debe designar como un servicio singleton agregando el siguiente valor a AndroidManifest.xml. Esto es necesario para garantizar que se ejecute una sola copia del servicio de clúster de instrumentos, incluso durante la inicialización y el cambio de usuario:
    android:singleUser="true"
  • Debe tener el permiso del sistema BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE. Esto garantiza que `CarService` solo vincule el servicio de renderización del clúster de instrumentos incluido como parte de la imagen del sistema Android:CarService
    <uses-permission android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"/>
    

Implementa InstrumentClusterRenderingService

Para compilar el servicio, haz lo siguiente:

  1. Escribe una clase que se extienda desde ClusterRenderingService y, luego, agrega una entrada correspondiente a tu archivo AndroidManifest.xml. Esta clase controla la pantalla del clúster de instrumentos y puede renderizar datos de la API de Navigation State (opcionalmente).
  2. Durante onCreate(), usa este servicio para inicializar la comunicación con el hardware de renderización. Estas son algunas opciones:
    • Determina la pantalla secundaria que se usará para el clúster de instrumentos.
    • Crea una pantalla virtual para que la app del clúster de instrumentos renderice y transmita la imagen renderizada a una unidad externa (con un formato de transmisión de video, como H.264).
  3. Cuando la pantalla indicada anteriormente esté lista, este servicio debe llamar a InstrumentClusterRenderingService#setClusterActivityLaunchOptions() para definir el ActivityOptions exacto que se debe usar para mostrar una actividad en el clúster de instrumentos. Usa estos parámetros:
    • category. ClusterRenderingService.
    • ActivityOptions. Una instancia de ActivityOptions que se puede usar para iniciar una actividad en el clúster de instrumentos. Por ejemplo, desde la implementación de clúster de instrumentos de muestra en AOSP:
      getService().setClusterActivityLaunchOptions(
        CATEGORY_NAVIGATION,
        ActivityOptions.makeBasic()
            .setLaunchDisplayId(displayId));
  4. Cuando el clúster de instrumentos esté listo para mostrar actividades, este servicio debe invocar InstrumentClusterRenderingService#setClusterActivityState(). Usa estos parámetros:
    • category ClusterRenderingService
    • state Bundle generado con ClusterRenderingService. Asegúrate de proporcionar estos datos:
      • visible Especifica el clúster de instrumentos como visible y listo para mostrar contenido.
      • unobscuredBounds Un rectángulo que define el área dentro de la pantalla del clúster de instrumentos en la que es seguro mostrar contenido. Por ejemplo, áreas cubiertas por diales y medidores.
  5. Anula el método Service#dump() y muestra información de estado útil para la depuración (consulta dumpsys para obtener más información).

Ejemplo de implementación de InstrumentClusterRenderingService

En el siguiente ejemplo, se describe una implementación de InstrumentClusterRenderingService, que crea un VirtualDisplay para presentar el contenido del clúster de instrumentos en una pantalla física remota.

Como alternativa, este código podría pasar el displayId de una pantalla secundaria física conectada a la HU, si se sabe que hay una disponible.

/**
* Sample {@link InstrumentClusterRenderingService} implementation
*/
public class SampleClusterServiceImpl extends InstrumentClusterRenderingService {
   // Used to retrieve or create displays
   private final DisplayManager mDisplayManager;
   // Unique identifier for the display to be used for instrument
   // cluster
   private final String mUniqueId = UUID.randomUUID().toString();
   // Format of the instrument cluster display
   private static final int DISPLAY_WIDTH = 1280;
   private static final int DISPLAY_HEIGHT = 720;
   private static final int DISPLAY_DPI = 320;
   // Area not covered by instruments
   private static final int DISPLAY_UNOBSCURED_LEFT = 40;
   private static final int DISPLAY_UNOBSCURED_TOP = 0;
   private static final int DISPLAY_UNOBSCURED_RIGHT = 1200;
   private static final int DISPLAY_UNOBSCURED_BOTTOM = 680;
   @Override
   public void onCreate() {
      super.onCreate();
      // Create a virtual display to render instrument cluster activities on
      mDisplayManager = getSystemService(DisplayManager.class);
      VirtualDisplay display = mDisplayManager.createVirtualDisplay(
          mUniqueId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_DPI, null,
          0 /* flags */, null, null);
      // Do any additional initialization (e.g.: start a video stream
      // based on this virtual display to present activities on a remote
      // display).
      onDisplayReady(display.getDisplay());
}
private void onDisplayReady(Display display) {
    // Report activity options that should be used to launch activities on
    // the instrument cluster.
    String category = CarInstrumentClusterManager.CATEGORY_NAVIGATION;
    ActionOptions options = ActivityOptions.makeBasic()
        .setLaunchDisplayId(display.getDisplayId());
    setClusterActivityOptions(category, options);
    // Report instrument cluster state.
    Rect unobscuredBounds = new Rect(DISPLAY_UNOBSCURED_LEFT,
        DISPLAY_UNOBSCURED_TOP, DISPLAY_UNOBSCURED_RIGHT,
        DISPLAY_UNOBSCURED_BOTTOM);
    boolean visible = true;
    ClusterActivityState state = ClusterActivityState.create(visible,
       unobscuredBounds);
    setClusterActivityState(category, options);
  }
}

Usa la API de CarAppFocusManager

La API de CarAppFocusManager proporciona un método llamado getAppTypeOwner(), que permite que el servicio de clúster escrito por los OEMs sepa qué app de navegación tiene el enfoque de navegación en un momento determinado. Los OEMs pueden usar el método CarAppFocusManager#addFocusListener() existente y luego usar getAppTypeOwner() para saber qué app tiene el enfoque. Con esta información, los OEMs pueden hacer lo siguiente:

  • Cambiar la actividad que se muestra en el clúster a la actividad del clúster que proporciona la app de navegación que tiene el enfoque.
  • Detectar si la app de navegación enfocada tiene una actividad de clúster o no. Si la app de navegación enfocada no tiene una actividad de clúster (o si esa actividad está inhabilitada), los OEMs pueden enviar esta señal al DIM del vehículo para que se omita por completo la faceta de navegación del clúster.

Usa CarAppFocusManager para establecer y escuchar el enfoque actual de la app, como la navegación activa o un comando por voz. Por lo general, solo una instancia de esa app se ejecuta de forma activa (o enfocada) en el sistema.

Usa el método CarAppFocusManager#addFocusListener(..) para escuchar los cambios de enfoque de la app:

import android.car.CarAppFocusManager;

...

Car car = Car.createCar(this);
mAppFocusManager = (CarAppFocusManager)car.getCarManager(Car.APP_FOCUS_SERVICE);
mAppFocusManager.addFocusListener(this, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);

...

public void onAppFocusChanged(int appType, boolean active) {
    // Use the CarAppFocusManager#getAppTypeOwner(appType) method call
    // to retrieve a list of active package names
}

Usa el método CarAppFocusManager#getAppTypeOwner(..) para recuperar los nombres de paquete del propietario actual de un tipo de app determinado que está enfocado. Este método puede mostrar más de un nombre de paquete si el propietario actual usa la función android:sharedUserId.

import android.car.CarAppFocusManager;

...

Car car = Car.createCar(this);
mAppFocusManager = (CarAppFocusManager)car.getCarManager(Car.APP_FOCUS_SERVICE);
List<String> focusOwnerPackageNames = mAppFocusManager.getAppTypeOwner(
              CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);

if (focusOwnerPackageNames == null || focusOwnerPackageNames.isEmpty()) {
        // No Navigation app has focus
        // OEM may choose to show their default cluster view
} else {
       // focusOwnerPackageNames
       // Use the PackageManager to retrieve the cluster activity for the package(s)
       // returned in focusOwnerPackageNames
}

...

Identifica apps de plantillas

Para las apps de navegación basadas en plantillas que usan la biblioteca de apps para vehículos, CarAppFocusManager#getAppTypeOwner() muestra el nombre de paquete del host (por ejemplo, com.google.android.apps.automotive.templates.host) porque el host tiene el enfoque del sistema en nombre de la app cliente.

Para identificar la app cliente de navegación, los OEMs pueden extraer el nombre del paquete del paquete de estado de navegación enviado con CarNavigationStatusManager. El nombre del paquete se almacena con la clave active_app_package_name en el paquete que recibe NavigationRenderer#onNavigationStateChanged(Bundle):

// In your NavigationRenderer implementation
@Override
public void onNavigationStateChanged(Bundle bundle) {
    if (bundle.containsKey("active_app_package_name")) {
        String activeAppPackage = bundle.getString("active_app_package_name");
        // Use the package name to identify the navigating app (e.g., com.waze)
    }
}

Apéndice: Usa la app de ejemplo

AOSP proporciona una app de ejemplo que implementa la API de Navigation State.

Para ejecutar esta app de ejemplo, haz lo siguiente:

  1. Compila y actualiza Android Auto en una HU compatible. Usa las instrucciones de compilación y actualización de Android específicas para tu dispositivo. Para obtener instrucciones, consulta Usa placas de referencia.
  2. Conecta una pantalla secundaria física a la HU (si es compatible) o activa la HU secundaria virtual:
    1. Selecciona Modo para desarrolladores en la app de Configuración.
    2. Ve a Configuración > Sistema > Avanzado > Opciones para desarrolladores > Simular pantallas secundarias.
  3. Reinicia la HU.
  4. Para iniciar la app de KitchenSink, haz lo siguiente:
    1. Abre el panel.
    2. Ve a Clúster de instrumentos.
    3. Haz clic en INICIAR METADATOS.

KitchenSink solicita el enfoque de NAVIGATION, que le indica al servicio DirectRenderingCluster que muestre una interfaz de usuario simulada en el clúster de instrumentos.