פיתוח אפליקציית ניווט

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

הצהרה על תמיכה בניווט במניפסט

באפליקציית הניווט צריך להצהיר על androidx.car.app.category.NAVIGATION קטגוריית אפליקציית הרכב במסנן ה-Intent של CarAppService:

<application>
    ...
   <service
       ...
        android:name=".MyNavigationCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService" />
        <category android:name="androidx.car.app.category.NAVIGATION"/>
      </intent-filter>
    </service>
    ...
</application>

תמיכה בכוונות ניווט

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

כדי לתמוך בפורמטים האלה של Intent, צריך קודם להצהיר על תמיכה על ידי הוספת מסנני Intent במניפסט של האפליקציה. המיקום של מסנני הכוונות האלה תלוי בפלטפורמה:

  • Android Auto: ברכיב המניפסט <activity> של Activity שמשמש לטיפול ב-Intent כשמשתמש לא משתמש ב-Android Auto.
  • Android Automotive OS: ברכיב <activity> manifest של CarAppActivity.

לאחר מכן, קוראים את הכוונות ומטפלים בהן בקריאות החוזרות (callback) של onCreateScreen() ושל onNewIntent() בהטמעה של Session באפליקציה.

פורמטים נדרשים של כוונות

כדי לעמוד בדרישת האיכות של NF-6, האפליקציה צריכה לטפל בכוונה להפעיל ניווט.

פורמטים אופציונליים של כוונות

כדי לשפר עוד יותר את יכולת הפעולה ההדדית של האפליקציה, אפשר גם לתמוך בפורמטים הבאים של כוונות:

גישה לתבניות הניווט

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

  • NavigationTemplate: תבנית שמציגה הודעת מידע אופציונלית והערכות לגבי משך הנסיעה במהלך הניווט הפעיל.
  • MapWithContentTemplate: תבנית שמאפשרת לאפליקציה לעבד משבצות של מפה עם תוכן מסוים (לדוגמה, רשימה). התוכן מוצג בדרך כלל כשכבת-על מעל אריחי המפה, והאזורים הגלויים והקבועים במפה מותאמים לתוכן.

מידע נוסף על עיצוב ממשק המשתמש של אפליקציית הניווט באמצעות התבניות האלה זמין במאמר אפליקציות ניווט.

כדי לקבל גישה לתבניות הניווט, האפליקציה צריכה להצהיר על ההרשאה androidx.car.app.NAVIGATION_TEMPLATES בקובץ AndroidManifest.xml שלה:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES"/>
  ...
</manifest>

נדרשת הרשאה נוספת כדי לצייר מפות.

מעבר ל-MapWithContentTemplate

החל מרמת Car App API 7, ‏ התגים MapTemplate, PlaceListNavigationTemplate, ו-RoutePreviewNavigationTemplate הוצאו משימוש. התמיכה בתבניות שהוצאו משימוש תימשך, אבל מומלץ מאוד לעבור אל MapWithContentTemplate.

אפשר להטמיע את הפונקציונליות שמסופקת על ידי התבניות האלה באמצעות MapWithContentTemplate. דוגמאות לקטעי קוד:

MapTemplate

// MapTemplate (deprecated)
val templateDeprecated = MapTemplate.Builder()
    .setPane(paneBuilder.build())
    .setActionStrip(actionStrip)
    .setHeader(header)
    .setMapController(mapController)
    .build()

// MapWithContentTemplate
val template = MapWithContentTemplate.Builder()
    .setContentTemplate(
        PaneTemplate.Builder(paneBuilder.build())
            .setHeader(header)
            .build()
    )
    .setActionStrip(actionStrip)
    .setMapController(mapController)
    .build()

PlaceListNavigationTemplate

// PlaceListNavigationTemplate (deprecated)
val templateDeprecated = PlaceListNavigationTemplate.Builder()
    .setItemList(itemListBuilder.build())
    .setHeader(header)
    .setActionStrip(actionStrip)
    .setMapActionStrip(mapActionStrip)
    .build()

// MapWithContentTemplate
val template = MapWithContentTemplate.Builder()
    .setContentTemplate(
        ListTemplate.Builder()
            .setSingleList(itemListBuilder.build())
            .setHeader(header)
            .build()
    )
    .setActionStrip(actionStrip)
    .setMapController(
        MapController.Builder()
            .setMapActionStrip(mapActionStrip)
            .build()
    )
    .build()

