การกำหนดเวอร์ชันอินเทอร์เฟซ

HIDL กําหนดให้อินเทอร์เฟซทุกรายการที่เขียนด้วย HIDL ต้องมีเวอร์ชัน หลังจากเผยแพร่อินเทอร์เฟซ HAL แล้ว ระบบจะหยุดอินเทอร์เฟซนั้นไว้ และการเปลี่ยนแปลงเพิ่มเติมจะต้องทำในอินเทอร์เฟซเวอร์ชันใหม่ แม้ว่าอินเทอร์เฟซที่เผยแพร่แล้วจะแก้ไขไม่ได้ แต่อินเทอร์เฟซอื่นสามารถขยายอินเทอร์เฟซนั้นได้

โครงสร้างโค้ด HIDL

โค้ด HIDL จะจัดระเบียบเป็นประเภท อินเทอร์เฟซ และแพ็กเกจที่ผู้ใช้กำหนด ดังนี้

  • ประเภทที่กําหนดโดยผู้ใช้ (UDT) HIDL ให้สิทธิ์เข้าถึงชุดประเภทข้อมูลพื้นฐานซึ่งสามารถใช้เพื่อเขียนประเภทที่ซับซ้อนมากขึ้นผ่านโครงสร้าง ยูเนียน และการแจกแจง ระบบจะส่ง UDT ไปยังเมธอดของอินเทอร์เฟซ และสามารถกำหนดได้ที่ระดับแพ็กเกจ (ใช้ร่วมกันกับอินเทอร์เฟซทั้งหมด) หรือที่อินเทอร์เฟซ
  • อินเทอร์เฟซ อินเทอร์เฟซเป็นองค์ประกอบพื้นฐานของ HIDL ซึ่งประกอบด้วยการประกาศ UDT และเมธอด อินเทอร์เฟซยังรับค่าจากอินเทอร์เฟซอื่นได้ด้วย
  • แพ็กเกจ จัดระเบียบอินเทอร์เฟซ HIDL ที่เกี่ยวข้องและประเภทข้อมูลที่อินเทอร์เฟซดังกล่าวทำงานด้วย แพ็กเกจจะระบุด้วยชื่อและเวอร์ชัน รวมถึงข้อมูลต่อไปนี้
    • ไฟล์คําจํากัดความประเภทข้อมูลชื่อ types.hal
    • อินเทอร์เฟซอย่างน้อย 1 รายการ โดยแต่ละรายการอยู่ในไฟล์ .hal ของตนเอง

ไฟล์คำจำกัดความของประเภทข้อมูล types.hal มีเฉพาะ UDT (UDT ระดับแพ็กเกจทั้งหมดจะเก็บไว้ในไฟล์เดียว) การแสดงในภาษาเป้าหมายจะใช้ได้กับอินเทอร์เฟซทั้งหมดในแพ็กเกจ

ปรัชญาการกำหนดเวอร์ชัน

แพ็กเกจ HIDL (เช่น android.hardware.nfc) หลังจากเผยแพร่สำหรับเวอร์ชันหนึ่งๆ (เช่น 1.0) จะเปลี่ยนแปลงไม่ได้ การแก้ไขอินเทอร์เฟซในแพ็กเกจหรือการเปลี่ยนแปลง UDT ใดๆ จะเกิดขึ้นได้ในแพ็กเกจอื่นเท่านั้น

ใน HIDL เวอร์ชันจะมีผลในระดับแพ็กเกจ ไม่ใช่ระดับอินเทอร์เฟซ และอินเทอร์เฟซและ UDT ทั้งหมดในแพ็กเกจจะใช้เวอร์ชันเดียวกัน เวอร์ชันของแพ็กเกจจะเป็นไปตามการกำหนดเวอร์ชันแบบเชิงอรรถโดยไม่มีองค์ประกอบระดับแพตช์และข้อมูลเมตาของบิลด์ ภายในแพ็กเกจหนึ่งๆ การอัปเกรดเวอร์ชันย่อยหมายความว่าแพ็กเกจเวอร์ชันใหม่เข้ากันได้แบบย้อนหลังกับแพ็กเกจเก่า และการอัปเกรดเวอร์ชันหลักหมายความว่าแพ็กเกจเวอร์ชันใหม่เข้ากันไม่ได้แบบย้อนหลังกับแพ็กเกจเก่า

