Android Auto 내비게이션 사용 설정

이 섹션에서는 자동차용 Android 앱 라이브러리와 함께 Navigation SDK를 사용하여 앱의 내비게이션 환경을 대시 헤드 단위에 표시하는 방법을 설명합니다. 운전자의 대시보드 내 시스템에서 Android Auto를 지원하는 경우 운전자는 휴대전화를 기기에 연결하여 자동차 디스플레이에서 직접 앱을 사용할 수 있습니다. 음성 안내는 자동차 스피커에서도 실행됩니다.

Android Auto에 사용 설정된 앱을 사용하여 안내 내비게이션을 표시하는 대시 내 헤드 단위입니다.

자동차용 Android 앱 라이브러리를 사용하면 운전자 안전을 위해 승인된 일련의 시각적 템플릿을 제공하여 Android 애플리케이션을 Android Auto에서 실행할 수 있습니다. 이러한 템플릿은 운전자 주의 분산 행동을 줄이기 위해 휴대전화 UI 컨트롤의 대시보드 내 UI 컨트롤을 의도적으로 제한합니다.

Navigation SDK 기반 앱이 Android Auto와 함께 작동하도록 하면 내비게이션 환경을 위한 추가 뷰가 제공됩니다. 이렇게 하면 휴대전화용과 헤드 단위용 등 두 개의 지도뷰가 허용됩니다. 두 디스플레이 모두 싱글톤인 Navigator.java에서 안내를 수신합니다.

운전자가 지나친 주의 분산 없이 안전하게 목적지로 이동할 수 있도록 안전이 승인된 대화형 요소가 대시보드 내 시스템에 표시됩니다. 운전자는 주문 수락 또는 거부, 지도에서 고객 위치 보기와 같은 앱별 기능과 상호작용할 수도 있습니다. 주문 상태 업데이트는 대시보드 내 단위에도 표시될 수 있습니다.

Android Auto로 세부 경로 안내를 표시하는 대시 내 헤드 단위 개요와 동일한 경로를 표시하는 Android 휴대전화

연결된 휴대전화에서는 표준 Navigation SDK 환경이나 애플리케이션의 다른 뷰나 워크플로를 계속 표시할 수 있습니다. 이렇게 하면 자동차 화면에서 제대로 작동하지 않을 수 있는 맞춤 기능을 계속 제공할 수 있습니다.

설정

앱이 Android Auto에서 작동하도록 하려면 먼저 Android Auto로 자동차 서비스를 설정한 다음 Navigation SDK 앱에서 TurnByTurn 라이브러리를 사용 설정해야 합니다.

Android Auto 시작하기

Android Auto에서 작동하도록 설계된 내비게이션 SDK 기능으로 작업을 시작하려면 먼저 Android Auto에서 앱을 검색할 수 있도록 앱의 자동차 서비스를 설정해야 합니다.

다음 단계를 따르세요. 이 모든 내용은 자동차용 Android 개발자 문서에서 확인할 수 있습니다.

  1. 기본적인 Android Auto 기능을 숙지합니다.
  2. 자동차용 Android 앱 라이브러리를 설치합니다.
  3. Android Auto를 포함하도록 앱의 매니페스트 파일을 구성합니다.
  4. 매니페스트에서 최소 자동차 앱 수준 1을 선언합니다.
  5. CarAppService 및 세션을 만듭니다.

Navigation SDK 설정

자동차 앱 서비스를 설정했으면 Navigation SDK를 사용할 준비가 된 것입니다.

  1. 앱에 Navigation SDK를 아직 통합하지 않았다면 프로젝트를 설정합니다.
  2. 앱에 TurnbyTurn 안내 피드를 사용 설정합니다.
  3. 선택사항입니다. Navigation SDK에서 생성된 아이콘을 사용합니다.
  4. Screen 클래스에 제공된 Android Auto Surface의 NavigationViewForAuto 클래스를 사용하여 지도를 그립니다.
  5. TurnbyTurn 라이브러리의 데이터로 Android Auto Navigation 템플릿을 입력합니다.

이제 앱에 내비게이션 정보를 제공하는 등록된 서비스가 있고 앱을 Android Auto에 연결할 수 있으므로 앱이 Android Auto와 제대로 작동하는 데 필요한 나머지 탐색 요소를 만들 준비가 되었습니다.

지도 및 탐색 UI 그리기