RoutePreviewNavigationTemplate

// RoutePreviewNavigationTemplate (deprecated)
val templateDeprecated = RoutePreviewNavigationTemplate.Builder()
    .setItemList(
        ItemList.Builder()
            .addItem(
                Row.Builder()
                    .setTitle(title)
                    .build()
            )
            .build()
    )
    .setHeader(header)
    .setNavigateAction(
        Action.Builder()
            .setTitle(actionTitle)
            .setOnClickListener { /* onClick */ }
            .build()
    )
    .setActionStrip(actionStrip)
    .setMapActionStrip(mapActionStrip)
    .build()

// MapWithContentTemplate
val template = MapWithContentTemplate.Builder()
    .setContentTemplate(
        ListTemplate.Builder()
            .setSingleList(
                ItemList.Builder()
                    .addItem(
                        Row.Builder()
                            .setTitle(title)
                            .addAction(
                                Action.Builder()
                                    .setTitle(actionTitle)
                                    .setOnClickListener { /* onClick */ }
                                    .build()
                            )
                            .build()
                    )
                    .build()
            )
            .setHeader(header)
            .build()
    )
    .setActionStrip(actionStrip)
    .setMapController(
        MapController.Builder()
            .setMapActionStrip(mapActionStrip)
            .build()
    )
    .build()

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

המטא-נתונים לניווט מסופקים דרך שירות המכוניות NavigationManager שאפשר לגשת אליו מ-CarContext:

val navigationManager = carContext.getCarService(NavigationManager::class.java)

התחלה, סיום ועצירה של הניווט

כדי שהמארח יוכל לנהל כמה אפליקציות ניווט, התראות על מסלולים ונתונים של לוח המחוונים ברכב, הוא צריך לדעת מהו המצב הנוכחי של הניווט. כשמשתמש מתחיל בניווט, מתבצעת קריאה אל NavigationManager.navigationStarted. באופן דומה, כשהניווט מסתיים – למשל, כשהמשתמש מגיע ליעד או מבטל את הניווט – צריך להפעיל את NavigationManager.navigationEnded.

מפעילים את NavigationManager.navigationEnded רק כשמשתמש מסיים את הניווט. לדוגמה, אם אתם צריכים לחשב מחדש את המסלול באמצע הנסיעה, השתמשו במקום זאת ב-Trip.Builder.setLoading(true).

לפעמים המארח צריך שאפליקציה תפסיק את הניווט והשיחות onStopNavigation באובייקט NavigationManagerCallback שסופק על ידי האפליקציה באמצעות NavigationManager.setNavigationManagerCallback. לאחר מכן, האפליקציה צריכה להפסיק להציג מידע על הפנייה הבאה בתצוגת הצבירים, בהתראות הניווט ובהנחיות הקוליות.

עדכון פרטי הנסיעה

במהלך ניווט פעיל, מתקשרים אל NavigationManager.updateTrip. המידע שמועבר בשיחה הזו יכול לשמש את לוח המחוונים ואת התצוגה העילית של הרכב. בהתאם לרכב הספציפי שבו נוהגים, לא כל המידע מוצג למשתמש. לדוגמה, ב-Desktop Head Unit‏ (DHU) מוצג Step שנוסף ל-Trip, אבל לא מוצג המידע Destination.

ציור בלוח המחוונים

כדי לספק חוויית משתמש סוחפת ככל האפשר, כדאי להציג יותר ממטא-נתונים בסיסיים בלוח המחוונים של הרכב. החל מרמת Car App API 6, לאפליקציות ניווט יש אפשרות להציג את התוכן שלהן ישירות במסך של לוח המחוונים (ברכבים נתמכים), עם המגבלות הבאות:

  • ממשק ה-API של תצוגת האשכול לא תומך בפקדי קלט
  • הנחיות איכות לאפליקציות למכוניות NF-9: בלוח המחוונים צריכות להופיע רק משבצות של מפות. אפשר להציג במשבצות האלה מסלול ניווט פעיל.
  • ה-API של תצוגת האשכולות תומך רק בשימוש ב-NavigationTemplate
    • בניגוד למסכים הראשיים, יכול להיות שבמסכים של לוחות מחוונים לא יוצגו באופן עקבי כל NavigationTemplateרכיבי ממשק המשתמש, כמו הוראות הגעה מפורטות, כרטיסי הערכת זמן ההגעה ופעולות. משבצות המפה הן הרכיב היחיד בממשק המשתמש שמוצג באופן עקבי.

