Fulfill 명령어

이 페이지에서는 음성 상호작용으로 명령어를 처리하는 방법을 설명합니다.

미디어 명령어 처리

미디어 관련 명령어는 다음 3가지 그룹으로 나눌 수 있습니다.

  • 외부 미디어 소스(예: AAOS에 설치된 Spotify)
  • 백엔드 미디어 소스(예: VIA를 통해 스트리밍되는 음악)
  • 로컬 미디어 소스(예: 자동차 라디오)

외부 미디어 소스 명령어 처리

외부 미디어 소스는 MediaSessionCompatMediaBrowseCompat API를 지원하는 Android 앱으로 정의됩니다(이러한 API 사용에 관한 자세한 내용은 자동차용 미디어 앱 빌드를 참고하세요).

중요: 어시스턴트 앱이 시스템에 설치된 모든 미디어 앱의 MediaBrowseService에 연결하려면 다음을 충족해야 합니다.

  1. 시스템 서명으로 설치되어야 합니다(AAOS 및 샘플 PackageValidator 코드의 미디어 애플리케이션 개발 가이드라인 참고).
  2. android.permission.MEDIA_CONTENT_CONTROL 시스템 독점 권한을 유지해야 합니다(시스템 독점 권한 부여 참고).

MediaBrowserCompatMediaControllerCompat 외에도 AAOS에서는 다음을 제공합니다.

  • CarMediaService는 현재 선택된 미디어 소스에 관한 통합된 정보를 제공합니다. 이 정보는 자동차 종료 후 재시작 시 이전에 재생된 미디어 소스를 다시 시작하는 데도 사용됩니다.
  • car-media-common은 미디어 앱을 나열, 연결, 상호작용하는 편리한 메서드를 제공합니다.

아래에는 일반적인 음성 상호작용 명령어의 구현과 관련된 가이드라인이 나와 있습니다.

설치된 미디어 소스 목록 가져오기

미디어 소스는 PackageManager를 사용하고 MediaBrowserService.SERVICE_INTERFACE와 일치하는 서비스를 필터링하여 감지할 수 있습니다. 일부 자동차에는 제외되어야 하는 몇 가지 특수 미디어 브라우저 서비스 구현이 있을 수 있습니다. 다음은 이 로직의 예입니다.

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

미디어 소스는 언제든지 설치하거나 제거할 수 있습니다. 정확한 목록을 유지하려면 인텐트 작업 ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REPLACED, ACTION_PACKAGE_REMOVEDBroadcastReceiver 인스턴스를 구현하는 것이 좋습니다.

현재 재생 중인 미디어 소스에 연결

CarMediaService는 현재 선택된 미디어 소스를 가져오는 메서드와 이 미디어 소스가 변경되는 시기를 제공합니다. 이러한 변경은 사용자가 UI와 직접 상호작용했거나 자동차에서 하드웨어 버튼을 사용하기 때문에 발생할 수 있습니다. 반면에 car-media-common 라이브러리를 사용하면 지정된 미디어 소스에 편리하게 연결할 수 있습니다. 다음은 현재 선택된 미디어 앱에 연결하는 방법에 관한 간단한 스니펫입니다.

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
    }

    …
}

현재 재생 중인 미디어 소스의 재생 제어

연결된 MediaBrowserCompat을 사용하면 전송 제어 명령어를 타겟 앱에 쉽게 전송할 수 있습니다. 다음은 예를 간략하게 보여줍니다.

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

    …
}

로컬 미디어 소스 명령어 처리(라디오, CD 플레이어, 블루투스, USB)

로컬 미디어 소스는 위에서 설명한 동일한 MediaSession 및 MediaBrowse API를 사용하여 기능을 시스템에 노출합니다. 각 하드웨어 유형의 특이성을 수용하기 위해 이러한 MediaBrowse 서비스는 특정 규칙을 사용하여 정보 및 미디어 명령어를 구성합니다.

라디오 처리

라디오 MediaBrowseService는 ACTION_PLAY_BROADCASTRADIO 인텐트 필터로 식별할 수 있습니다. 이는 라디오 구현에 설명된 재생 컨트롤 및 미디어 탐색 구조를 따라야 합니다. AAOS는 OEM이 정의된 프로토콜을 따르는 자체 라디오 서비스의 MediaBrowseService 구현을 생성할 수 있도록 상수와 메서드가 포함된 car-broadcastradio-support 라이브러리를 제공하며 탐색 트리를 소비하는 앱을 지원합니다(예: VIA).

보조 입력, CD 오디오, USB 미디어 처리

이러한 미디어 소스는 AOSP의 일부로 기본적으로 구현되지 않습니다. 추천 방법은 다음과 같습니다.

  • OEM이 각각에 대한 미디어 서비스를 구현하도록 합니다. 자세한 내용은 자동차용 미디어 앱 빌드를 참고하세요.
  • 이러한 MediaBrowseService 구현은 일반 재생 인텐트에서 정의된 인텐트 작업에서 식별되고 응답됩니다.
  • 이러한 서비스는 기타 소스 유형에 설명된 가이드라인에 따라 탐색 트리를 노출합니다.

블루투스 처리

