Realizowanie poleceń

Na tej stronie opisujemy, jak realizować polecenia za pomocą interakcji głosowej.

Realizowanie poleceń dotyczących multimediów

Polecenia związane z multimediami można podzielić na 3 różne grupy:

  • zewnętrzne źródła multimediów (np. Spotify zainstalowane w AAOS);
  • źródła multimediów w backendzie (np. muzyka przesyłana strumieniowo przez VIA);
  • lokalne źródła multimediów (np. radio samochodowe).

Obsługa poleceń dotyczących zewnętrznych źródeł multimediów

Zewnętrzne źródła multimediów są definiowane jako aplikacje na Androida, które obsługują interfejsy API MediaSessionCompat i MediaBrowseCompat (szczegółowe wyjaśnienie dotyczące korzystania z tych interfejsów API znajdziesz w artykule Tworzenie aplikacji do multimediów dla samochodów).

Ważne: aby aplikacja asystenta mogła połączyć się z MediaBrowseService wszystkich zainstalowanych w systemie aplikacji do multimediów, musi:

  1. być zainstalowana jako aplikacja podpisana przez system (zapoznaj się z wytycznymi dotyczącymi tworzenia aplikacji do multimediów dla AAOS i przykładowym kodem PackageValidator);
  2. mieć uprawnienie systemowe android.permission.MEDIA_CONTENT_CONTROL (zapoznaj się z artykułem Przyznawanie uprawnień systemowych).

Oprócz MediaBrowserCompat i MediaControllerCompat, AAOS udostępnia te elementy:

  • CarMediaService zawiera scentralizowane informacje o aktualnie wybranym źródle multimediów. Jest też używany do wznawiania wcześniej odtwarzanego źródła multimediów po wyłączeniu i ponownym uruchomieniu samochodu.
  • car-media-common udostępnia wygodne metody do wyświetlania listy aplikacji do multimediów, łączenia się z nimi i interakcji z nimi.

Poniżej znajdziesz wytyczne dotyczące implementacji typowych poleceń interakcji głosowej.

Pobieranie listy zainstalowanych źródeł multimediów

Źródła multimediów można wykrywać za pomocą PackageManager, i filtrowania usług pasujących do MediaBrowserService.SERVICE_INTERFACE. W niektórych samochodach mogą występować specjalne implementacje usługi przeglądarki multimediów, które należy wykluczyć. Oto przykład takiej logiki:

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;
}

Pamiętaj, że źródła multimediów można w każdej chwili zainstalować lub odinstalować. Aby zachować dokładną listę, zalecamy zaimplementowanie BroadcastReceiverinstancji dla działań intencji ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REPLACED, i ACTION_PACKAGE_REMOVED.

Łączenie się z aktualnie odtwarzanym źródłem multimediów

CarMediaService udostępnia metody do pobierania aktualnie wybranego źródła multimediów i informacji o jego zmianie. Zmiany te mogą nastąpić, gdy użytkownik bezpośrednio korzysta z interfejsu lub używa przycisków sprzętowych w samochodzie. Z drugiej strony biblioteka car-media-common oferuje wygodne sposoby łączenia się z danym źródłem multimediów. Oto uproszczony fragment kodu pokazujący, jak połączyć się z aktualnie wybraną aplikacją do multimediów:

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
    }

    
}

Sterowanie odtwarzaniem aktualnie odtwarzanego źródła multimediów

Po połączeniu z MediaBrowserCompat można łatwo wysyłać polecenia sterowania transportem do aplikacji docelowej. Oto uproszczony przykład:

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;
    }

    
}

Obsługa poleceń dotyczących lokalnych źródeł multimediów (radio, odtwarzacz CD, Bluetooth, USB)

Lokalne źródła multimediów udostępniają swoje funkcje systemowi za pomocą tych samych interfejsów API MediaSession i MediaBrowse, które zostały opisane powyżej. Aby uwzględnić specyfikę każdego typu sprzętu, te usługi MediaBrowse używają określonych konwencji do organizowania informacji i poleceń dotyczących multimediów.

Obsługa radia

Usługę MediaBrowse radia można zidentyfikować za pomocą filtra intencji ACTION_PLAY_BROADCASTRADIO. Oczekuje się, że będą one zgodne z elementami sterującymi odtwarzaniem i strukturą przeglądania multimediów opisanymi w artykule Implementowanie radia. AAOS udostępnia bibliotekę zawierającą stałe i metody, które pomagają producentom OEM tworzyć implementacje MediaBrowseService dla własnych usług radiowych zgodnych z określonym protokołem. Zapewnia też obsługę aplikacji korzystających z drzewa przeglądania (np. VIA).car-broadcastradio-support

Obsługa wejścia pomocniczego, dźwięku z płyty CD i multimediów z USB

W AOSP nie ma domyślnej implementacji tych źródeł multimediów. Sugerujemy te rozwiązania:

Obsługa Bluetootha

Treści multimedialne Bluetootha są udostępniane za pomocą profilu Bluetooth AVRCP. Aby ułatwić dostęp do tej funkcji, AAOS zawiera implementację MediaBrowserService i MediaSession, która abstrahuje szczegóły komunikacji (zobacz packages/apps/Bluetooth).