ในแง่แนวคิด แพ็กเกจหนึ่งอาจเกี่ยวข้องกับแพ็กเกจอื่นได้หลายวิธี ดังนี้

  • ไม่เลย
  • ความสามารถในการขยายการทำงานแบบเข้ากันได้กับเวอร์ชันเก่าระดับแพ็กเกจ กรณีนี้เกิดขึ้นเมื่ออัปเดตแพ็กเกจเป็นเวอร์ชันย่อยใหม่ (การแก้ไขที่เพิ่มขึ้นครั้งถัดไป) โดยแพ็กเกจใหม่จะมีชื่อและเวอร์ชันหลักเหมือนกับแพ็กเกจเก่า แต่เวอร์ชันย่อยจะสูงกว่า ในแง่ฟังก์ชันการทำงาน แพ็กเกจใหม่จะเป็นเซตที่ใหญ่กว่าแพ็กเกจเก่า หมายความว่า
    • อินเทอร์เฟซระดับบนสุดของแพ็กเกจหลักจะอยู่ในแพ็กเกจใหม่ แม้ว่าอินเทอร์เฟซอาจมีเมธอดใหม่ UDT ระดับอินเทอร์เฟซใหม่ (ส่วนขยายระดับอินเทอร์เฟซที่อธิบายไว้ด้านล่าง) และ UDT ใหม่ใน types.hal
    • นอกจากนี้ คุณยังเพิ่มอินเทอร์เฟซใหม่ลงในแพ็กเกจใหม่ได้ด้วย
    • ประเภทข้อมูลทั้งหมดของแพ็กเกจหลักจะอยู่ในแพ็กเกจใหม่และสามารถจัดการโดยเมธอด (อาจติดตั้งใช้งานอีกครั้ง) จากแพ็กเกจเก่า
    • นอกจากนี้ คุณยังเพิ่มประเภทข้อมูลใหม่เพื่อใช้งานด้วยวิธีการใหม่ของอินเทอร์เฟซที่มีอยู่ซึ่งอัปเกรดแล้ว หรือด้วยอินเทอร์เฟซใหม่ก็ได้
  • ความสามารถในการขยายระดับอินเทอร์เฟซที่เข้ากันได้แบบย้อนหลัง นอกจากนี้ แพ็กเกจใหม่ยังขยายแพ็กเกจเดิมได้ด้วยการมีอินเทอร์เฟซที่แยกตามตรรกะซึ่งให้ฟังก์ชันการทำงานเพิ่มเติมเท่านั้น ไม่ใช่ฟังก์ชันหลัก ในกรณีนี้ คุณอาจต้องดำเนินการต่อไปนี้
    • อินเทอร์เฟซในแพ็กเกจใหม่ต้องใช้ประเภทข้อมูลของแพ็กเกจเก่า
    • อินเทอร์เฟซในแพ็กเกจใหม่สามารถขยายอินเทอร์เฟซของแพ็กเกจเก่าได้อย่างน้อย 1 แพ็กเกจ
  • ขยายความไม่เข้ากันแบบย้อนหลังเดิม นี่เป็นแพ็กเกจเวอร์ชันหลักที่อัปเกรด และไม่จำเป็นต้องมีความสัมพันธ์กัน หากมี ก็สามารถแสดงด้วยชุดค่าผสมของประเภทจากแพ็กเกจเวอร์ชันเก่าและการรับค่าอินเทอร์เฟซย่อยของแพ็กเกจเก่า

การจัดโครงสร้างอินเทอร์เฟซ

สำหรับอินเทอร์เฟซที่มีโครงสร้างดี การเพิ่มฟังก์ชันการทำงานประเภทใหม่ที่ไม่ได้อยู่ในการออกแบบเดิมจะต้องมีการแก้ไขอินเทอร์เฟซ HIDL ในทางกลับกัน หากคุณทำได้หรือคาดว่าจะทำการเปลี่ยนแปลงทั้ง 2 ด้านของอินเทอร์เฟซที่เปิดตัวฟังก์ชันใหม่โดยไม่เปลี่ยนอินเทอร์เฟซนั้น แสดงว่าอินเทอร์เฟซนั้นไม่มีโครงสร้าง

Treble รองรับคอมโพเนนต์ของผู้ให้บริการและระบบที่คอมไพล์แยกต่างหาก ซึ่งสามารถคอมไพล์ vendor.img ในอุปกรณ์และ system.img แยกกันได้ การโต้ตอบทั้งหมดระหว่าง vendor.img กับ system.img ต้องได้รับการกําหนดอย่างละเอียดและชัดเจนเพื่อให้ทํางานต่อไปได้อีกหลายปี ซึ่งรวมถึงอินเทอร์เฟซ API หลายรายการ แต่อินเทอร์เฟซหลักคือกลไก IPC ที่ HIDL ใช้สำหรับการสื่อสารระหว่างกระบวนการในขอบเขต system.img/vendor.img

ข้อกำหนด

ข้อมูลทั้งหมดที่ส่งผ่าน HIDL ต้องได้รับการกําหนดไว้อย่างชัดเจน ข้อมูลต้องเป็นไปตามข้อกําหนดต่อไปนี้เพื่อให้การติดตั้งใช้งานและไคลเอ็นต์ทํางานร่วมกันได้ต่อไปแม้ว่าจะคอมไพล์แยกกันหรือพัฒนาแยกต่างหากก็ตาม

  • อธิบายได้ใน HIDL โดยตรง (โดยใช้โครงสร้าง อาร์เรย์แบบจำกัด ฯลฯ) พร้อมชื่อและความหมายเชิงความหมาย
  • อธิบายได้ด้วยมาตรฐานสาธารณะ เช่น ISO/IEC 7816
  • อธิบายได้ด้วยมาตรฐานฮาร์ดแวร์หรือเลย์เอาต์ฮาร์ดแวร์จริง
  • อาจเป็นข้อมูลที่ไม่สามารถมองเห็นได้ (เช่น คีย์สาธารณะ รหัส ฯลฯ) หากจำเป็น

