ביצוע פקודות

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

ביצוע פקודות מדיה

הפקודות שקשורות למדיה מחולקות לשלוש קבוצות שונות:

  • מקורות מדיה חיצוניים (כמו Spotify שמותקן ב-AAOS).
  • מקורות מדיה לקצה העורפי (כמו מוזיקה שמשודרת דרך ה-VIA).
  • מקורות מדיה מקומיים (למשל רדיו ברכב).

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

מקורות מדיה חיצוניים מוגדרים כאפליקציות ל-Android שתומכות בממשקי ה-API MediaSessionCompat ו-MediaBrowseCompat (במאמר פיתוח אפליקציות מדיה לרכבים מוסבר בפירוט איך משתמשים בממשקי ה-API האלה).

חשוב: כדי שאפליקציית עוזרת אישית תוכל להתחבר ל-MediaBrowseService של כל אפליקציות המדיה המותקנות במערכת, היא צריכה:

  1. להיות מותקנות כחתימות מערכת (ראו הנחיות לפיתוח אפליקציות מדיה ל-AAOS וקוד לדוגמה PackageValidator).
  2. יש להם הרשאת 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. הגישה המומלצת היא:

טיפול ב-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 צריך להשתמש ברכיבים של ספריית ממשק המשתמש שברכב לרוב רכיבי ממשק המשתמש. פרטים נוספים זמינים במאמר התאמה אישית.