בדף הזה מוסבר איך להשתמש בפקודות באמצעות אינטראקציה קולית.
ביצוע פקודות מדיה
אפשר לחלק את הפקודות שקשורות למדיה לשלוש קבוצות שונות:
- מקורות מדיה חיצוניים (כמו 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; } … }
טיפול בפקודות של מקורות מדיה מקומיים (רדיו, נגן תקליטורים, Bluetooth, USB)
מקורות מדיה מקומיים חושפים את הפונקציונליות שלהם למערכת באמצעות אותם ממשקי MediaSession ו-MediaBrowse API שמתוארים למעלה. כדי להתאים את עצמם למאפיינים הספציפיים של כל סוג חומרה, שירותי MediaBrowse האלה משתמשים במוסכמות ספציפיות לארגון המידע ופקודות המדיה שלהם.
הפעלת רדיו
אפשר לזהות את Radio MediaBrowseService באמצעות מסנן ה-Intent ACTION_PLAY_BROADCASTRADIO. הם צריכים לפעול בהתאם לפקדי ההפעלה ולמבנה של דפדוף במדיה שמתוארים במאמר בנושא הטמעה של רדיו. AAOS מציעה את הספרייה car-broadcastradio-support שמכילה קבועים ושיטות שעוזרים ליצרני ציוד מקורי (OEM) ליצור הטמעות של MediaBrowseService בשירותי הרדיו שלהם בהתאם לפרוטוקול המוגדר, ומספקת תמיכה באפליקציות שצורכות את עץ העיון שלהן (לדוגמה, VIA).
הפעלה של קלט עזר, אודיו של תקליטור ומדיה ב-USB
אין הטמעה שמוגדרת כברירת מחדל של מקורות המדיה האלה כחלק מ-AOSP. הגישה המומלצת היא:
- יצרני ציוד מקורי (OEM) מטמיעים שירותי מדיה בכל אחד מהם. פרטים נוספים זמינים במאמר בנושא פיתוח אפליקציות מדיה לרכב.
- הטמעות של MediaBrowseService יזוהו ויתקבלו בתגובה לפעולות של כוונות שמוגדרות בכוונות כלליות להפעלה.
- השירותים האלה יציגו עץ עיון בהתאם להנחיות שמתוארות בקטע סוגי מקורות אחרים.
טיפול ב-Bluetooth
תוכן מדיה ב-Bluetooth נחשף דרך פרופיל Bluetooth AVRCP. כדי לאפשר גישה לפונקציונליות הזו, מערכת AAOS כוללת הטמעה של MediaBrowserService ו-MediaSession שמבצעת הפשטה של פרטי התקשורת (ראו packages/apps/Bluetooth).
מבנה העץ של דפדפן המדיה המתאים מוגדר במחלקה BrowseTree. אפשר להעביר פקודות של שליטה בהפעלה באופן דומה לכל אפליקציה אחרת, באמצעות ההטמעה של MediaSession.
טיפול בפקודות של סטרימינג מדיה
כדי להטמיע סטרימינג של מדיה בצד השרת, ה-VIA צריך להפוך למקור מדיה בעצמו, ולהטמיע את MediaBrowse API ו-MediaSession API. מידע נוסף זמין במאמר בנושא פיתוח אפליקציות מדיה לרכב. הטמעה של ממשקי ה-API האלה מאפשרת לאפליקציה לשליטה קולית (בין היתר):
- השתתפות חלקה בבחירת מקור המדיה
- להמשיך אוטומטית אחרי הפעלה מחדש של הרכב
- הפעלת מדיה ושליטה בה באמצעות ממשק המשתמש של מרכז המדיה
- קבלת אירועים רגילים של לחצני מדיה בציוד
ביצוע פקודות ניווט
אין דרך סטנדרטית ליצור אינטראקציה עם כל אפליקציות הניווט. למידע על שילובים עם מפות Google, אפשר לעיין במאמר Google Maps for Android Automotive Intents. לגבי שילובים עם אפליקציות אחרות, צריך לפנות ישירות למפתחי האפליקציות. לפני שמפעילים intent באפליקציה כלשהי (כולל מפות Google), צריך לוודא שאפשר לפתור את ה-intent (ראו Google Maps Intents). כך יש לכם הזדמנות להודיע למשתמש אם האפליקציה שאליה הוא מנסה להגיע לא זמינה.
מילוי פקודות לרכב
הגישה למאפייני הרכב לקריאה ולכתיבה מתבצעת דרך CarPropertyManager.
הסבר על סוגי מאפייני הרכב, על ההטמעה שלהם ועל פרטים נוספים מופיע במאמר הגדרות של מאפיינים. כדי לקבל תיאור מדויק של המאפיינים שנתמכים ב-Android, מומלץ לעיין ישירות בhardware/interfaces/automotive/vehicle/2.0/types.hal.
ה-enum 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 צריכים לטפל בהודעות נכנסות לפי התהליך של 'הקשה להקראה' שמתואר במאמר הקשה להקראה בעוזר קולי. הם יכולים גם לטפל בשליחת תשובות בחזרה לשולח ההודעה הנכנסת.
בנוסף, מערכות VIA יכולות להשתמש ב-SmsManager (חלק מחבילת android.telephony) כדי לכתוב ולשלוח הודעות SMS ישירות מהרכב או באמצעות Bluetooth.
איך משתמשים בפקודות לשיחות
באופן דומה, ממשקי VIA יכולים להשתמש ב-TelephonyManager
כדי לבצע שיחות טלפון ולהתקשר למספר הדואר הקולי של המשתמש. במקרים כאלה, ממשקי VIA יפעלו ישירות עם מחסנית הטלפוניה או עם אפליקציית חייגן הרכב. בכל מקרה, אפליקציית חייגן הרכב היא זו שצריכה להציג למשתמש את ממשק המשתמש שקשור לשיחות קוליות.
ביצוע פקודות אחרות
רשימה של נקודות שילוב אפשריות אחרות בין VIA לבין המערכת מופיעה ברשימה של אינטנטים מוכרים של Android. אפשר לפתור הרבה פקודות של משתמשים בצד השרת (לדוגמה, קריאת אימיילים ואירועים ביומן של משתמשים) ולא נדרשות אינטראקציות עם המערכת מלבד האינטראקציה הקולית עצמה.
פעולות בתצוגה עשירה (הצגת תוכן ויזואלי)
במקרים שבהם היא יכולה לשפר את הפעולות של המשתמשים או את ההבנה שלהם, VIA יכולה לספק תוכן ויזואלי משלים במסך המכונית. כדי לצמצם את הסחת הדעת של הנהגים, התוכן צריך להיות פשוט, קצר ופרקטי. פרטים על הנחיות לגבי ממשק משתמש וחוויית משתמש בפעולות אינטראקטיביות מופיעים במאמר עוזרים דיגיטליים שנטענו מראש: הנחיות לגבי חוויית משתמש.
כדי לאפשר התאמה אישית ולשמור על עקביות עם שאר העיצוב של מערכת המולטימדיה, אפליקציות VIA צריכות להשתמש ברכיבים של Car UI Library עבור רוב רכיבי ממשק המשתמש. פרטים נוספים מופיעים במאמר בנושא התאמה אישית.