หากใช้ข้อมูลที่ทึบแสง ข้อมูลดังกล่าวจะต้องอ่านได้เพียงฝั่งเดียวของอินเทอร์เฟซ HIDL ตัวอย่างเช่น หากโค้ด vendor.img กำหนดข้อความสตริงหรือข้อมูล vec<uint8_t> ให้กับคอมโพเนนต์ใน system.img system.img จะไม่สามารถแยกวิเคราะห์ข้อมูลดังกล่าวได้ แต่จะส่งกลับไปยัง vendor.img เพื่อตีความได้เท่านั้น เมื่อส่งค่าจาก vendor.img ไปยังโค้ดของผู้ให้บริการใน system.img หรือไปยังอุปกรณ์อื่น คุณต้องอธิบายรูปแบบของข้อมูลและวิธีตีความข้อมูลอย่างถูกต้อง และข้อมูลดังกล่าวยังคงเป็นส่วนหนึ่งของอินเทอร์เฟซ

หลักเกณฑ์

คุณควรเขียนการใช้งานหรือไคลเอ็นต์ของ HAL ได้โดยใช้ไฟล์ .hal เท่านั้น (กล่าวคือ คุณไม่ควรต้องดูซอร์สโค้ด Android หรือมาตรฐานสาธารณะ) เราขอแนะนำให้ระบุลักษณะการทำงานที่จำเป็นอย่างละเอียด ข้อความอย่าง "การติดตั้งใช้งานอาจทํา A หรือ B" กระตุ้นให้การติดตั้งใช้งานผสานรวมกับลูกค้าที่พัฒนาขึ้น

เลย์เอาต์โค้ด HIDL

HIDL ประกอบด้วยแพ็กเกจหลักและแพ็กเกจของผู้ให้บริการ

อินเทอร์เฟซ HIDL หลักคืออินเทอร์เฟซที่ Google ระบุ แพ็กเกจที่ควรอยู่ด้วยจะขึ้นต้นด้วย android.hardware. และตั้งชื่อตามระบบย่อย โดยอาจมีการตั้งชื่อแบบซ้อนกัน เช่น แพ็กเกจ NFC มีชื่อว่า android.hardware.nfc และแพ็กเกจกล้องมีชื่อว่า android.hardware.camera โดยทั่วไป แพ็กเกจหลักจะมีชื่อเป็น android.hardware.[name1].[name2]…. แพ็กเกจ HiDL จะมีเวอร์ชันนอกเหนือจากชื่อ ตัวอย่างเช่น แพ็กเกจ android.hardware.camera อาจใช้เวอร์ชัน 3.4 ซึ่งข้อมูลนี้สำคัญเนื่องจากเวอร์ชันของแพ็กเกจส่งผลต่อตำแหน่งในลําดับชั้นซอร์สโค้ด

แพ็กเกจหลักทั้งหมดจะอยู่ภายใต้ hardware/interfaces/ ในระบบบิลด์ แพ็กเกจ android.hardware.[name1].[name2]… เวอร์ชัน $m.$n อยู่ใน hardware/interfaces/name1/name2//$m.$n/ แพ็กเกจ android.hardware.camera เวอร์ชัน 3.4 อยู่ในไดเรกทอรี hardware/interfaces/camera/3.4/. มีการแมปแบบฮาร์ดโค้ดระหว่างคำนำหน้าแพ็กเกจ android.hardware. กับเส้นทาง hardware/interfaces/

แพ็กเกจที่ไม่ใช่แกนหลัก (ของผู้ให้บริการ) คือแพ็กเกจที่ผลิตโดยผู้ให้บริการ SoC หรือ ODM ส่วนคำนำหน้าสำหรับแพ็กเกจที่ไม่ใช่ส่วนกลางคือ vendor.$(VENDOR).hardware. โดยที่ $(VENDOR) หมายถึงผู้ให้บริการ SoC หรือ OEM/ODM ซึ่งจะแมปกับเส้นทาง vendor/$(VENDOR)/interfaces ในต้นไม้ (การแมปนี้ยังเป็นการเขียนโค้ดแบบฮาร์ดโค้ดด้วย)

ชื่อประเภทที่ผู้ใช้กำหนดที่สมบูรณ์ในตัวเอง

