เปิดใช้การนําทางสําหรับ Android Auto

หัวข้อนี้จะอธิบายวิธีที่คุณสามารถใช้ SDK การนำทางกับไลบรารีแอป Android สำหรับรถยนต์ เพื่อแสดงประสบการณ์การนำทางของแอปบนเครื่องเล่นวิทยุในรถยนต์ หากระบบติดตั้งในรถยนต์ของ คนขับรองรับ Android Auto ผู้ขับจะสามารถใช้แอปของคุณได้โดยตรงบนจอแสดงผลของรถโดยเชื่อมต่อโทรศัพท์เข้ากับอุปกรณ์ การนำทางด้วยเสียงจะทำงานบนลำโพงรถยนต์ด้วย

เครื่องเสียงติดรถยนต์ที่แสดงการนำทางพร้อมคำแนะนำโดยใช้แอปที่เปิดใช้สำหรับ Android Auto

ไลบรารีแอป Android สำหรับรถยนต์ ช่วยให้แอปพลิเคชัน Android ทำงานใน Android Auto ได้ด้วยชุดเทมเพลตภาพที่ได้รับการอนุมัติเพื่อความปลอดภัยของผู้ขับ เทมเพลตเหล่านี้จงใจจำกัดตัวควบคุม UI ในแดชบอร์ดจากโทรศัพท์เพื่อลดสิ่งรบกวนผู้ขับ

เมื่อคุณเปิดใช้แอปที่ขับเคลื่อนโดย SDK การนำทางเพื่อทำงานร่วมกับ Android Auto เป็นการเพิ่มมุมมองของการนำทาง ซึ่งจะทำให้เกิดมุมมองแผนที่ 2 มุมมอง โดยมุมมองหนึ่งสำหรับโทรศัพท์และอีกมุมมองสำหรับเครื่องเล่นวิทยุ จอแสดงผลทั้ง 2 จอได้รับคําแนะนําจาก Navigator.java ซึ่งเป็น Singleton

ระบบในแดชบอร์ดแสดงองค์ประกอบแบบอินเทอร์แอกทีฟที่ได้รับการอนุมัติด้านความปลอดภัย เพื่อให้คนขับนำทางไปยังจุดหมายได้อย่างปลอดภัยโดยไม่มีสิ่งรบกวนอย่างไม่เหมาะสม นอกจากนี้ คนขับยังสามารถโต้ตอบกับ ฟังก์ชันเฉพาะแอปของคุณ เช่น การยอมรับหรือปฏิเสธคำสั่งซื้อ หรือ การดูตำแหน่งของลูกค้าบนแผนที่ การอัปเดตสถานะคำสั่งซื้อ ยังปรากฏในหน่วยแดชบอร์ดด้วย

เครื่องเสียงติดรถยนต์ที่แสดงคำแนะนำแบบเลี้ยวต่อเลี้ยวด้วย Android Auto โทรศัพท์ Android ที่แสดงเส้นทางเดียวกันกับภาพรวม

โทรศัพท์ที่เชื่อมต่อไว้ยังคงแสดงประสบการณ์ใช้งาน SDK การนำทางมาตรฐาน หรือมุมมองหรือเวิร์กโฟลว์อื่นๆ ในแอปพลิเคชันของคุณต่อไปได้ วิธีนี้จะช่วยให้คุณมอบฟังก์ชันที่กำหนดเอง ที่อาจทำงานได้ไม่ดีในหน้าจอรถต่อไป

ตั้งค่า

ส่วนแรกของการทำให้แอปทำงานร่วมกับ Android Auto ได้นั้นคือการตั้งค่า บริการรถยนต์ด้วย Android Auto จากนั้นเปิดใช้ไลบรารี TurnByTurn ในแอป Navigation SDK ของคุณ

เริ่มต้นด้วย Android Auto

ก่อนที่จะเริ่มทำงานกับฟีเจอร์ Navigation SDK ที่ออกแบบมาให้ทำงานร่วมกับ Android Auto คุณต้องตั้งค่าบริการรถยนต์สำหรับ แอปเพื่อให้ Android Auto ค้นพบได้

