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

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

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

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

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

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

יחידה ראשית (HU) במרכז השליטה של הרכב שמציגה מסלול מפורט עם 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. מגדירים את קובץ המניפסט של האפליקציה כך שיכלול את Android Auto.
  4. מגדירים ברמה 1 לפחות את האפליקציה לרכב במניפסט.
  5. יוצרים את CarAppService ואת הסשן.

הגדרת Navigation SDK

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

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

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

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

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

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

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

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

@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;
  }
}