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

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

จอภาพส่วนกลางในแดชบอร์ดที่แสดงการนําทางด้วยระบบนำทางโดยใช้แอปที่พร้อมใช้งานสำหรับ Android Auto

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

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

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

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

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

ตั้งค่า

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

เริ่มต้นใช้งาน Android Auto

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

ทำตามขั้นตอนต่อไปนี้ ซึ่งดูทั้งหมดได้ในเอกสารประกอบสำหรับนักพัฒนาแอป Android for Cars

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

ตั้งค่า Navigation SDK

เมื่อสร้างบริการแอปรถยนต์แล้ว คุณก็พร้อมใช้งาน Navigation SDK

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

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

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

คลาส NavigationViewForAuto จะแสดงผลแผนที่และ UI การนำทางบนหน้าจอ Android Auto ฟีเจอร์นี้มีฟังก์ชันการทำงานส่วนใหญ่เหมือนกับ NavigationView สําหรับโทรศัพท์ แต่มีการโต้ตอบแบบจำกัด ใช้ NavigationViewForAuto เพื่อวาดบนพื้นผิวที่ 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 สอดคล้องกับท่าทางสัมผัสแตะและบีบนิ้วของผู้ใช้ Callbacks แบบอินเทอร์แอกทีฟต้องใช้ map action strip ดังนี้

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

เลี้ยวต่อเลี้ยวในคลัง Android Auto Maneuver
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;
  }
}