블루투스 미디어 콘텐츠는 AVRCP 블루투스 프로필을 통해 노출됩니다. 이 기능에 쉽게 액세스할 수 있도록 AAOS에는 통신 세부정보를 추상화하는 MediaBrowserService 및 MediaSession 구현이 포함됩니다(packages/apps/Bluetooth 참조).

각 미디어 브라우저 트리 구조는 BrowseTree 클래스에서 정의됩니다. 재생 컨트롤 명령어는 MediaSession 구현을 사용하여 다른 앱과 유사하게 전달할 수 있습니다.

스트리밍 미디어 명령어 처리

서버 측 미디어 스트리밍을 구현하려면 VIA 자체가 MediaBrowse 및 MediaSession API를 구현하는 미디어 소스가 되어야 합니다. 자동차용 미디어 앱 빌드를 참고하세요. 이러한 API를 구현하면 음성 제어 앱이 무엇보다 다음을 할 수 있습니다.

  • 미디어 소스 선택에 원활하게 참여
  • 자동차 재시작 후 자동으로 다시 시작됨
  • 미디어 센터 UI를 사용하여 재생 및 탐색 컨트롤 제공
  • 표준 하드웨어 미디어 버튼 이벤트 수신

모든 내비게이션 앱과 상호작용하는 표준화된 방법은 없습니다. Google 지도와 통합에 관해서는 Android Automotive 인텐트용 Google 지도를 참고하세요. 다른 앱과의 통합은 앱 개발자에게 직접 문의하세요. Google 지도 등 모든 앱에 대한 인텐트를 시작하기 전에 인텐트를 확인할 수 있는지 확인합니다(인텐트 요청 참고). 이렇게 하면 타겟 앱을 사용할 수 없는 경우 사용자에게 알릴 수 있습니다.

차량 명령어 처리

읽기 및 쓰기를 위한 차량 속성 액세스 권한은 CarPropertyManager를 통해 제공됩니다. 차량 속성 유형과 구현, 기타 세부정보는 속성 구성에 설명되어 있습니다. Android에서 지원하는 속성에 관한 정확한 설명은 hardware/interfaces/automotive/vehicle/2.0/types.hal을 직접 참조하는 것이 좋습니다. 링크에 정의된 VehicleProperty enum에는 표준 및 공급업체별 속성, 데이터 유형, 변경 모드, 단위, 읽기/쓰기 액세스 정의가 포함됩니다.

Java에서 이러한 동일한 상수에 액세스하려면 VehiclePropertyIds와 해당 컴패니언 클래스를 사용하면 됩니다. 속성마다 액세스를 제어하는 Android 권한이 다릅니다. 이러한 권한은 CarService 매니페스트에서 선언되고, 속성과 권한 간 매핑은 VehiclePropertyIds Javadoc에 설명되고 PropertyHalServiceIds에서 적용됩니다.

차량 속성 읽기

다음은 차량 속도를 읽는 방법을 보여 주는 예시입니다.

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

    ...
}

차량 속성 설정

다음은 전면 AC를 켜고 끄는 방법을 보여 주는 예시입니다.

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

    …
}

통신 명령어 처리

메시지 명령어 처리

VIA는 필요한 경우 받은 메시지의 보내는 사람에게 다시 회신을 보내는 작업을 처리할 수 있는 음성 어시스턴트 탭하여 읽기에 설명된 '탭하여 읽기' 흐름에 따라 받은 메시지를 처리해야 합니다. 또한 VIA는 SmsManager(android.telephony 패키지의 일부)를 사용하여 자동차에서 또는 블루투스를 통해 직접 SMS 메시지를 작성하고 보낼 수 있습니다.

통화 명령어 처리

비슷한 방식으로 VIA는 TelephonyManager를 사용하여 전화를 걸고 사용자의 음성사서함 번호로 전화할 수 있습니다. 이러한 경우 VIA는 전화 통신 스택과 직접 상호작용하거나 자동차 다이얼러 앱과 상호작용합니다. 어느 경우든 자동차 다이얼러 앱이 사용자에게 음성 통화 관련 UI를 표시해야 합니다.

기타 명령어 처리

VIA와 시스템 간에 가능한 다른 통합 지점 목록은 잘 알려진 Android 인텐트 목록을 확인하세요. 많은 사용자 명령어를 서버 측에서 확인할 수 있으며(예: 사용자 이메일 및 캘린더 일정 읽기) 음성 상호작용 자체 이외에 시스템과 상호작용이 필요하지 않습니다.

몰입형 작업(시각적 콘텐츠 표시)

VIA는 사용자 작업이나 이해를 지원하는 경우 자동차 화면에 보조 시각적 콘텐츠를 제공할 수 있습니다. 운전자 주의 분산 행동을 최소화하려면 이러한 콘텐츠를 단순하고, 간략하며, 실행 가능한 상태로 유지합니다. 몰입형 작업의 UI/UX 가이드라인에 관한 자세한 내용은 미리 로드된 어시스턴트: UX 가이드를 참조하세요.

나머지 헤드 단위(HU) 설계와 맞춤설정하고 일관성을 유지하기 위해 VIA는 대부분의 UI 요소에 자동차 UI 라이브러리 구성요소를 사용해야 합니다. 자세한 내용은 맞춤설정을 참고하세요.