Odpowiednia struktura drzewa przeglądarki multimediów jest zdefiniowana w klasie BrowseTree. Polecenia sterowania odtwarzaniem można dostarczać podobnie jak w przypadku innych aplikacji, używając implementacji MediaSession.

Obsługa poleceń dotyczących strumieniowania multimediów

Aby zaimplementować strumieniowanie multimediów po stronie serwera, VIA musi stać się źródłem multimediów, implementując interfejsy API MediaBrowse i MediaSession. Zapoznaj się z artykułem Tworzenie aplikacji do multimediów dla samochodów. Dzięki implementacji tych interfejsów API aplikacja do sterowania głosowego będzie mogła (między innymi):

  • bezproblemowo uczestniczyć w wyborze źródła multimediów;
  • być automatycznie wznawiana po ponownym uruchomieniu samochodu;
  • zapewniać sterowanie odtwarzaniem i przeglądaniem za pomocą interfejsu Centrum multimediów;
  • otrzymywać standardowe zdarzenia z przycisków sprzętowych multimediów.

Nie ma standardowego sposobu interakcji ze wszystkimi aplikacjami do nawigacji. Informacje o integracji z Mapami Google znajdziesz w artykule Google Intencje Map Google na Androida. W przypadku integracji z innymi aplikacjami skontaktuj się bezpośrednio z ich deweloperami. Przed uruchomieniem intencji w dowolnej aplikacji (w tym w Mapach Google) sprawdź, czy można ją rozwiązać (zobacz Intencje Map Google). Dzięki temu możesz poinformować użytkownika, jeśli aplikacja docelowa jest niedostępna.

Realizowanie poleceń dotyczących pojazdu

Dostęp do właściwości pojazdu (zarówno do odczytu, jak i zapisu) jest zapewniany przez CarPropertyManager. Typy właściwości pojazdu, ich implementacja i inne szczegóły są opisane w Konfiguracje właściwości. Aby uzyskać dokładny opis właściwości obsługiwanych przez Androida, najlepiej zapoznać się bezpośrednio z hardware/interfaces/automotive/vehicle/2.0/types.hal. Zdefiniowany tam wyliczenie VehicleProperty zawiera zarówno standardowe, jak i specyficzne dla dostawcy właściwości, typy danych, tryb zmiany, jednostki oraz definicję dostępu do odczytu i zapisu.

Aby uzyskać dostęp do tych samych stałych w Javie, możesz użyć VehiclePropertyIds i jego klas towarzyszących. Różne właściwości mają różne uprawnienia Androida, które kontrolują do nich dostęp. Te uprawnienia są zadeklarowane w manifeście CarService, a mapowanie między właściwościami i uprawnieniami jest opisane w dokumentacji Javadoc VehiclePropertyIds i wymuszane w PropertyHalServiceIds.

Odczytywanie właściwości pojazdu

Oto przykład pokazujący, jak odczytać prędkość pojazdu:

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);
    }

    ...
}

Ustawianie właściwości pojazdu

Oto przykład pokazujący, jak włączyć i wyłączyć klimatyzację z przodu:

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"));
    }

    …
}

Realizowanie poleceń dotyczących komunikacji

Obsługa poleceń dotyczących wiadomości

VIA muszą obsługiwać wiadomości przychodzące zgodnie z przepływem "kliknij, aby przeczytać" opisanym w artykule Asystent głosowy „Kliknij, aby przeczytać”. Opcjonalnie mogą też obsługiwać wysyłanie odpowiedzi do nadawcy wiadomości przychodzącej. Dodatkowo VIA mogą używać SmsManager (część pakietu android.telephony ) do tworzenia i wysyłania SMS-ów bezpośrednio z samochodu lub przez Bluetooth.

Obsługa poleceń dotyczących połączeń

Podobnie VIA mogą używać TelephonyManager do wykonywania połączeń telefonicznych i dzwonienia na pocztę głosową użytkownika. W takich przypadkach VIA będą wchodzić w interakcję bezpośrednio ze stosem telefonicznym lub z aplikacją Car Dialer. W każdym przypadku to aplikacja Car Dialer powinna wyświetlać użytkownikowi interfejs związany z połączeniami głosowymi.

Realizowanie innych poleceń

Listę innych możliwych punktów integracji VIA z systemem znajdziesz w artykule Znane intencje Androida. Wiele poleceń użytkownika można rozwiązać po stronie serwera (np. odczytywanie e-maili i wydarzeń z kalendarza użytkowników) i nie wymagają one żadnych interakcji z systemem poza samą interakcją głosową.

Działania immersyjne (wyświetlanie treści wizualnych)

Jeśli to ułatwia użytkownikowi wykonywanie działań lub zrozumienie, VIA może wyświetlać dodatkowe treści wizualne na ekranie samochodu. Aby nie rozpraszać uwagi kierowcy, takie treści powinny być proste, krótkie i umożliwiać podjęcie działania. Szczegółowe informacje o wytycznych dotyczących interfejsu i UX w przypadku działań immersyjnych znajdziesz w artykule Wstępnie zainstalowani asystenci: wskazówki dotyczące UX.

Aby umożliwić dostosowanie i zachować spójność z resztą projektu jednostki głównej, VIA powinny używać komponentów biblioteki Car UI w przypadku większości elementów interfejsu. Więcej informacji znajdziesz w sekcji Dostosowywanie.