ใน HIDL ยูทิลิตีทุกรายการจะมีชื่อที่สมบูรณ์ในตัวเอง ซึ่งประกอบด้วยชื่อยูทิลิตี ชื่อแพ็กเกจที่กําหนดยูทิลิตี และเวอร์ชันแพ็กเกจ ระบบจะใช้ชื่อแบบเต็มที่สมบูรณ์เฉพาะเมื่อมีการประกาศอินสแตนซ์ของประเภทนั้นๆ เท่านั้น และจะไม่ใช้ในกรณีที่มีการกําหนดประเภทนั้นๆ ตัวอย่างเช่น สมมติว่าแพ็กเกจ android.hardware.nfc, เวอร์ชัน 1.0 กำหนดโครงสร้างที่มีชื่อว่า NfcData ในการประกาศ (ไม่ว่าจะอยู่ในtypes.halหรือภายในการประกาศของอินเทอร์เฟซ) การประกาศจะระบุเพียงว่า

struct NfcData {
    vec<uint8_t> data;
};

เมื่อประกาศอินสแตนซ์ของประเภทนี้ (ไม่ว่าจะภายในโครงสร้างข้อมูลหรือเป็นพารามิเตอร์ของเมธอด) ให้ใช้ชื่อประเภทแบบเต็มที่สมบูรณ์

android.hardware.nfc@1.0::NfcData

ไวยากรณ์ทั่วไปคือ PACKAGE@VERSION::UDT โดยที่

  • PACKAGE คือชื่อแพ็กเกจ HIDL ที่คั่นด้วยจุด (เช่น android.hardware.nfc)
  • VERSION คือรูปแบบแพ็กเกจ major.minor-version ที่คั่นด้วยจุด (เช่น 1.0)
  • UDT คือชื่อที่แยกด้วยจุดของ UDT HIDL เนื่องจาก HIDL รองรับ UDT ที่ฝังอยู่และอินเทอร์เฟซ HIDL อาจมี UDT (การประกาศแบบฝัง) ระบบจึงใช้จุดเพื่อเข้าถึงชื่อ

ตัวอย่างเช่น หากมีการประกาศที่ฝังไว้ต่อไปนี้ในไฟล์ประเภททั่วไปในแพ็กเกจ android.hardware.example เวอร์ชัน 1.0

// types.hal
package android.hardware.example@1.0;
struct Foo {
    struct Bar {
        // …
    };
    Bar cheers;
};

ชื่อที่สมบูรณ์ในตัวเองของ Bar คือ android.hardware.example@1.0::Foo.Bar หากนอกเหนือจากที่อยู่ในแพ็กเกจข้างต้นแล้ว การประกาศที่ฝังอยู่ยังอยู่ในอินเทอร์เฟซที่ชื่อ IQuux

// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
    struct Foo {
        struct Bar {
            // …
        };
        Bar cheers;
    };
    doSomething(Foo f) generates (Foo.Bar fb);
};

ชื่อที่สมบูรณ์ในตัวเองของ Bar คือ android.hardware.example@1.0::IQuux.Foo.Bar

ในทั้ง 2 กรณี Bar จะเรียกเป็น Bar ได้ก็ต่อเมื่ออยู่ภายในขอบเขตของการประกาศ Foo ที่ระดับแพ็กเกจหรือระดับอินเทอร์เฟซ คุณต้องอ้างอิง Bar ผ่าน Foo: Foo.Bar ดังที่ประกาศเมธอด doSomething ด้านบน หรือจะประกาศเมธอดให้แสดงรายละเอียดมากขึ้นก็ได้ ดังนี้

// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);

ค่าการแจกแจงที่สมบูรณ์ในตัวเอง

หาก UDT เป็นประเภท Enum ค่าแต่ละค่าของประเภท Enum จะมีชื่อแบบเต็มที่ขึ้นต้นด้วยชื่อแบบเต็มที่สมบูรณ์ของประเภท Enum ตามด้วยโคลอน ตามด้วยชื่อของค่า Enum ตัวอย่างเช่น ให้สมมติว่าแพ็กเกจ android.hardware.nfc, เวอร์ชัน 1.0 กำหนดประเภท enum NfcStatus ดังนี้

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

เมื่อพูดถึง STATUS_OK ชื่อที่สมบูรณ์ในตัวเองคือ

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

ไวยากรณ์ทั่วไปคือ PACKAGE@VERSION::UDT:VALUE โดยที่

  • PACKAGE@VERSION::UDT เป็นชื่อที่มีคุณสมบัติเหมาะสมโดยสมบูรณ์แบบเดียวกันสำหรับประเภท enum
  • VALUE คือชื่อของค่า

กฎการอนุมานอัตโนมัติ

โดยไม่จำเป็นต้องระบุชื่อ UDT ที่สมบูรณ์ในตัวเอง ชื่อ UDT สามารถละเว้นข้อมูลต่อไปนี้ได้

  • แพ็กเกจ เช่น @1.0::IFoo.Type
  • ทั้งแพ็กเกจและเวอร์ชัน เช่น IFoo.Type

HIDL จะพยายามเติมชื่อให้สมบูรณ์โดยใช้กฎการแทรกแซงอัตโนมัติ (ตัวเลขกฎที่ต่ำลงหมายถึงลำดับความสำคัญสูงขึ้น)

กฎข้อ 1

