הפעלת ניווט ל-Android Auto

בקטע הזה מוסבר איך להשתמש ב-Navigation SDK עם ספריית האפליקציות של Android לרכבים כדי להציג את חוויית הניווט של האפליקציה במערכות מולטימדיה בלוח המחוונים. אם המערכת בלוח המחוונים של המשתמש תומכת ב-Android Auto, המשתמשים יכולים לחבר את הטלפון ליחידה ולהשתמש באפליקציה ישירות במסך הרכב. ההנחיה הקולית פועלת גם ברמקולים של הרכב.

מערכת מולטימדיה בלוח המחוונים שמציגה ניווט מודרך באמצעות אפליקציה שמותאמת ל-Android Auto.

ספריית האפליקציות של Android לרכבים מאפשרת לאפליקציות ל-Android לפעול ב-Android Auto באמצעות קבוצה של תבניות חזותיות שאושרו לבטיחות הנהג. התבניות האלה מגבילות בכוונה את הרכיבים האינטראקטיביים בממשק המשתמש של לוח הבקרה, בהשוואה לאלה של הטלפון, כדי להפחית את הסחת הדעת של הנהג.

כשמפעילים את האפליקציה שמבוססת על Navigation SDK כך שתפעל עם Android Auto, מקבלים תצוגה נוספת של חוויית הניווט. כך אפשר לראות שתי תצוגות של המפה – אחת בטלפון ואחת במערכת המולטימדיה. שני המסכים מקבלים הנחיות מ-Navigator.java, שהוא סינגלטון.

המערכת בלוח הבקרה מציגה את הרכיבים האינטראקטיביים שאושרו מבחינת בטיחות, כדי שהמשתמש יוכל לנווט בבטחה ליעד שלו בלי הסחות דעת מיותרות. המשתמש יכול גם להשתמש בפונקציונליות הספציפית לאפליקציה, כמו אישור או דחייה של הזמנות, או צפייה במיקום של הלקוח במפה. עדכונים על סטטוס ההזמנה יכולים להופיע גם ביחידה שבלוח הבקרה.

מערכת מולטימדיה בלוח המחוונים שמציגה מסלול מפורט עם Android Auto. טלפון Android שמוצג בו אותו מסלול כסקירה כללית.

הטלפון המחובר יכול להמשיך להציג את חוויית השימוש הרגילה ב-Navigation SDK או כל תצוגה או תהליך עבודה אחרים באפליקציה. התכונה הזו מאפשרת לכם להמשיך לספק פונקציונליות בהתאמה אישית, שאולי לא תפעל כמו שצריך במסך של הרכב.

הגדרה

החלק הראשון בהפעלת האפליקציה עם Android Auto כולל הגדרה של שירות לרכב עם Android Auto, ואז הפעלה של ספריית TurnByTurn באפליקציית Navigation SDK.

איך מתחילים להשתמש ב-Android Auto

לפני שמתחילים לעבוד עם התכונות של Navigation SDK שמיועדות ל-Android Auto, צריך להגדיר שירות לרכב באפליקציה כדי ש-Android Auto יוכל לזהות אותה.

פועלים לפי השלבים הבאים, שכולם מופיעים במאמרי העזרה למפתחים של Android לרכבים:

  1. הכירו את התכונות הבסיסיות של Android Auto.
  2. מתקינים את ספריית האפליקציות של Android לרכבים.
  3. מגדירים את קובץ המניפסט של האפליקציה כך שיכלול את Android Auto.
  4. מצהירים במניפסט על רמה מינימלית של אפליקציה לרכב של 1.
  5. יוצרים את CarAppService והסשן.

הגדרת Navigation SDK

אחרי שהגדרתם את שירות האפליקציה לרכב, אתם יכולים להתחיל לעבוד עם Navigation SDK.

  1. מגדירים את הפרויקט, אם עדיין לא שילבתם את Navigation SDK באפליקציה.
  2. מפעילים את פיד ההנחיות המפורטות באפליקציה.
  3. אופציונלי. שימוש בסמלים שנוצרו מ-Navigation SDK.
  4. משרטטים את המפה באמצעות המחלקה NavigationViewForAuto ב-Android Auto Surface שסופקה במחלקה Screen.
  5. מאכלסים את תבנית הניווט של Android Auto בנתונים מספריית TurnbyTurn.

עכשיו, אחרי שרשמתם שירות שמספק מידע על ניווט לאפליקציה, והאפליקציה יכולה להתחבר ל-Android Auto, אתם יכולים ליצור את שאר רכיבי הניווט שנדרשים כדי שהאפליקציה תפעל כמו שצריך עם Android Auto:

שרטוט ממשק המשתמש של המפה והניווט

המחלקה NavigationViewForAuto מעבדת ממשק משתמש של מפה וניווט במסכי 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 תואמים לתנועות הקשה וצביטה של המשתמש. בקריאות חוזרות (callback) של אינטראקטיביות צריך להשתמש ברצועת הפעולות במפה באופן הבא:

  • כדי לקבל קריאות חוזרות (callbacks) של אינטראקטיביות במפה, האפליקציה צריכה להשתמש בכפתור Action.PAN.

  • כדי לתמוך בפעולות נוספות של המשתמשים, אפשר להוסיף לחצנים לרצועת הפעולות במפה.

הפעלת קריאות חוזרות (callback) של פלטפורמות

@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 מספקת. עם זאת, צריך להמיר את התמרונים של 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;
  }
}