הצהרה על תמיכה באשכול

כדי שהאפליקציה המארחת תדע שהאפליקציה שלכם תומכת ברינדור בתצוגות של אשכולות, צריך להוסיף רכיב androidx.car.app.category.FEATURE_CLUSTER <category> ל-CarAppService של <intent-filter>, כמו שמוצג בקטע הקוד הבא:

<application>
    ...
   <service
       ...
        android:name=".MyNavigationCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService" />
        <category android:name="androidx.car.app.category.NAVIGATION"/>
        <category android:name="androidx.car.app.category.FEATURE_CLUSTER"/>
      </intent-filter>
    </service>
    ...
</application>

ניהול מחזור החיים והמצב

החל מרמת API‏ 6, זרימת מחזור החיים של אפליקציית הרכב נשארת זהה, אבל עכשיו הפונקציה CarAppService::onCreateSession מקבלת פרמטר מסוג SessionInfo שמספק מידע נוסף על Session שנוצר (כלומר, סוג התצוגה וקבוצת התבניות הנתמכות).

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

override fun onCreateSession(sessionInfo: SessionInfo): Session {
    return if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) {
        ClusterSession()
    } else {
        MainDisplaySession()
    }
}

אין ערובה לכך שהתצוגה המקובצת תסופק, או מתי היא תסופק, וגם יכול להיות שהאשכול Session יהיה האשכול היחיד Session (לדוגמה, אם המשתמש החליף את התצוגה הראשית לאפליקציה אחרת בזמן שהאפליקציה שלכם מנווטת באופן פעיל). ההסכם הסטנדרטי הוא שהאפליקציה מקבלת שליטה בתצוגת האשכול רק אחרי שמתבצעת קריאה ל-NavigationManager::navigationStarted. עם זאת, יכול להיות שהאפליקציה תקבל את התצוגה של לוח המכוונים גם כשלא מתבצעת ניווט פעיל, או שהיא אף פעם לא תקבל את התצוגה של לוח המכוונים. האחריות לטיפול בתרחישים האלה מוטלת על האפליקציה שלכם, באמצעות עיבוד של מצב ההמתנה של משבצות המפה באפליקציה.

המארח יוצר מופעים נפרדים של CarContext ושל קובץ מאגד לכל Session. כלומר, כשמשתמשים ב-methods כמו ScreenManager::push או Screen::invalidate, רק Session שממנו הן נקראות מושפע. אם האפליקציות צריכות לתקשר ביניהן, הן צריכות ליצור ערוצי תקשורת משלהן בין המופעים האלה (לדוגמה, באמצעות שידורים, singleton משותף או משהו אחר).Session

בדיקת התמיכה באשכול

אפשר לבדוק את ההטמעה ב-Android Auto וב-Android Automotive OS. ב-Android Auto, כדי לעשות את זה צריך להגדיר את Desktop Head Unit כך שידמה מסך משני של לוח מחוונים. ב-Android Automotive OS, ‏ תמונות המערכת הכלליות לרמת API‏ 30 ומעלה מדמות תצוגת לוח מחוונים.

התאמה אישית של רכיב TravelEstimate באמצעות טקסט או סמל

כדי להתאים אישית את הערכת זמן הנסיעה באמצעות טקסט, סמל או שניהם, משתמשים בשיטות TravelEstimate.Builder, ‏ setTripIcon או setTripText של המחלקה. התג NavigationTemplate משתמש בערך TravelEstimate כדי להגדיר טקסט וסמלים לצד הזמן המשוער להגעה, הזמן שנותר והמרחק שנותר, או במקומם.

איור 1. הערכת נסיעה עם סמל וטקסט בהתאמה אישית.

בקטע הקוד הבא נעשה שימוש ב-setTripIcon וב-setTripText כדי להתאים אישית את הערכת משך הנסיעה:

TravelEstimate.Builder(
    Distance.create(350.0, Distance.UNIT_METERS),
    arrivalTimeAtDestination
)
    .setTripIcon(
        CarIcon.Builder(
            IconCompat.createWithResource(carContext, R.drawable.ic_garage)
        ).build()
    )
    .setTripText(CarText.create("Custom Text"))
    .build()

קבלת התראות מסלול מפורט

הסוכן מספק הוראות ניווט מפורטות באמצעות התראה על ניווט שמתעדכנת לעיתים קרובות. כדי שההתראה תופיע במסך הרכב כהתראה על ניווט, האובייקט לבניית ההתראה צריך:

  1. סימון ההתראה כפעולה מתמשכת באמצעות השיטה NotificationCompat.Builder.setOngoing.
  2. מגדירים את הקטגוריה של ההתראה ל-Notification.CATEGORY_NAVIGATION.
  3. להרחיב את ההתראה באמצעות CarAppExtender.

התראה על ניווט מוצגת בווידג'ט של פס הניווט בחלק התחתון של מסך הרכב. אם רמת החשיבות של ההתראה מוגדרת ל-IMPORTANCE_HIGH, היא מוצגת גם כהתראת "שימו לב". אם לא מגדירים את רמת החשיבות באמצעות השיטה CarAppExtender.Builder.setImportance, המערכת משתמשת ברמת החשיבות של ערוץ ההתראות.

האפליקציה יכולה להגדיר PendingIntent ב-CarAppExtender שנשלח לאפליקציה כשהמשתמש מקיש על ה-HUN או על הווידג'ט של סרגל הניווט.

אם הפונקציה NotificationCompat.Builder.setOnlyAlertOnce מופעלת עם הערך true, התראה עם חשיבות גבוהה תופיע רק פעם אחת ב-HUN.

בקטע הקוד הבא מוצג איך ליצור התראה על ניווט:

NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setOnlyAlertOnce(true)
    .setOngoing(true)
    .setCategory(NotificationCompat.CATEGORY_NAVIGATION)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(carScreenTitle)
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_OPEN_APP.hashCode(),
                    Intent(ACTION_OPEN_APP).setComponent(
                        ComponentName(context, MyNotificationReceiver::class.java)
                    ),
                    PendingIntent.FLAG_IMMUTABLE
                )
            )
            .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH)
            .build()
    )
    .build()

עדכון קבוע של ההתראה על הזמן שנותר עד להגעה במקרה של שינויים במרחק, שמתעדכן בווידג'ט של הרכבת, והצגת ההתראה רק כהתראה מסוג HUN. אפשר לשלוט בהתנהגות של HUN על ידי הגדרת החשיבות של ההתראה באמצעות CarAppExtender.Builder.setImportance. הגדרת החשיבות לערך IMPORTANCE_HIGH מציגה HUN. אם מגדירים ערך אחר, רק הווידג'ט של הפס מתעדכן.

רענון התוכן של PlaceListNavigationTemplate

אתם יכולים לאפשר לנהגים לרענן את התוכן בלחיצת כפתור בזמן שהם מעיינים ברשימות של מקומות שנוצרו באמצעות PlaceListNavigationTemplate. כדי להפעיל רענון של הרשימה, מטמיעים את הממשק OnContentRefreshListener של השיטה onContentRefreshRequested ומשתמשים ב-PlaceListNavigationTemplate.Builder.setOnContentRefreshListener כדי להגדיר את מאזין התבנית.

בקטע הקוד הבא אפשר לראות איך מגדירים את מאזין התבנית:

PlaceListNavigationTemplate.Builder()
    .setOnContentRefreshListener {
        // Execute any desired logic
        // Then call invalidate() so onGetTemplate() is called again
        screen.invalidate()
    }
    .build()

לחצן הרענון מוצג רק בכותרת של התג PlaceListNavigationTemplate אם יש ערך למאזין.

כשמשתמש לוחץ על לחצן הרענון, מתבצעת קריאה לשיטה onContentRefreshRequested של ההטמעה של OnContentRefreshListener. בתוך onContentRefreshRequested, מבצעים קריאה ל-method‏ Screen.invalidate. המארח מתקשר חזרה לשיטה Screen.onGetTemplate של האפליקציה כדי לאחזר את התבנית עם התוכן המעודכן. מידע נוסף על רענון תבניות זמין במאמר רענון התוכן של תבנית. כל עוד התבנית הבאה שמוחזרת על ידי onGetTemplate היא מאותו סוג, היא נחשבת לרענון ולא נספרת במכסת התבניות.