หากไม่ได้ระบุแพ็กเกจและเวอร์ชัน ระบบจะพยายามค้นหาชื่อในเครื่อง ตัวอย่าง

interface Nfc {
    typedef string NfcErrorMessage;
    send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};

ระบบจะค้นหา NfcErrorMessage ในเครื่อง และพบ typedef ที่ด้านบน ระบบจะค้นหา NfcData ในเครื่องด้วย แต่เนื่องจากไม่ได้กำหนดค่าในเครื่อง ระบบจะใช้กฎ 2 และ 3 @1.0::NfcStatus ระบุเวอร์ชัน ดังนั้นกฎข้อที่ 1 จึงไม่มีผล

กฎข้อ 2

หากกฎข้อที่ 1 ไม่ผ่านและไม่มีคอมโพเนนต์ของชื่อที่สมบูรณ์ในตัวเอง (แพ็กเกจ เวอร์ชัน หรือแพ็กเกจและเวอร์ชัน) ระบบจะป้อนข้อมูลจากแพ็กเกจปัจจุบันโดยอัตโนมัติในคอมโพเนนต์นั้น จากนั้นคอมไพเลอร์ HIDL จะค้นหาในไฟล์ปัจจุบัน (และการนําเข้าทั้งหมด) เพื่อหาชื่อที่สมบูรณ์ซึ่งระบบกรอกให้โดยอัตโนมัติ จากตัวอย่างข้างต้น สมมติว่าประกาศของ ExtendedNfcData อยู่ในแพ็กเกจเดียวกัน (android.hardware.nfc) ในเวอร์ชันเดียวกัน (1.0) กับ NfcData ดังนี้

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

คอมไพเลอร์ HIDL จะกรอกชื่อแพ็กเกจและชื่อเวอร์ชันจากแพ็กเกจปัจจุบันเพื่อสร้างชื่อ UDT ที่สมบูรณ์ในตัวเอง android.hardware.nfc@1.0::NfcData เนื่องจากมีชื่ออยู่ในแพ็กเกจปัจจุบัน (สมมติว่านําเข้าอย่างถูกต้อง) ระบบจะใช้ชื่อนั้นสําหรับการประกาศ

ระบบจะนำเข้าชื่อในแพ็กเกจปัจจุบันก็ต่อเมื่อเงื่อนไขข้อใดข้อหนึ่งต่อไปนี้เป็นจริงเท่านั้น

  • ระบบจะนําเข้าอย่างชัดแจ้งด้วยคำสั่ง import
  • มีการกําหนดไว้ใน types.hal ในแพ็กเกจปัจจุบัน

ระบบจะใช้กระบวนการเดียวกันนี้หาก NfcData มีคุณสมบัติตรงตามหมายเลขเวอร์ชันเท่านั้น

struct ExtendedNfcData {
    // autofill the current package name (android.hardware.nfc)
    @1.0::NfcData base;
    // … additional members
};

กฎข้อ 3

หากกฎ 2 ไม่พบรายการที่ตรงกัน (ไม่ได้กำหนด UDT ในแพ็กเกจปัจจุบัน) คอมไพเลอร์ HIDL จะสแกนหารายการที่ตรงกันภายในแพ็กเกจที่นำเข้าทั้งหมด ใช้ตัวอย่างข้างต้น โดยสมมติว่า ExtendedNfcData ประกาศในเวอร์ชัน 1.1 ของแพ็กเกจ android.hardware.nfc, 1.1 นําเข้า 1.0 ตามที่ควรจะเป็น (ดูส่วนขยายระดับแพ็กเกจ) และคําจํากัดความระบุเฉพาะชื่อ UDT เท่านั้น

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

คอมไพเลอร์จะค้นหา UDT ที่มีชื่อว่า NfcData และพบใน android.hardware.nfc เวอร์ชัน 1.0 ส่งผลให้มี UDT ของ android.hardware.nfc@1.0::NfcData ที่ผ่านการรับรองอย่างสมบูรณ์ หากพบรายการที่ตรงกันมากกว่า 1 รายการสําหรับ UDT ที่ผ่านการรับรองบางส่วนที่ระบุ คอมไพเลอร์ HIDL จะแสดงข้อผิดพลาด

ตัวอย่าง

เมื่อใช้กฎข้อที่ 2 ระบบจะเลือกประเภทที่นําเข้าซึ่งกําหนดไว้ในแพ็กเกจปัจจุบันมากกว่าประเภทที่นําเข้าจากแพ็กเกจอื่น

// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};

// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;

// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
    baz1(S s); // android.hardware.bar@1.0::S
    baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
  • S ได้รับการหาค่าเฉลี่ยเป็น android.hardware.bar@1.0::S และพบใน bar/1.0/types.hal (เนื่องจากระบบนําเข้า types.hal โดยอัตโนมัติ)
  • IFooCallback ได้รับการประมาณเป็น android.hardware.bar@1.0::IFooCallback โดยใช้กฎ 2 แต่ระบบไม่พบ bar/1.0/IFooCallback.hal เนื่องจากไม่ได้นำเข้า bar/1.0/IFooCallback.hal โดยอัตโนมัติ (เช่นเดียวกับ types.hal) ดังนั้น กฎที่ 3 จะเปลี่ยนค่าเป็น android.hardware.foo@1.0::IFooCallback แทน ซึ่งนําเข้าผ่าน import android.hardware.foo@1.0;)

