Cumpliendo Comandos

Aprende a cumplir con este tipo de comandos con interacción de voz:

Cumplimiento de los comandos de medios

El comando relacionado con los medios se puede dividir en tres grupos diferentes:

  • Fuentes de medios externos (como Spotify instalado en AAOS).
  • Fuentes de medios back-end (como música transmitida a través de VIA).
  • Fuentes de medios locales (como la radio del automóvil).

Manejo de comandos de fuente de medios externos

Las fuentes de medios externas se definen como aplicaciones de Android que admiten las API de MediaSession y MediaBrowse (consulte Crear aplicaciones de medios para automóviles para obtener una explicación detallada sobre el uso de estas API).

Importante. Para que una aplicación asistente se conecte al MediaBrowseService de todas las aplicaciones multimedia instaladas en el sistema, debe:

  1. Estar instalado como firmado por el sistema (consulte las pautas de desarrollo de aplicaciones de medios para AAOS y el código PackageValidator de muestra).
  2. Mantenga el permiso con privilegios del sistema android.permission.MEDIA_CONTENT_CONTROL (consulte Otorgar permisos con privilegios del sistema ).

Además de MediaBrowserCompat y MediaControllerCompat , AAOS proporciona lo siguiente:

  • CarMediaService proporciona información centralizada sobre la fuente de medios actualmente seleccionada. Esto también se usa para reanudar una fuente de medios que se estaba reproduciendo previamente después de apagar y reiniciar el automóvil.
  • car-media-common proporciona métodos convenientes para enumerar, conectarse e interactuar con aplicaciones de medios.

A continuación se proporcionan pautas específicas para la implementación de comandos de interacción de voz comunes.

Obtener una lista de fuentes de medios instaladas

Las fuentes de medios se pueden detectar utilizando PackageManager y filtrando los servicios que coinciden con MediaBrowserService.SERVICE_INTERFACE . En algunos automóviles puede haber algunas implementaciones especiales del servicio de navegador de medios, que deben excluirse. A continuación se muestra un ejemplo de esta lógica:

private Map<String, MediaSource> getAvailableMediaSources() {
    List<String> customMediaServices =
        Arrays.asList(mContext.getResources()
            .getStringArray(R.array.custom_media_packages));
    List<ResolveInfo> mediaServices = mPackageManager.queryIntentServices(
            new Intent(MediaBrowserService.SERVICE_INTERFACE),
            PackageManager.GET_RESOLVED_FILTER);
    Map<String, MediaSource> result = new HashMap<>();
    for (ResolveInfo info : mediaServices) {
        String packageName = info.serviceInfo.packageName;
        if (customMediaServices.contains(packageName)) {
            // Custom media sources should be ignored, as they might have a
            // specialized handling (e.g.: radio).
            continue;
        }
        String className = info.serviceInfo.name;
        ComponentName componentName = new ComponentName(packageName,
            className);
        MediaSource source = MediaSource.create(mContext, componentName);
        result.put(source.getDisplayName().toString().toLowerCase(),
            source);
    }
    return result;
}

Tenga en cuenta que las fuentes de medios pueden instalarse o desinstalarse en cualquier momento. Para mantener una lista precisa, se recomienda implementar un BroadcastReceiver para las siguientes acciones de intención: ACTION_PACKAGE_ADDED , ACTION_PACKAGE_CHANGED , ACTION_PACKAGE_REPLACED , ACTION_PACKAGE_REMOVED .

Conectar a la fuente de medios que se está reproduciendo actualmente

CarMediaService proporciona métodos para obtener la fuente de medios actualmente seleccionada y cuándo cambia esta fuente de medios. Estos cambios podrían ocurrir porque el usuario interactuó directamente con la interfaz de usuario o debido al uso de botones de hardware en el automóvil. Por otro lado, la biblioteca car-media-common ofrece formas convenientes de conectarse a una fuente de medios determinada. Aquí hay un fragmento simplificado sobre cómo conectarse a la aplicación de medios seleccionada actualmente:

public class MediaActuator implements
        MediaBrowserConnector.onConnectedBrowserChanged {
    private final Car mCar;
    private CarMediaManager mCarMediaManager;
    private MediaBrowserConnector mBrowserConnector;

    …

    public void initialize(Context context) {
        mCar = Car.createCar(context);
        mBrowserConnector = new MediaBrowserConnector(context, this);
        mCarMediaManager = (CarMediaManager)
            mCar.getCarManager(Car.CAR_MEDIA_SERVICE);
        mBrowserConnector.connectTo(mCarMediaManager.getMediaSource());
        …
    }

    @Override
    public void onConnectedBrowserChanged(
            @Nullable MediaBrowserCompat browser) {
        // TODO: Handle connected/disconnected browser
    }

    …
}

Controlar la reproducción de la fuente de medios que se está reproduciendo actualmente

Con un MediaBrowserCompat conectado, es muy sencillo enviar comandos de "control de transporte" a la aplicación de destino. Aquí hay un ejemplo simplificado:

public class MediaActuator …  {
    …
    private MediaControllerCompat mMediaController;

    @Override
    public void onConnectedBrowserChanged(
            @Nullable MediaBrowserCompat browser) {
        if (browser != null && browser.isConnected()) {
            mMediaController = new MediaControllerCompat(mContext,
                browser.getSessionToken());
        } else {
            mMediaController = null;
        }
    }

    private boolean playSongOnCurrentSource(String song) {
        if (mMediaController == null) {
            // No source selected.
            return false;
        }
        MediaControllerCompat.TransportControls controls =
            mMediaController.getTransportControls();
        PlaybackStateCompat state = controller.getPlaybackState();
        if (state == null || ((state.getActions() &
                PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH) == 0)) {
            // Source can't play from search
            return false;
        }
        controls.playFromSearch(query, null);
        return true;
    }

    …
}

Manejo de comandos de fuentes de medios locales (radio, reproductor de CD, Bluetooth, USB)

Las fuentes de medios locales exponen su funcionalidad al sistema utilizando las mismas API de MediaSession y MediaBrowse detalladas anteriormente. Para adaptarse a las particularidades de cada tipo de hardware, estos servicios de MediaBrowse utilizan convenciones específicas para organizar su información y comandos de medios.

manejo de radios

Radio MediaBrowseService se puede identificar mediante el filtro de intenciones ACTION_PLAY_BROADCASTRADIO . Se espera que sigan los controles de reproducción y la estructura de exploración de medios que se describen aquí: Implementación de radio con medios . AAOS ofrece la biblioteca de soporte de radio de transmisión de automóviles que contiene constantes y métodos para ayudar a los OEM a crear implementaciones de MediaBrowseService para sus propios servicios de radio que siguen el protocolo definido y brinda soporte para aplicaciones que consumen su árbol de navegación (por ejemplo, VIA).

Manejo de entrada auxiliar, CD de audio y medios USB

No hay una implementación predeterminada de estas fuentes de medios como parte de AOSP. El enfoque sugerido es:

Manejo de Bluetooth

El contenido multimedia de Bluetooth se expone a través del perfil de Bluetooth AVRCP. Para facilitar el acceso a esta funcionalidad, AAOS incluye una implementación de MediaBrowserService y MediaSession que abstrae los detalles de la comunicación (ver paquetes/aplicaciones/Bluetooth ).

La estructura de árbol respectiva del navegador de medios se define en la clase BrowseTree . Los comandos de control de reproducción se pueden entregar de manera similar a cualquier otra aplicación, utilizando su implementación MediaSession.

Manejo de comandos de transmisión de medios

Para implementar la transmisión de medios del lado del servidor, el VIA debe convertirse en una fuente de medios, implementando MediaBrowse y MediaSession API. Consulte Crear aplicaciones de medios para automóviles . Al implementar estas API, una aplicación de control de voz podría (entre otras cosas):

  • Participe sin problemas en la selección de fuentes de medios
  • Se reanudará automáticamente después de reiniciar el automóvil
  • Proporcione control de reproducción y navegación mediante la interfaz de usuario de Media Center
  • Reciba eventos de botón de medios de hardware estándar

No existe una forma estandarizada de interactuar con todas las aplicaciones de navegación. Para integraciones con Google Maps, consulte Google Maps para Android Automotive Intents . Para integraciones con otras aplicaciones, comuníquese directamente con los desarrolladores de la aplicación. Antes de lanzar una intención a cualquier aplicación (incluido Google Maps), verifique que la intención se pueda resolver (consulte Solicitudes de intención ). Esto crea la oportunidad de informar al usuario en caso de que la aplicación de destino no esté disponible.

Cumplimiento de los comandos del vehículo

El acceso a las propiedades del vehículo para lectura y escritura se proporciona a través de CarPropertyManager . Los tipos de propiedades del vehículo, su implementación y otros detalles se explican aquí: AOSP/Develop/Automotive - Vehicle Properties . Para obtener una descripción precisa de las propiedades compatibles con Android, es mejor consultar directamente hardware/interfaces/automotive/vehicle/2.0/types.hal . La enumeración VehicleProperty definida allí contiene propiedades estándar y específicas del proveedor, tipos de datos, modo de cambio, unidades y definición de acceso de lectura/escritura.

Para acceder a estas mismas constantes desde Java, puede usar VehiclePropertyIds y sus clases complementarias. Diferentes propiedades tienen diferentes permisos de Android que controlan su acceso. Estos permisos se declaran en el manifiesto de CarService , y la asignación entre propiedades y permisos se describe en el Javadoc de VehiclePropertyIds y se aplica en PropertyHalServiceIds .

Lectura de una propiedad de vehículo

El siguiente es un ejemplo que muestra cómo leer la velocidad del vehículo.

public class CarActuator ... {
    private final Car mCar;
    private final CarPropertyManager mCarPropertyManager;
    private final TextToSpeech mTTS;

