음성 상호작용으로 이러한 유형의 명령어를 처리하는 방법을 알아보세요.
미디어 명령어 처리
미디어 관련 명령어는 다음 3가지 그룹으로 나눌 수 있습니다.
- 외부 미디어 소스(예: AAOS에 설치된 Spotify)
- 백엔드 미디어 소스(예: VIA를 통해 스트림되는 음악)
- 로컬 미디어 소스(예: 자동차 라디오)
외부 미디어 소스 명령어 처리
외부 미디어 소스는 MediaSessionCompat
및 MediaBrowseCompat
API를 지원하는 Android 애플리케이션으로 정의됩니다(이러한 API 사용에 관한 자세한 내용은 자동차용 미디어 앱 빌드를 참조하세요).
중요: 어시스턴트 애플리케이션이 시스템에 설치된 모든 미디어 애플리케이션의 MediaBrowseService
에 연결하려면 다음을 충족해야 합니다.
- 시스템 서명으로 설치되어야 합니다(AAOS 및 샘플
PackageValidator
코드의 미디어 애플리케이션 개발 가이드라인 참조). android.permission.MEDIA_CONTENT_CONTROL
시스템 독점 권한을 유지해야 합니다(시스템 독점 권한 부여 참조).
AAOS는 MediaBrowserCompat 및 MediaControllerCompat 외에도 다음을 제공합니다.
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_REMOVE의 BroadcastReceiver를 구현하는 것이 좋습니다.
현재 재생 중인 미디어 소스에 연결
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을 사용하면 매우 간단하게 "transport control" 명령어를 타겟 애플리케이션에 전송할 수 있습니다. 다음은 간단한 예입니다.
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를 통해 제공됩니다. 차량 속성 유형, 구현 및 기타 세부정보는 AOSP/Develop/Automotive - 차량 속성을 참조하세요. Android에서 지원하는 속성에 관한 정확한 설명은 hardware/interfaces/automotive/vehicle/vetypes.hal을 직접 참조하는 것이 좋습니다. 링크에 정의된 VehicleProperty 열거형에는 표준 및 공급업체별 속성, 데이터 유형, 변경 모드, 단위 및 읽기/쓰기 액세스 정의가 둘 다 포함됩니다.
자바에서 이러한 동일한 상수에 액세스하려면 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 라이브러리 구성요소를 사용해야 합니다. 자세한 내용은 맞춤설정을 참조하세요.