types.hal

แพ็กเกจ HIDL ทุกแพ็กเกจจะมีไฟล์ types.hal ที่มี UDT ที่แชร์กันระหว่างอินเทอร์เฟซทั้งหมดที่เข้าร่วมในแพ็กเกจนั้น ประเภท HIDL จะเป็นแบบสาธารณะเสมอ ไม่ว่าจะประกาศ UDT ใน types.hal หรือภายในการประกาศอินเทอร์เฟซ ประเภทเหล่านี้จะเข้าถึงได้นอกขอบเขตที่กําหนด types.hal ไม่ได้มีไว้เพื่ออธิบาย API สาธารณะของแพ็กเกจ แต่มีไว้เพื่อโฮสต์ UDT ที่อินเทอร์เฟซทั้งหมดในแพ็กเกจใช้ UDT ทั้งหมดเป็นส่วนหนึ่งของอินเทอร์เฟซเนื่องจากลักษณะของ HIDL

types.hal ประกอบด้วย UDT และคำสั่ง import เนื่องจาก types.hal พร้อมใช้งานสำหรับอินเทอร์เฟซทุกรายการของแพ็กเกจ (เป็นการนําเข้าโดยนัย) คำสั่ง import เหล่านี้จึงอยู่ในระดับแพ็กเกจตามคำจำกัดความ นอกจากนี้ UDT ใน types.hal ยังรวม UDT และอินเทอร์เฟซที่นําเข้าได้ด้วย

เช่น IFoo.hal

package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;

ระบบจะนำเข้าข้อมูลต่อไปนี้

  • android.hidl.base@1.0::IBase (โดยนัย)
  • android.hardware.foo@1.0::types (โดยนัย)
  • ทุกอย่างใน android.hardware.bar@1.0 (รวมถึงอินเทอร์เฟซทั้งหมดและtypes.hal)
  • types.hal จาก android.hardware.baz@1.0::types (ระบบจะไม่นำเข้าอินเทอร์เฟซใน android.hardware.baz@1.0)
  • IQux.hal และ types.hal จาก android.hardware.qux@1.0
  • Quuz จาก android.hardware.quuz@1.0 (สมมติว่ามีการกําหนด Quuz ใน types.hal ระบบจะแยกวิเคราะห์ไฟล์ types.hal ทั้งหมด แต่จะไม่นําเข้าประเภทอื่นนอกเหนือจาก Quuz)

การกำหนดเวอร์ชันระดับอินเทอร์เฟซ

อินเทอร์เฟซแต่ละรายการภายในแพ็กเกจจะอยู่ในไฟล์ของตัวเอง ระบบจะประกาศแพ็กเกจที่อินเทอร์เฟซอยู่ด้านบนของอินเทอร์เฟซโดยใช้คำสั่ง package รายการการนําเข้าระดับอินเทอร์เฟซ (ทั้งแพ็กเกจหรือบางส่วน) อาจแสดงอยู่หลังการประกาศแพ็กเกจ เช่น

package android.hardware.nfc@1.0;

ใน HIDL อินเทอร์เฟซจะรับค่าจากอินเทอร์เฟซอื่นๆ ได้โดยใช้คีย์เวิร์ด extends หากต้องการให้อินเทอร์เฟซขยายอินเทอร์เฟซอื่น อินเทอร์เฟซนั้นต้องมีสิทธิ์เข้าถึงอินเทอร์เฟซนั้นผ่านคำสั่ง import ชื่อของอินเทอร์เฟซที่ขยาย (อินเทอร์เฟซพื้นฐาน) ต้องเป็นไปตามกฎสำหรับข้อกำหนดชื่อประเภทที่อธิบายไว้ข้างต้น อินเทอร์เฟซจะรับช่วงค่าจากอินเทอร์เฟซได้เพียงอินเทอร์เฟซเดียวเท่านั้น เนื่องจาก HIDL ไม่รองรับการรับช่วงค่าหลายรายการ

ตัวอย่างการจัดเวอร์ชัน uprev ด้านล่างใช้แพ็กเกจต่อไปนี้

// types.hal
package android.hardware.example@1.0
struct Foo {
    struct Bar {
        vec<uint32_t> val;
    };
};

// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
    fromFooToBar(Foo f) generates (Foo.Bar b);
}

กฎ Uprev

หากต้องการกำหนดแพ็กเกจ package@major.minor เงื่อนไข A หรือ B ทั้งหมดต่อไปนี้ต้องเป็นจริง