NavigationViewForAuto 클래스는 Android Auto 화면에 지도 및 탐색 UI를 렌더링합니다. 휴대전화의 NavigationView와 동일한 기능을 많이 제공하지만 상호작용성이 제한적입니다. NavigationViewForAuto를 사용하여 Android Auto에서 제공하는 Surface에 그립니다.

private boolean isSurfaceReady(SurfaceContainer surfaceContainer) {
  return surfaceContainer.getSurface() != null
        && surfaceContainer.getDpi() != 0
        && surfaceContainer.getHeight() != 0
        && surfaceContainer.getWidth() != 0;
}

@Override
public void onSurfaceAvailable(@NonNull SurfaceContainer surfaceContainer) {
  if (!isSurfaceReady(surfaceContainer)) {
    return;
   }
  virtualDisplay =
      getCarContext()
          .getSystemService(DisplayManager.class)
          .createVirtualDisplay(
            VIRTUAL_DISPLAY_NAME,
            surfaceContainer.getWidth(),
            surfaceContainer.getHeight(),
            surfaceContainer.getDpi(),
            surfaceContainer.getSurface(),
            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
    presentation = new Presentation(getCarContext(), virtualDisplay.getDisplay());

    navigationView = new NavigationViewForAuto(getCarContext());
    navigationView.onCreate(null);
    navigationView.onStart();
    navigationView.onResume();

    presentation.setContentView(navigationView);
    presentation.show();

    navigationView.getMapAsync(googleMap -> this.googleMap = googleMap);
  }

@Override
public void onSurfaceDestroyed(@NonNull SurfaceContainer surfaceContainer) {
  navigationView.onPause();
  navigationView.onStop();
  navigationView.onDestroy();

  presentation.dismiss();
  virtualDisplay.release();
}

지도 상호작용 사용 설정

운전자의 안전을 보장하기 위해 Android Auto는 화면 노출 영역 상호작용을 일련의 SurfaceCallback 메서드로 제한합니다. 이러한 콜백을 사용하여 계기판 내 지도와의 제한된 운전자 상호작용을 지원합니다. 예를 들어 onClickonScale는 사용자의 손가락 모으기 동작에 해당합니다. 상호작용 콜백은 다음과 같이 지도 작업 스트립을 사용해야 합니다.

  • 지도 상호작용 콜백을 수신하려면 앱에서 Action.PAN 버튼을 사용해야 합니다.

  • 추가 사용자 작업을 지원하려면 지도 작업 스트립에 버튼을 추가하세요.

노출 영역 콜백 사용 설정

@NonNull
@Override
public Template onGetTemplate() {
  return new NavigationTemplate.Builder()
    .setActionStrip(new ActionStrip.Builder().build())
    .setMapActionStrip(new ActionStrip.Builder().addAction(Action.PAN).build())
    .build();
}

손가락을 모으거나 펼쳐 확대/축소

@Override
public void onScale(float focusX, float focusY, float scaleFactor) {
  CameraUpdate update =
      CameraUpdateFactory.zoomBy((scaleFactor - 1),
                                  new Point((int) focusX, (int) focusY));
  googleMap.animateCamera(update); // map is set in onSurfaceAvailable.
}

화면 이동

@Override
public void onScroll(float distanceX, float distanceY) {
  googleMap.moveCamera(CameraUpdateFactory.scrollBy(distanceX, distanceY));
}

탐색 경로 표시

이 섹션에서는 탐색 게시물의 관찰자를 설정하고 턴 카드 템플릿에 탐색 경로를 채우는 방법을 설명합니다.

Android Auto 안내를 위한 턴 카드

Android Auto 내비게이션 템플릿은 현재 경로와 관련된 내비게이션 정보를 표시하는 턴 카드를 제공합니다. Navigation SDK의 TurnByTurn 라이브러리는 코드에서 Android Auto 탐색 템플릿을 채우는 데 사용하는 탐색 정보를 제공합니다.

관찰자 설정

다음 예에서 SampleApplicationMutableLiveData<NavInfo> 객체를 유지관리하는 커스텀 애플리케이션 클래스입니다. 관찰자는 탐색기 객체에서 업데이트를 수신하면 이 NavInfo 객체를 SampleApplication 클래스에서 관리하는 NavInfoMutableLiveData에 게시합니다.

다음 예에서는 Android Auto 화면의 구현에서 이 객체의 관찰자를 등록합니다.

public SampleAndroidAutoNavigationScreen(@NonNull CarContext carContext,
                                     SampleApplication application) {
  super(carContext);
  getCarContext().getCarService(AppManager.class).setSurfaceCallback(this);
  application.getNavInfoMutableLiveData().observe(this, this::processNextStep);
}

내비게이션 정보 채우기

다음 코드 스니펫은 Android Auto 템플릿을 걸음 수, 거리, 아이콘 등 현재 경로 정보로 채우는 방법을 보여줍니다. 이러한 디스플레이 요소에 관한 자세한 내용은 피드 표시 채우기를 참고하세요.

펼쳐서 코드 예를 확인하세요.

private RoutingInfo currentRoutingInfo;

@NonNull
@Override
public Template onGetTemplate() {
NavigationTemplate.Builder navigationTemplateBuilder =
  new NavigationTemplate.Builder()
    .setActionStrip(...)
    .setMapActionStrip(...)
  if (currentRoutingInfo != null) {
    navigationTemplateBuilder.setNavigationInfo(currentRoutingInfo);
  }
  return navigationTemplateBuilder.build();
}

private void processNextStep(NavInfo navInfo) {
  if (navInfo == null || navinfo.getCurrentStep() == null) {
    return;
  }

/**
*   Converts data received from the Navigation data feed
*   into Android-Auto compatible data structures. For more information
*   see the "Ensure correct maneuver types" below.
*/
  Step currentStep = buildStepFromStepInfo(navInfo.getCurrentStep());
  Distance distanceToStep =
              buildDistanceFromMeters(navInfo.getDistanceToCurrentStepMeters());

  currentRoutingInfo =
     new RoutingInfo.Builder().setCurrentStep(currentStep, distanceToStep).build();

  // Invalidate the current template which leads to another onGetTemplate call.
  invalidate();
}

private Step buildStepFromStepInfo(StepInfo stepInfo) {
  IconCompat maneuverIcon =
               IconCompat.createWithBitmap(stepInfo.getManeuverBitmap());
  Maneuver.Builder
            maneuverBuilder = newManeuver.Builder(
                  ManeuverConverter
                          .getAndroidAutoManeuverType(stepInfo.getManeuver()));
  CarIcon maneuverCarIcon = new CarIcon.Builder(maneuverIcon).build();
  maneuverBuilder.setIcon(maneuverCarIcon);
  Step.Builder stepBuilder =
    new Step.Builder()
       .setRoad(stepInfo.getFullRoadName())
       .setCue(stepInfo.getFullInstructionText())
       .setManeuver(maneuverBuilder.build());

  if (stepInfo.getLanes() != null
           && stepInfo.getLanesBitmap() != null) {
    for (Lane lane : buildAndroidAutoLanesFromStep(stepInfo)) {
      stepBuilder.addLane(lane);
    }
    IconCompat lanesIcon =
               IconCompat.createWithBitmap(stepInfo.getLanesBitmap());
    CarIcon lanesImage = new CarIcon.Builder(lanesIcon).build();
    stepBuilder.setLanesImage(lanesImage);
  }
    return stepBuilder.build();
}

/*
*   Constructs a {@code Distance} object in imperial measurement units.
*   In a real world scenario, units would be based on locale.
*/
private Distance buildDistanceFromMeters(int distanceMeters) {

// Distance can be negative so set the min distance to 0.
  int remainingFeet = (int) max(0, distanceMeters * DistanceConstants.FEET_PER_METER);
  double remainingMiles = ((double) remainingFeet) / DistanceConstants.FEET_PER_MILE;

// Only use the tenths place digit if distance is less than 10 miles and show
// feet if distance is less than 0.25 miles.

  if (remainingMiles >= DistanceConstants.MIN_MILES_TO_SHOW_INTEGER) {
    return Distance.create((int) round(remainingMiles), Distance.UNIT_MILES);
  } else if (remainingMiles >= 0.25) {
    return Distance.create((int) remainingMiles, Distance.UNIT_MILES);
  } else {
    return Distance.create(remainingFeet, Distance.UNIT_FEET);
  }
}

올바른 조작 유형 확인

Android Auto Car 라이브러리에서 사용되는 조작 유형은 TurnByTurn 라이브러리에서 제공되는 조작 방식과 일대일로 일치합니다. 그러나 Navigation SDK 조작을 Android Auto Car 라이브러리에서 유효한 선언으로 변환해야 합니다. 다음 표는 몇 개의 필드에 상응하는 항목과 편의상 제공되는 샘플 변환기 유틸리티를 차례로 보여줍니다.

세부 경로 안내 라이브러리 전략 Android Auto 조작
DEPART TYPE_DEPART
DESTINATION TYPE_DESTINATION
DESTINATION_LEFT TYPE_DESTINATION_LEFT
DESTINATION_RIGHT TYPE_DESTINATION_RIGHT
TURN_U_TURN_CLOCKWISE TYPE_U_TURN_RIGHT
ON_RAMP_LEFT TYPE_ON_RAMP_NORMAL_LEFT
ON_RAMP_RIGHT TYPE_ON_RAMP_NORMAL_RIGHT
ON_RAMP_SLIGHT_LEFT TYPE_ON_RAMP_SLIGHT_LEFT
FORK_RIGHT TYPE_FORK_RIGHT

펼쳐서 코드 예를 확인하세요.

import com.google.android.libraries.mapsplatform.turnbyturn.model.Maneuver;
import com.google.common.collect.ImmutableMap;
import javax.annotation.Nullable;

/** Converter that converts between turn-by-turn and Android Auto Maneuvers. */
public final class ManeuverConverter {
  private ManeuverConverter() {}

  // Map from turn-by-turn Maneuver to Android Auto Maneuver.Type.
  private static final ImmutableMap<Integer, Integer> MANEUVER_TO_ANDROID_AUTO_MANEUVER_TYPE =
      ImmutableMap.<Integer, Integer>builder()
          .put(Maneuver.DEPART, androidx.car.app.navigation.model.Maneuver.TYPE_DEPART)
          .put(Maneuver.DESTINATION, androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION)
          .put(
              Maneuver.DESTINATION_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_LEFT)
          .put(
              Maneuver.DESTINATION_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_RIGHT)
          .put(Maneuver.STRAIGHT, androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT)
          .put(Maneuver.TURN_LEFT, androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT)
          .put(
              Maneuver.TURN_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT)
          .put(Maneuver.TURN_KEEP_LEFT, androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_LEFT)
          .put(Maneuver.TURN_KEEP_RIGHT, androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_RIGHT)
          .put(
              Maneuver.TURN_SLIGHT_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT)
          .put(
              Maneuver.TURN_SLIGHT_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT)
          .put(
              Maneuver.TURN_SHARP_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_LEFT)
          .put(
              Maneuver.TURN_SHARP_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SHARP_RIGHT)
          .put(
              Maneuver.TURN_U_TURN_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_U_TURN_RIGHT)
          .put(
              Maneuver.TURN_U_TURN_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_U_TURN_LEFT)
          .put(
              Maneuver.MERGE_UNSPECIFIED,
              androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_SIDE_UNSPECIFIED)
          .put(Maneuver.MERGE_LEFT, androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_LEFT)
          .put(Maneuver.MERGE_RIGHT, androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_RIGHT)
          .put(Maneuver.FORK_LEFT, androidx.car.app.navigation.model.Maneuver.TYPE_FORK_LEFT)
          .put(Maneuver.FORK_RIGHT, androidx.car.app.navigation.model.Maneuver.TYPE_FORK_RIGHT)
          .put(
              Maneuver.ON_RAMP_UNSPECIFIED,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_RIGHT)
          .put(
              Maneuver.ON_RAMP_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_LEFT)
          .put(
              Maneuver.ON_RAMP_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_RIGHT)
          .put(
              Maneuver.ON_RAMP_KEEP_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_LEFT)
          .put(
              Maneuver.ON_RAMP_KEEP_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_RIGHT)
          .put(
              Maneuver.ON_RAMP_SLIGHT_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SLIGHT_LEFT)
          .put(
              Maneuver.ON_RAMP_SLIGHT_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SLIGHT_RIGHT)
          .put(
              Maneuver.ON_RAMP_SHARP_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SHARP_LEFT)
          .put(
              Maneuver.ON_RAMP_SHARP_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SHARP_RIGHT)
          .put(
              Maneuver.ON_RAMP_U_TURN_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_U_TURN_RIGHT)
          .put(
              Maneuver.ON_RAMP_U_TURN_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_U_TURN_LEFT)
          .put(
              Maneuver.OFF_RAMP_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_NORMAL_LEFT)
          .put(
              Maneuver.OFF_RAMP_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_NORMAL_RIGHT)
          .put(
              Maneuver.OFF_RAMP_KEEP_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT)
          .put(
              Maneuver.OFF_RAMP_KEEP_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT)
          .put(
              Maneuver.OFF_RAMP_SLIGHT_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT)
          .put(
              Maneuver.OFF_RAMP_SLIGHT_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT)
          .put(
              Maneuver.OFF_RAMP_SHARP_LEFT,
              androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_NORMAL_LEFT)
          .put(
              Maneuver.OFF_RAMP_SHARP_RIGHT,
              androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_NORMAL_RIGHT)
          .put(
              Maneuver.ROUNDABOUT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW)
          .put(
              Maneuver.ROUNDABOUT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW)
          .put(
              Maneuver.ROUNDABOUT_STRAIGHT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW)
          .put(
              Maneuver.ROUNDABOUT_STRAIGHT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CCW)
          .put(
              Maneuver.ROUNDABOUT_LEFT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_LEFT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_RIGHT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_RIGHT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_SLIGHT_LEFT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_SLIGHT_LEFT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_SLIGHT_RIGHT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_SLIGHT_RIGHT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_SHARP_LEFT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_SHARP_LEFT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_SHARP_RIGHT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_SHARP_RIGHT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_U_TURN_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_U_TURN_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver
                  .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
          .put(
              Maneuver.ROUNDABOUT_EXIT_CLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_EXIT_CW)
          .put(
              Maneuver.ROUNDABOUT_EXIT_COUNTERCLOCKWISE,
              androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_EXIT_CCW)
          .put(Maneuver.FERRY_BOAT, androidx.car.app.navigation.model.Maneuver.TYPE_FERRY_BOAT)
          .put(Maneuver.FERRY_TRAIN, androidx.car.app.navigation.model.Maneuver.TYPE_FERRY_TRAIN)
          .put(Maneuver.NAME_CHANGE, androidx.car.app.navigation.model.Maneuver.TYPE_NAME_CHANGE)
          .buildOrThrow();

  /** Represents the roundabout turn angle for a slight turn in either right or left directions. */
  private static final int ROUNDABOUT_ANGLE_SLIGHT = 10;

  /** Represents the roundabout turn angle for a normal turn in either right or left directions. */
  private static final int ROUNDABOUT_ANGLE_NORMAL = 45;

  /** Represents the roundabout turn angle for a sharp turn in either right or left directions. */
  private static final int ROUNDABOUT_ANGLE_SHARP = 135;

  /** Represents the roundabout turn angle for a u-turn in either right or left directions. */
  private static final int ROUNDABOUT_ANGLE_U_TURN = 180;

  /**
   * Returns the corresponding {@link androidx.car.app.navigation.model.Maneuver.Type} for the given
   * direction {@link Maneuver}
   *
   * @throws {@link IllegalArgumentException} if the given maneuver does not have a corresponding
   *     Android Auto Maneuver type.
   */
  public static int getAndroidAutoManeuverType(@Maneuver int maneuver) {
    if (MANEUVER_TO_ANDROID_AUTO_MANEUVER_TYPE.containsKey(maneuver)) {
      return MANEUVER_TO_ANDROID_AUTO_MANEUVER_TYPE.get(maneuver);
    }
    throw new IllegalArgumentException(
        String.format(
            "Given turn-by-turn Maneuver %d cannot be converted to an Android Auto equivalent.",
            maneuver));
  }

  /**
   * Returns the corresponding Android Auto roundabout angle for the given turn {@link Maneuver}.
   * Returns {@code null} if given maneuver does not involve a roundabout with a turn.
   */
  @Nullable
  public static Integer getAndroidAutoRoundaboutAngle(@Maneuver int maneuver) {
    if (maneuver == Maneuver.ROUNDABOUT_LEFT_CLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_RIGHT_CLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_LEFT_COUNTERCLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_RIGHT_COUNTERCLOCKWISE) {
      return ROUNDABOUT_ANGLE_NORMAL;
    }
    if (maneuver == Maneuver.ROUNDABOUT_SHARP_LEFT_CLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_SHARP_RIGHT_CLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_SHARP_LEFT_COUNTERCLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_SHARP_RIGHT_COUNTERCLOCKWISE) {
      return ROUNDABOUT_ANGLE_SHARP;
    }
    if (maneuver == Maneuver.ROUNDABOUT_SLIGHT_LEFT_CLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_SLIGHT_RIGHT_CLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_SLIGHT_LEFT_COUNTERCLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_SLIGHT_RIGHT_COUNTERCLOCKWISE) {
      return ROUNDABOUT_ANGLE_SLIGHT;
    }
    if (maneuver == Maneuver.ROUNDABOUT_U_TURN_CLOCKWISE
        || maneuver == Maneuver.ROUNDABOUT_U_TURN_COUNTERCLOCKWISE) {
      return ROUNDABOUT_ANGLE_U_TURN;
    }
    return null;
  }
}