מתן הנחיות קוליות

כדי להשמיע את הוראות הניווט ברמקולים של הרכב, האפליקציה צריכה לבקש מיקוד אודיו. כחלק מAudioFocusRequest, הגדרת את השימוש כ-AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE. בנוסף, מגדירים את הגברת הפוקוס כ-AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

סימולציה של הניווט

כדי לאמת את פונקציונליות הניווט של האפליקציה כששולחים אותה לחנות Google Play, צריך להטמיע באפליקציה את הקריאה החוזרת NavigationManagerCallback.onAutoDriveEnabled. כשמתבצעת קריאה לקריאה החוזרת הזו, האפליקציה צריכה לדמות ניווט ליעד שנבחר כשהמשתמש מתחיל בניווט. האפליקציה יכולה לצאת מהמצב הזה בכל פעם שמחזור החיים של Session הנוכחי מגיע למצב Lifecycle.Event.ON_DESTROY.

כדי לבדוק שההטמעה של onAutoDriveEnabled נקראת, מריצים את הפקודה הבאה משורת הפקודה:

adb shell dumpsys activity service CAR_APP_SERVICE_NAME AUTO_DRIVE

כך זה נראה בדוגמה הבאה:

adb shell dumpsys activity service androidx.car.app.samples.navigation.car.NavigationCarAppService AUTO_DRIVE

אפליקציית הניווט שמוגדרת כברירת מחדל ברכב

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

הצגת התראות ניווט בהקשר

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

התכונה Alert זמינה רק ב-NavigationTemplate. כדי להודיע למשתמש מחוץ ל-NavigationTemplate, כדאי להשתמש בהתראת "שימו לב", כמו שמוסבר במאמר הצגת התראות.

לדוגמה, אפשר להשתמש ב-Alert כדי:

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

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

איור 2. התראה על ניווט בהקשר.

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

יצירת התראה

כדי ליצור מופע Alert, משתמשים בפקודה Alert.Builder:

Alert.Builder(
    1, // alertId
    CarText.create("Hello"), // title
    5000 // durationMillis
)
    // The fields below are optional
    .addAction(firstAction)
    .addAction(secondAction)
    .setSubtitle(CarText.create("Subtitle"))
    .setIcon(CarIcon.APP_ICON)
    .setCallback(alertCallback)
    .build()

אם רוצים להאזין לAlert ביטול או דחייה, צריך ליצור הטמעה של הממשק AlertCallback. נתיבי השיחות AlertCallback הם:

הגדרת משך ההתראה

בוחרים משך זמן של Alert שמתאים לצרכים של האפליקציה. משך הזמן המומלץ לניווט Alert הוא 10 שניות. מידע נוסף זמין במאמר בנושא התראות ניווט.

הצגת התראה

כדי להציג Alert, צריך להפעיל את ה-method‏ AppManager.showAlert שזמין דרך CarContext של האפליקציה.

carContext.getCarService(AppManager::class.java).showAlert(alert)

  • קריאה ל-showAlert עם Alert שיש לו alertId זהה למזהה של Alert שכבר מוצג לא עושה כלום. הסמל Alert לא מתעדכן. כדי לעדכן Alert, צריך ליצור אותו מחדש עם alertId חדש.
  • קריאה ל-showAlert עם Alert שיש לו alertId שונה מזה של Alert שכבר מוצג מבטלת את ההתראה Alert שמוצגת.

סגירת התראה

Alert נסגר אוטומטית בגלל זמן קצוב לתפוגה או אינטראקציה של הנהג, אבל אפשר גם לסגור Alert באופן ידני, למשל אם המידע שמוצג בו כבר לא עדכני. כדי לבטל Alert, צריך לבצע קריאה ל-method‏ dismissAlert עם alertId של Alert.

carContext.getCarService(AppManager::class.java).dismissAlert(alert.id)

אם מתקשרים אל dismissAlert באמצעות alertId שלא תואם ל-Alert שכבר מוצג, שום דבר לא קורה. לא מתבצעת חריגה.