בדף הזה מוסבר איך לבצע פקודות באמצעות אינטראקציה קולית.
ביצוע פקודות מדיה
הפקודות שקשורות למדיה מחולקות לשלוש קבוצות שונות:
- מקורות מדיה חיצוניים (כמו Spotify שמותקן ב-AAOS).
- מקורות מדיה לקצה העורפי (כמו מוזיקה שמשודרת דרך ה-VIA).
- מקורות מדיה מקומיים (למשל רדיו ברכב).
טיפול בפקודות של מקורות מדיה חיצוניים
מקורות מדיה חיצוניים מוגדרים כאפליקציות ל-Android שתומכות בממשקי ה-API MediaSessionCompat
ו-MediaBrowseCompat
(במאמר פיתוח אפליקציות מדיה לרכבים מוסבר בפירוט איך משתמשים בממשקי ה-API האלה).
חשוב: כדי שאפליקציית עוזרת אישית תוכל להתחבר ל-MediaBrowseService
של כל אפליקציות המדיה המותקנות במערכת, היא צריכה:
- להיות מותקנות כחתימות מערכת (ראו הנחיות לפיתוח אפליקציות מדיה ל-AAOS וקוד לדוגמה
PackageValidator
). - יש להם הרשאת
android.permission.MEDIA_CONTENT_CONTROL
ברמת המערכת (ראו הענקת הרשאות ברמת המערכת).
בנוסף ל-MediaBrowserCompat
ול-MediaControllerCompat
, AAOS מספק את השירותים הבאים:
CarMediaService
מספק מידע מרוכז על מקור המדיה שנבחר כרגע. הוא משמש גם להמשך של מקור מדיה שהיה פועל לפני כיבוי המכונית והפעלה מחדש שלה.car-media-common
מספק שיטות נוחות להצגת רשימה של אפליקציות מדיה, לחיבור אליהן וליצירת אינטראקציה איתן.
בהמשך מפורטות הנחיות ספציפיות להטמעה של פקודות נפוצות של אינטראקציה קולית.
הצגת רשימה של מקורות המדיה המותקנים
אפשר לזהות מקורות מדיה באמצעות PackageManager
ולסנן שירותים שתואמים ל-MediaBrowserService.SERVICE_INTERFACE
.
ברכבים מסוימים יכולות להיות הטמעות מיוחדות של שירותי דפדפני מדיה, שצריך להחריג. דוגמה לשימוש בלוגיקה הזו:
private Map<String, MediaSource> getAvailableMediaSources() { List<String> customMediaServices = Arrays.asList(mContext.getResources() .getStringArray(R.array.custom_media_packages)); List<ResolveInfo> mediaServices = mPackageManager.queryIntentServices( new Intent(MediaBrowserService.SERVICE_INTERFACE), PackageManager.GET_RESOLVED_FILTER); Map<String, MediaSource> result = new HashMap<>(); for (ResolveInfo info : mediaServices) { String packageName = info.serviceInfo.packageName; if (customMediaServices.contains(packageName)) { // Custom media sources should be ignored, as they might have a // specialized handling (e.g., radio). continue; } String className = info.serviceInfo.name; ComponentName componentName = new ComponentName(packageName, className); MediaSource source = MediaSource.create(mContext, componentName); result.put(source.getDisplayName().toString().toLowerCase(), source); } return result; }
חשוב לדעת שאפשר להתקין או להסיר מקורות מדיה בכל שלב. כדי לשמור על רשימה מדויקת, מומלץ להטמיע מופע של BroadcastReceiver
לפעולות הכוונה ACTION_PACKAGE_ADDED
, ACTION_PACKAGE_CHANGED
, ACTION_PACKAGE_REPLACED
ו-ACTION_PACKAGE_REMOVED
.
התחברות למקור המדיה שפועל כרגע
CarMediaService
מכיל שיטות לקבלת מקור המדיה שנבחר כרגע, ולזיהוי מתי מקור המדיה הזה משתנה. השינויים האלה יכולים לקרות בגלל שהמשתמש נכנס לאינטראקציה ישירה עם ממשק המשתמש, או בגלל השימוש בלחצני החומרה ברכב. לעומת זאת, הספרייה car-media-common מציעה דרכים נוחות להתחבר למקור מדיה נתון. הנה קטע קוד פשוט שמראה איך להתחבר לאפליקציית המדיה שנבחרה כרגע:
public class MediaActuator implements MediaBrowserConnector.onConnectedBrowserChanged { private final Car mCar; private CarMediaManager mCarMediaManager; private MediaBrowserConnector mBrowserConnector; … public void initialize(Context context) { mCar = Car.createCar(context); mBrowserConnector = new MediaBrowserConnector(context, this); mCarMediaManager = (CarMediaManager) mCar.getCarManager(Car.CAR_MEDIA_SERVICE); mBrowserConnector.connectTo(mCarMediaManager.getMediaSource()); … } @Override public void onConnectedBrowserChanged( @Nullable MediaBrowserCompat browser) { // TODO: Handle connected/disconnected browser } … }
שליטה בהפעלה של מקור המדיה שפועל כרגע
כשMediaBrowserCompat
מחובר, קל לשלוח פקודות לניהול התחבורה לאפליקציית היעד. הנה דוגמה פשוטה:
public class MediaActuator … { … private MediaControllerCompat mMediaController; @Override public void onConnectedBrowserChanged( @Nullable MediaBrowserCompat browser) { if (browser != null && browser.isConnected()) { mMediaController = new MediaControllerCompat(mContext, browser.getSessionToken()); } else { mMediaController = null; } } private boolean playSongOnCurrentSource(String song) { if (mMediaController == null) { // No source selected. return false; } MediaControllerCompat.TransportControls controls = mMediaController.getTransportControls(); PlaybackStateCompat state = controller.getPlaybackState(); if (state == null || ((state.getActions() & PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH) == 0)) { // Source can't play from search return false; } controls.playFromSearch(query, null); return true; } … }
טיפול בפקודות של מקורות מדיה מקומיים (רדיו, נגן CD, Bluetooth, USB)
מקורות מדיה מקומיים חושפים את הפונקציונליות שלהם למערכת באמצעות אותם ממשקי ה-API של MediaSession ו-MediaBrowse שמפורטים למעלה. כדי להתאים את השירותים האלה לצרכים הספציפיים של כל סוג חומרה, הם משתמשים במוסכמות ספציפיות לארגון המידע והפקודות שלהם.
טיפול ברדיו
אפשר לזהות את Radio MediaBrowseService לפי מסנן ה-Intent ACTION_PLAY_BROADCASTRADIO
. הם אמורים לפעול בהתאם לפקדי ההפעלה ולמבנה של דפדוף המדיה שמתוארים בקטע הטמעת רדיו. מערכת AAOS כוללת את הספרייה car-broadcastradio-support
שמכילה קבועים ושיטות שיעזרו ליצרני ציוד מקורי ליצור הטמעות של MediaBrowseService לשירותי הרדיו שלהם בהתאם לפרוטוקול המוגדר, ומספקת תמיכה באפליקציות שמשתמשות בעץ הגלישה שלהם (למשל, VIA).
טיפול בקלט עזר, באודיו מ-CD ובמדיה ב-USB
אין הטמעה שמוגדרת כברירת מחדל של מקורות המדיה האלה כחלק מ-AOSP. הגישה המומלצת היא:
- לבקש מ-OEMs להטמיע שירותי מדיה לכל אחד מהם. פרטים נוספים זמינים במאמר פיתוח אפליקציות מדיה לרכבים.
- הטמעות של MediaBrowseService יזוהו ויקבלו תגובה בפעולות הכוונה שמוגדרות בקטע כוונות כלליות להפעלה.
- השירותים האלה יחשפו עץ גלישה בהתאם להנחיות שמפורטות בקטע סוגי מקורות אחרים.
טיפול ב-Bluetooth
תוכן מדיה ב-Bluetooth נחשף דרך פרופיל Bluetooth של AVRCP. כדי להקל על הגישה לפונקציונליות הזו, מערכת AAOS כוללת הטמעה של MediaBrowserService ו-MediaSession שמבודדת את פרטי התקשורת (מידע נוסף זמין במאמר packages/apps/Bluetooth).
מבנה העץ המתאים של דפדפן המדיה מוגדר בכיתה BrowseTree. אפשר להעביר פקודות לניהול ההפעלה באופן דומה לכל אפליקציה אחרת, באמצעות ההטמעה של MediaSession.
טיפול בפקודות של מדיה בסטרימינג
כדי להטמיע סטרימינג של מדיה בצד השרת, ה-VIA צריך להפוך למקור מדיה בעצמו, ולהטמיע את MediaBrowse ו-MediaSession API. פיתוח אפליקציות מדיה לרכב הטמעת ממשקי ה-API האלה תאפשר לאפליקציה עם שליטה קולית (בין היתר):
- להשתתף בצורה חלקה בבחירת מקור המדיה
- להמשיך אוטומטית אחרי הפעלה מחדש של הרכב
- שליטה בהפעלה ובגלישה באמצעות ממשק המשתמש של Media Center
- קבלת אירועים רגילים של לחצני מדיה בחומרה
ביצוע פקודות ניווט
אין דרך סטנדרטית לאינטראקציה עם כל אפליקציות הניווט. למידע על שילובים עם מפות Google, אפשר לעיין במאמר מפות Google לכוונות (intents) של Android Automotive. כדי לשלב את השירות עם אפליקציות אחרות, צריך לפנות ישירות למפתחי האפליקציות. לפני שמפעילים כוונה באפליקציה כלשהי (כולל מפות Google), צריך לוודא שאפשר לפתור את הכוונה (ראו בקשות כוונה). כך תוכלו להודיע למשתמש אם אפליקציית היעד לא זמינה.
ביצוע פקודות ברכב
הגישה למאפייני הרכב לצורכי קריאה וכתיבה ניתנת באמצעות CarPropertyManager.
הסבר על סוגי המאפיינים של רכבים, על ההטמעה שלהם ועל פרטים נוספים זמין במאמר הגדרות נכסים. כדי לקבל תיאור מדויק של הנכסים שנתמכים ב-Android, מומלץ לעיין ישירות ב-hardware/interfaces/automotive/vehicle/2.0/types.hal
.
המאפיין VehicleProperty שמוגדר שם מכיל מאפיינים רגילים ומאפיינים ספציפיים לספק, סוגי נתונים, מצב שינוי, יחידות והגדרת גישה לקריאה/כתיבה.
כדי לגשת לאותם קבועים מ-Java, אפשר להשתמש ב-VehiclePropertyIds ובכיתות הנלוות שלו. לנכסים שונים יש הרשאות Android שונות ששולטות בגישה שלהם. ההרשאות האלה מוצהרות ב-מניפסט של CarService, והמיפוי בין המאפיינים להרשאות מתואר ב-Javadoc של VehiclePropertyIds ונאכף ב-PropertyHalServiceIds.
קריאת מאפיין של רכב
הדוגמה הבאה מראה איך קוראים את מהירות הרכב:
public class CarActuator ... { private final Car mCar; private final CarPropertyManager mCarPropertyManager; private final TextToSpeech mTTS; /** Global VHAL area id */ public static final int GLOBAL_AREA_ID = 0; public CarActuator(Context context, TextToSpeech tts) { mCar = Car.createCar(context); mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE); mTTS = tts; ... } @Nullable private void getSpeedInMetersPerSecond() { if (!mCarPropertyManager.isPropertyAvailable(VehiclePropertyIds.PERF_VEHICLE_SPEED, GLOBAL_AREA_ID)) { mTTS.speak("I'm sorry, but I can't read the speed of this vehicle"); return; } // Data type and unit can be found in // automotive/vehicle/2.0/types.hal float speedInMps = mCarPropertyManager.getFloatProperty( VehiclePropertyIds.PERF_VEHICLE_SPEED, GLOBAL_AREA_ID); int speedInMph = (int)(speedInMetersPerSecond * 2.23694f); mTTS.speak(String.format("Sure. Your current speed is %d miles " + "per hour", speedInUserUnit); } ... }
הגדרת מאפיין של רכב
הדוגמה הבאה מראה איך להפעיל ולהשבית את המזגן הקדמי.
public class CarActuator … { … private void changeFrontAC(boolean turnOn) { List<CarPropertyConfig> configs = mCarPropertyManager .getPropertyList(new ArraySet<>(Arrays.asList( VehiclePropertyIds.HVAC_AC_ON))); if (configs == null || configs.size() != 1) { mTTS.speak("I'm sorry, but I can't control the AC of your vehicle"); return; } // Find the front area Ids for the AC property. int[] areaIds = configs.get(0).getAreaIds(); List<Integer> areasToChange = new ArrayList<>(); for (int areaId : areaIds) { if ((areaId & (VehicleAreaSeat.SEAT_ROW_1_CENTER | VehicleAreaSeat.SEAT_ROW_1_LEFT | VehicleAreaSeat.SEAT_ROW_1_RIGHT)) == 0) { continue; } boolean isACInAreaAlreadyOn = mCarPropertyManager .getBooleanProperty(VehiclePropertyIds.HVAC_AC_ON, areaId); if ((!isACInAreaAlreadyOn && turnOn) || (isACInAreaAlreadyOn && !turnOn)) { areasToChange.add(areaId); } } if (areasToChange.isEmpty()) { mTTS.speak(String.format("The AC is already %s", turnOn ? "on" : "off")); return; } for (int areaId : areasToChange) { mCarPropertyManager.setBooleanProperty( VehiclePropertyIds.HVAC_AC_ON, areaId, turnOn); } mTTS.speak(String.format("Okay, I'm turning your front AC %s", turnOn ? "on" : "off")); } … }
ביצוע פקודות תקשורת
טיפול בפקודות של הודעות
שירותי VIA חייבים לטפל בהודעות נכנסות בהתאם לתהליך 'הקשה כדי לקרוא' שמתואר במאמר הקשה כדי לקרוא ב-Voice Assistant. השירותים האלה יכולים גם לשלוח תשובות חזרה לשולח ההודעה הנכנסת.
בנוסף, שירותי VIA יכולים להשתמש ב-SmsManager
(חלק מחבילת android.telephony
) כדי לכתוב ולשלוח הודעות SMS ישירות מהרכב או דרך Bluetooth.
טיפול בפקודות לשיחות
באופן דומה, חשבונות VIA יכולים להשתמש ב-TelephonyManager
כדי לבצע שיחות טלפון ולתקשר למספר הדואר הקולי של המשתמש. במקרים כאלה, ממשקי ה-VIA יתקשרו עם סטאק הטלפון ישירות או עם אפליקציית Car Dialer. בכל מקרה, אפליקציית Car Dialer אמורה להציג למשתמש את ממשק המשתמש שקשור לשיחות הקוליות.
ביצוע פקודות אחרות
כדי לראות רשימה של נקודות שילוב אפשריות אחרות בין ה-VIA לבין המערכת, אפשר לעיין ברשימה של כוונות ידועות של Android. אפשר לטפל בהרבה פקודות של משתמשים בצד השרת (לדוגמה, קריאת אימיילים ואירועים ביומן של המשתמשים), בלי צורך באינטראקציות עם המערכת מלבד האינטראקציה הקולית עצמה.
פעולות בתצוגה עשירה (הצגת תוכן חזותי)
כשהיא משפרת את הפעולות או את ההבנה של המשתמשים, ה-VIA יכולה לספק תוכן חזותי נוסף במסך הרכב. כדי לצמצם את הסחות הדעת של הנהגים, כדאי שהתוכן יהיה פשוט, קצר וברור. לפרטים על הנחיות לממשק המשתמש/חוויית המשתמש בפעולות immersive, ראו עוזרות שהוגדרו מראש: הנחיות בנושא חוויית משתמש.
כדי לאפשר התאמה אישית ועקביות עם שאר העיצוב של היחידה הראשית (HU), ב-VIA צריך להשתמש ברכיבים של ספריית ממשק המשתמש שברכב לרוב רכיבי ממשק המשתמש. פרטים נוספים זמינים במאמר התאמה אישית.