    /** Global VHAL area id */
    public static final int GLOBAL_AREA_ID = 0;

    public CarActuator(Context context, TextToSpeech tts) {
        mCar = Car.createCar(context);
        mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
        mTTS = tts;
        ...
    }

    @Nullable
    private void getSpeedInMetersPerSecond() {
        if (!mCarPropertyManager.isPropertyAvailable(VehiclePropertyIds.PERF_VEHICLE_SPEED,
                GLOBAL_AREA_ID)) {
            mTTS.speak("I'm sorry, but I can't read the speed of this vehicle");
            return;
        }
        // Data type and unit can be found in
        // automotive/vehicle/2.0/types.hal
        float speedInMps = mCarPropertyManager.getFloatProperty(
                VehiclePropertyIds.PERF_VEHICLE_SPEED, GLOBAL_AREA_ID);
        int speedInMph = (int)(speedInMetersPerSecond * 2.23694f);
        mTTS.speak(String.format("Sure. Your current speed is %d miles "
                + "per hour", speedInUserUnit);
    }

    ...
}

Establecer una propiedad de vehículo

El siguiente es un ejemplo que muestra cómo encender y apagar el aire acondicionado frontal.

public class CarActuator … {
    …

    private void changeFrontAC(boolean turnOn) {
        List<CarPropertyConfig> configs = mCarPropertyManager
                .getPropertyList(new ArraySet<>(Arrays.asList(
                    VehiclePropertyIds.HVAC_AC_ON)));
        if (configs == null || configs.size() != 1) {
            mTTS.speak("I'm sorry, but I can't control the AC of your vehicle");
            return;
        }

        // Find the front area Ids for the AC property.
        int[] areaIds = configs.get(0).getAreaIds();
        List<Integer> areasToChange = new ArrayList<>();
        for (int areaId : areaIds) {
            if ((areaId & (VehicleAreaSeat.SEAT_ROW_1_CENTER
                        | VehicleAreaSeat.SEAT_ROW_1_LEFT
                        | VehicleAreaSeat.SEAT_ROW_1_RIGHT)) == 0) {
                continue;
            }
            boolean isACInAreaAlreadyOn = mCarPropertyManager
                    .getBooleanProperty(VehiclePropertyIds.HVAC_AC_ON, areaId);
            if ((!isACInAreaAlreadyOn && turnOn) || (isACInAreaAlreadyOn && !turnOn)) {
                areasToChange.add(areaId);
            }
        }
        if (areasToChange.isEmpty()) {
            mTTS.speak(String.format("The AC is already %s", turnOn ? "on" : "off"));
            return;
        }

        for (int areaId : areasToChange) {
            mCarPropertyManager.setBooleanProperty(
                VehiclePropertyIds.HVAC_AC_ON, areaId, turnOn);
        }
        mTTS.speak(String.format("Okay, I'm turning your front AC %s",
            turnOn ? "on" : "off"));
    }

    …
}

Cumplimiento de los comandos de comunicación

Manejo de comandos de mensajería

Los VIA deben manejar los mensajes entrantes siguiendo el flujo de "tocar para leer" descrito en Voice Assistant Tap-to-Read , que opcionalmente puede manejar el envío de respuestas al remitente del mensaje entrante. Además, los VIA pueden usar SmsManager (parte del paquete android.telephony ) para redactar y enviar mensajes SMS directamente desde el automóvil o por Bluetooth.

Manejo de comandos de llamada

De manera similar, los VIA pueden usar TelephonyManager para realizar llamadas telefónicas y llamar al número de correo de voz del usuario. En estos casos, los VIA interactuarán con la pila de telefonía directamente o con la aplicación Car Dialer. En cualquier caso, la aplicación Car Dialer debe ser la que muestre al usuario la interfaz de usuario relacionada con las llamadas de voz.

Cumpliendo otros comandos

Para obtener una lista de otros posibles puntos de integración entre VIA y el sistema, consulte la lista de Android Intents conocidos. Muchos comandos de usuario se pueden resolver en el lado del servidor (por ejemplo, leer los correos electrónicos de los usuarios y los eventos del calendario) y no requieren ninguna interacción con el sistema aparte de la interacción de voz en sí.

Acciones inmersivas (mostrar contenido visual)

Cuando mejora las acciones o la comprensión del usuario, un VIA puede proporcionar contenido visual complementario en la pantalla del automóvil. Para minimizar la distracción del conductor, mantenga dicho contenido simple, breve y procesable. Para obtener detalles sobre las pautas de UI/UX sobre acciones inmersivas, consulte Asistentes precargados: orientación de UX .

Para permitir la personalización y la coherencia con el resto del diseño de la unidad principal (HU), los VIA deben usar los componentes de la biblioteca de interfaz de usuario del automóvil para la mayoría de los elementos de la interfaz de usuario. Para obtener más información, consulte Personalización .