กฎ ก "Is a start minor version": ต้องไม่กำหนดเวอร์ชันย่อยก่อนหน้าทั้งหมด นั่นคือ package@major.0, package@major.1, …, package@major.(minor-1)
หรือ
กฎ ข.

เงื่อนไขต่อไปนี้ทั้งหมดเป็นจริง

  1. "เวอร์ชันย่อยก่อนหน้าใช้งานได้": package@major.(minor-1) ต้องได้รับการกําหนดและต้องเป็นไปตามกฎ A เดียวกัน (ไม่มีการกำหนด package@major.0 ถึง package@major.(minor-2)) หรือกฎ B (หากเป็นเวอร์ชันที่อัปเกรดจาก @major.(minor-2))

    และ

  2. "รับค่าอินเทอร์เฟซอย่างน้อย 1 รายการที่มีชื่อเดียวกัน": มี package@major.minor::IFoo อินเทอร์เฟซที่ขยายจาก package@major.(minor-1)::IFoo (หากแพ็กเกจก่อนหน้ามีอินเทอร์เฟซ)

    และ

  3. "ไม่มีอินเทอร์เฟซที่รับค่ามาซึ่งมีชื่อต่างกัน": ต้องไม่มี package@major.minor::IBar ที่ขยายจาก package@major.(minor-1)::IBaz โดยที่ IBar และ IBaz เป็นชื่อที่แตกต่างกัน หากมีอินเทอร์เฟซที่มีชื่อเดียวกัน package@major.minor::IBar ต้องขยายจาก package@major.(minor-k)::IBar เพื่อไม่ให้มี IBar ที่มี k มีค่าน้อยกว่า

เนื่องจากกฎ ก.

  • แพ็กเกจสามารถเริ่มต้นด้วยหมายเลขเวอร์ชันย่อยใดก็ได้ (เช่น android.hardware.biometrics.fingerprint เริ่มต้นที่ @2.1)
  • ข้อกำหนด "android.hardware.foo@1.0 ไม่ได้กําหนด" หมายความว่าไดเรกทอรี hardware/interfaces/foo/1.0 ไม่ควรมีอยู่เลย

อย่างไรก็ตาม กฎ ก. จะไม่มีผลกับแพ็กเกจที่มีชื่อแพ็กเกจเดียวกันแต่มีเวอร์ชันหลักต่างกัน (เช่น android.hardware.camera.device มีการกําหนดทั้ง @1.0 และ @3.2 โดย @3.2 ไม่จำเป็นต้องโต้ตอบกับ @1.0) ด้วยเหตุนี้ @3.2::IExtFoo จึงขยาย @1.0::IFoo ได้

package@major.minor::IBar สามารถขยายมาจากอินเทอร์เฟซที่มีชื่อต่างกันได้ (เช่น android.hardware.bar@1.0::IBar สามารถขยายมาจาก android.hardware.baz@2.2::IBaz) ตราบใดที่ชื่อแพ็กเกจต่างกัน หากอินเทอร์เฟซไม่ได้ประกาศประเภทหลักอย่างชัดเจนด้วยคีย์เวิร์ด extend แสดงว่าอินเทอร์เฟซนั้นขยายมาจาก android.hidl.base@1.0::IBase (ยกเว้น IBase เอง)

คุณต้องปฏิบัติตามข้อ B.2 และ B.3 พร้อมกัน ตัวอย่างเช่น แม้ว่า android.hardware.foo@1.1::IFoo จะขยาย android.hardware.foo@1.0::IFooเพื่อให้ผ่านกฎ B.2 แต่หาก android.hardware.foo@1.1::IExtBar ขยาย android.hardware.foo@1.0::IBar เวอร์ชันอัปเดตนี้ก็ยังไม่ถูกต้อง

อินเทอร์เฟซ Uprev

วิธีอัปเกรด android.hardware.example@1.0 (ที่ระบุไว้ด้านบน) เป็น @1.1

// types.hal
package android.hardware.example@1.1;
import android.hardware.example@1.0;

// IQuux.hal
package android.hardware.example@1.1
interface IQuux extends @1.0::IQuux {
    fromBarToFoo(Foo.Bar b) generates (Foo f);
}

นี่คือ import ระดับแพ็กเกจของเวอร์ชัน 1.0 ของ android.hardware.example ใน types.hal แม้ว่าจะไม่มีการเพิ่ม UDT ใหม่ในแพ็กเกจเวอร์ชัน 1.1 แต่ก็ยังต้องมีการอ้างอิงถึง UDT ในเวอร์ชัน 1.0 อยู่ จึงต้องมีการนําเข้าระดับแพ็กเกจใน types.hal (ผลลัพธ์เดียวกันนี้ทำได้ด้วยการนําเข้าระดับอินเทอร์เฟซใน IQuux.hal)