ทำตามขั้นตอนเหล่านี้ ซึ่งทั้งหมดสามารถดูได้ในเอกสารประกอบสำหรับนักพัฒนาซอฟต์แวร์ Android สำหรับรถยนต์

  1. ทำความคุ้นเคย กับฟีเจอร์พื้นฐานของ Android Auto
  2. ติดตั้ง ไลบรารีแอป Android สำหรับรถยนต์
  3. กำหนดค่า ไฟล์ Manifest ของแอปให้รวม Android Auto
  4. ประกาศ ระดับแอปสำหรับรถยนต์ขั้นต่ำ 1 ระดับในไฟล์ Manifest
  5. สร้าง CarAppService และเซสชันของคุณ

ตั้งค่า Navigation SDK

เมื่อสร้างบริการแอปสำหรับรถแล้ว คุณก็พร้อมทำงานกับ Navigation SDK

  1. ตั้งค่าโปรเจ็กต์หากยังไม่ได้ผสานรวม Navigation SDK เข้ากับแอป
  2. เปิดใช้ฟีดคำแนะนำ TurnbyTurnสำหรับแอปของคุณ
  3. ไม่บังคับ ใช้ไอคอนที่สร้างขึ้น จาก Navigation SDK
  4. วาดแผนที่โดยใช้คลาส NavigationViewForAuto บน Surface ของ Android Auto ที่ให้ไว้ในคลาส Screen
  5. ป้อนข้อมูลเทมเพลตการนำทางของ Android Auto ด้วยข้อมูลจากไลบรารี TurnbyTurn

ตอนนี้คุณมีบริการที่ลงทะเบียนไว้สำหรับให้ข้อมูลการไปยังส่วนต่างๆ ในแอปและแอปของคุณเชื่อมต่อกับ Android Auto ได้แล้ว คุณก็พร้อมที่จะสร้างองค์ประกอบการนำทางส่วนที่เหลือที่จำเป็นเพื่อให้แอปทำงานร่วมกับ Android Auto ได้อย่างถูกต้อง ดังนี้

วาด UI แผนที่และการนำทาง

คลาส NavigationViewForAuto จะแสดงผลแผนที่และ UI การนำทางในหน้าจอ Android Auto โดยมีฟังก์ชันการทำงานเหมือนกับ NavigationView สำหรับโทรศัพท์เป็นส่วนใหญ่ แต่มีความสามารถในการโต้ตอบที่จำกัด ใช้ NavigationViewForAuto เพื่อวางบน Surface ที่ให้บริการโดย Android Auto ดังนี้

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 หลายๆ วิธีเพื่อความปลอดภัยของผู้ขับ ใช้โค้ดเรียกกลับเหล่านี้เพื่อรองรับการโต้ตอบของผู้ขับขี่แบบจำกัดกับแผนที่บนหน้าจอในแดชบอร์ด ตัวอย่างเช่น onClick และ onScale สอดคล้องกับท่าทางสัมผัสแตะและบีบจากผู้ใช้ โค้ดเรียกกลับของการโต้ตอบต้องใช้แถบการทำงานของแผนที่ ดังนี้

  • ในการรับโค้ดเรียกกลับสำหรับการโต้ตอบบนแผนที่ แอปของคุณต้องใช้ปุ่ม 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 จะมีการ์ดเลี้ยวที่แสดงข้อมูลการนำทางที่เกี่ยวข้องกับการเดินทางปัจจุบัน ไลบรารี TurnByTurn ใน Navigation SDK จะให้ข้อมูลการนำทางนี้ ซึ่งโค้ดของคุณจะใช้ในการสร้างเทมเพลตการนำทางของ Android Auto

ตั้งค่าผู้สังเกตการณ์

ในตัวอย่างต่อไปนี้ SampleApplication เป็นคลาสแอปพลิเคชันที่กำหนดเองซึ่งเก็บรักษาออบเจ็กต์ MutableLiveData<NavInfo> เมื่อผู้สังเกตการณ์ได้รับอัปเดตจากออบเจ็กต์ตัวนำทาง ก็จะโพสต์ออบเจ็กต์ NavInfo นี้ไปยัง NavInfoMutableLiveData ที่คลาส SampleApplication ดูแล

ตัวอย่างต่อไปนี้บันทึกผู้สังเกตการณ์สำหรับออบเจ็กต์นี้ในการติดตั้งใช้งานหน้าจอ 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 แบบหนึ่งต่อหนึ่ง อย่างไรก็ตาม คุณต้องแปลงการนำทาง 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;
  }
}