ใน extends @1.0::IQuux ในการประกาศ IQuux เราได้ระบุเวอร์ชันของ IQuux ที่จะรับช่วงมา (ต้องมีการแยกแยะความหมายเนื่องจาก IQuux ใช้เพื่อประกาศอินเทอร์เฟซและรับช่วงมาจากอินเทอร์เฟซ) เนื่องจากประกาศเป็นเพียงชื่อที่รับค่าแอตทริบิวต์แพ็กเกจและเวอร์ชันทั้งหมดที่เว็บไซต์ของประกาศ การแยกความแตกต่างต้องอยู่ในชื่อของอินเทอร์เฟซพื้นฐาน เราอาจใช้ UDT แบบเต็มที่ได้ด้วย แต่นั่นจะเป็นการซ้ำซ้อน

อินเทอร์เฟซใหม่ IQuux ไม่ได้ประกาศเมธอด fromFooToBar() อีกครั้งซึ่งรับค่ามาจาก @1.0::IQuux แต่เพียงแค่แสดงรายการเมธอดใหม่ที่เพิ่มเข้ามา fromBarToFoo() ใน HIDL คุณจะไม่สามารถประกาศเมธอดที่รับช่วงมาอีกครั้งในอินเทอร์เฟซย่อย ดังนั้นอินเทอร์เฟซ IQuux จึงไม่สามารถประกาศเมธอด fromFooToBar() อย่างชัดเจน

รูปแบบ Uprev

บางครั้งชื่ออินเทอร์เฟซต้องเปลี่ยนชื่ออินเทอร์เฟซที่ขยาย เราขอแนะนำให้ส่วนขยายของ Enum, โครงสร้าง และยูเนียนมีชื่อเดียวกับสิ่งที่ขยาย เว้นแต่ว่าส่วนขยายจะแตกต่างกันมากพอที่จะต้องใช้ชื่อใหม่ ตัวอย่าง

// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };

// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };

// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };

หากเมธอดมีชื่อเชิงความหมายใหม่ได้ (เช่น fooWithLocation) ก็ควรใช้ชื่อนั้น ไม่เช่นนั้น ก็ควรตั้งชื่อให้คล้ายกับสิ่งที่ขยาย ตัวอย่างเช่น เมธอด foo_1_1 ใน @1.1::IFoo สามารถแทนที่ฟังก์ชันการทำงานของเมธอด foo ใน @1.0::IFoo ได้หากไม่มีชื่ออื่นที่ดีกว่า

การกำหนดเวอร์ชันระดับแพ็กเกจ

การกำหนดเวอร์ชัน HIDL จะเกิดขึ้นที่ระดับแพ็กเกจ หลังจากเผยแพร่แพ็กเกจแล้ว แพ็กเกจจะเปลี่ยนแปลงไม่ได้ (ชุดอินเทอร์เฟซและ UDT จะเปลี่ยนแปลงไม่ได้) แพ็กเกจต่างๆ อาจมีความสัมพันธ์กันหลายวิธี ซึ่งทั้งหมดนี้สามารถแสดงผ่านชุดค่าผสมของการรับช่วงระดับอินเทอร์เฟซและการสร้าง UDT โดยการคอมโพสิชัน

อย่างไรก็ตาม ความสัมพันธ์ 1 ประเภทได้รับการกําหนดอย่างเคร่งครัดและต้องบังคับใช้ ซึ่งก็คือการสืบทอดที่เข้ากันได้แบบย้อนหลังระดับแพ็กเกจ ในกรณีนี้ แพ็กเกจหลักคือแพ็กเกจที่รับค่ามา และแพ็กเกจย่อยคือแพ็กเกจที่ขยายแพ็กเกจหลัก กฎการสืบทอดที่เข้ากันได้แบบย้อนหลังระดับแพ็กเกจมีดังนี้

  1. อินเทอร์เฟซระดับบนสุดทั้งหมดของแพ็กเกจหลักจะรับค่ามาจากอินเทอร์เฟซในแพ็กเกจย่อย
  2. นอกจากนี้ คุณยังเพิ่มอินเทอร์เฟซใหม่ลงในแพ็กเกจใหม่ได้ด้วย (ไม่มีข้อจำกัดเกี่ยวกับความสัมพันธ์กับอินเทอร์เฟซอื่นๆ ในแพ็กเกจอื่นๆ)
  3. นอกจากนี้ คุณยังเพิ่มประเภทข้อมูลใหม่เพื่อใช้งานด้วยวิธีการใหม่ของอินเทอร์เฟซที่มีอยู่ซึ่งอัปเกรดแล้ว หรือด้วยอินเทอร์เฟซใหม่ก็ได้

กฎเหล่านี้สามารถใช้งานได้โดยใช้การสืบทอดระดับอินเทอร์เฟซ HIDL และการจัดเรียง UDT แต่ต้องใช้ความรู้ระดับเมตาเพื่อทราบว่าความสัมพันธ์เหล่านี้ถือเป็นส่วนขยายแพ็กเกจที่เข้ากันได้แบบย้อนหลัง ระบบจะอนุมานความรู้นี้ดังนี้

หากแพ็กเกจเป็นไปตามข้อกำหนดนี้ hidl-gen จะบังคับใช้กฎความเข้ากันได้แบบย้อนหลัง