Hướng dẫn về kiểu mã

Kiểu mã HIDL tương tự như mã C++ trong khung Android, với phần thụt lề 4 khoảng trắng và tên tệp có cả chữ hoa lẫn chữ thường. Khai báo gói, nhập và chuỗi tài liệu tương tự như trong Java, nhưng có một số điểm khác biệt nhỏ.

Các ví dụ sau đây về IFoo.haltypes.hal minh hoạ các kiểu mã HIDL và cung cấp đường liên kết nhanh đến thông tin chi tiết về từng kiểu (IFooClientCallback.hal, IBar.halIBaz.hal đã bị bỏ qua).

hardware/interfaces/foo/1.0/IFoo.hal
/*
 * (License Notice)
 */

package android.hardware.foo@1.0;

import android.hardware.bar@1.0::IBar;

import IBaz;
import IFooClientCallback;

/**
 * IFoo is an interface that*/
interface IFoo {

    /**
     * This is a multiline docstring.
     *
     * @return result 0 if successful, nonzero otherwise.
     */
     foo() generates (FooStatus result);

    /**
     * Restart controller by power cycle.
     *
     * @param bar callback interface that* @return result 0 if successful, nonzero otherwise.
     */
    powerCycle(IBar bar) generates (FooStatus result);

    /** Single line docstring. */
    baz();


    /**
     * The bar function.
     *
     * @param clientCallback callback after function is called
     * @param baz related baz object
     * @param data input data blob
     */
    bar(IFooClientCallback clientCallback,
        IBaz baz,
        FooData data);

};
hardware/interfaces/foo/1.0/types.hal
/*
 * (License Notice)
 */

package android.hardware.foo@1.0;

/** Replied status. */
enum Status : int32_t {
    OK,
    /* invalid arguments */
    ERR_ARG,
    /* note, no transport related errors */
    ERR_UNKNOWN = -1,
};

struct ArgData {
    int32_t[20]  someArray;
    vec<uint8_t> data;
};

Quy ước đặt tên

Tên hàm, tên biến và tên tệp phải mang tính mô tả; tránh viết tắt quá nhiều. Coi từ viết tắt là từ (ví dụ: sử dụng INfc thay vì INFC).

Cấu trúc thư mục và cách đặt tên tệp

Cấu trúc thư mục sẽ có dạng như sau:

  • ROOT-DIRECTORY
    • MODULE
      • SUBMODULE (không bắt buộc, có thể có nhiều cấp)
        • VERSION
          • Android.mk
          • IINTERFACE_1.hal
          • IINTERFACE_2.hal
          • IINTERFACE_N.hal
          • types.hal (không bắt buộc)

Trong trường hợp:

  • ROOT-DIRECTORY là:
    • hardware/interfaces cho các gói HIDL cốt lõi.
    • vendor/VENDOR/interfaces cho các gói của nhà cung cấp, trong đó VENDOR đề cập đến nhà cung cấp SoC hoặc OEM/ODM.
  • MODULE phải là một từ viết thường mô tả hệ thống con (ví dụ: nfc). Nếu cần nhiều từ, hãy dùng SUBMODULE lồng nhau. Có thể có nhiều cấp độ lồng ghép.
  • VERSION phải có phiên bản chính xác (major.minor) như mô tả trong phần Phiên bản.
  • IINTERFACE_X phải là tên giao diện có UpperCamelCase/PascalCase (ví dụ: INfc) như mô tả trong phần Tên giao diện.

Ví dụ:

  • hardware/interfaces
    • nfc
      • 1.0
        • Android.mk
        • INfc.hal
        • INfcClientCallback.hal
        • types.hal

Lưu ý: Tất cả các tệp phải có quyền không thực thi (trong Git).

Tên gói

Tên gói phải sử dụng định dạng tên đủ điều kiện (FQN) sau đây (được gọi là PACKAGE-NAME):

PACKAGE.MODULE[.SUBMODULE[.SUBMODULE[]]]@VERSION

Trong trường hợp:

  • PACKAGE là gói ánh xạ đến ROOT-DIRECTORY. Cụ thể, PACKAGE là:
    • android.hardware cho các gói HIDL cốt lõi (ánh xạ đến hardware/interfaces).
    • vendor.VENDOR.hardware cho các gói của nhà cung cấp, trong đó VENDOR đề cập đến nhà cung cấp SoC hoặc OEM/ODM (ánh xạ đến vendor/VENDOR/interfaces).
  • MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION là tên thư mục giống hệt nhau trong cấu trúc được mô tả trong phần Cấu trúc thư mục.
  • Tên gói phải là chữ thường. Nếu có nhiều hơn một từ, các từ đó phải được dùng làm mô-đun con hoặc được viết bằng snake_case.
  • Không được dùng dấu cách.

FQN luôn được dùng trong các khai báo gói.

Phiên bản

Các phiên bản phải có định dạng sau:

MAJOR.MINOR

Cả phiên bản MAJORMINOR đều phải là một số nguyên duy nhất. HIDL sử dụng các quy tắc phân phiên theo ngữ nghĩa.

Nhập

Tệp nhập có một trong 3 định dạng sau:

  • Nhập toàn bộ gói: import PACKAGE-NAME;
  • Nhập một phần: import PACKAGE-NAME::UDT; (hoặc, nếu loại được nhập nằm trong cùng một gói,import UDT;
  • Chỉ nhập các loại: import PACKAGE-NAME::types;

PACKAGE-NAME tuân theo định dạng trong Tên gói. types.hal của gói hiện tại (nếu có) sẽ tự động được nhập (không nhập một cách rõ ràng).

Tên đủ điều kiện (FQN)

Chỉ sử dụng tên đủ điều kiện cho một loại nhập do người dùng xác định khi cần thiết. Bỏ qua PACKAGE-NAME nếu loại nhập nằm trong cùng một gói. FQN không được chứa dấu cách. Ví dụ về tên đủ điều kiện:

android.hardware.nfc@1.0::INfcClientCallback

Trong một tệp khác trong android.hardware.nfc@1.0, hãy tham chiếu đến giao diện ở trên dưới dạng INfcClientCallback. Nếu không, chỉ sử dụng tên đủ điều kiện.

Nhóm và sắp xếp các tệp nhập

Sử dụng một dòng trống sau khai báo gói (trước các lệnh nhập). Mỗi câu lệnh nhập phải chiếm một dòng duy nhất và không được thụt lề. Nhập theo nhóm theo thứ tự sau:

  1. Các gói android.hardware khác (sử dụng tên đủ điều kiện).
  2. Các gói vendor.VENDOR khác (sử dụng tên đủ điều kiện).
    • Mỗi nhà cung cấp phải là một nhóm.
    • Sắp xếp nhà cung cấp theo thứ tự bảng chữ cái.
  3. Nhập từ các giao diện khác trong cùng một gói (sử dụng tên đơn giản).

Sử dụng một dòng trống giữa các nhóm. Trong mỗi nhóm, hãy sắp xếp các câu lệnh nhập theo bảng chữ cái. Ví dụ:

import android.hardware.nfc@1.0::INfc;
import android.hardware.nfc@1.0::INfcClientCallback;

/* Importing the whole module. */
import vendor.barvendor.bar@3.1;

import vendor.foovendor.foo@2.2::IFooBar;
import vendor.foovendor.foo@2.2::IFooFoo;

import IBar;
import IFoo;

Tên giao diện

Tên giao diện phải bắt đầu bằng I, theo sau là tên UpperCamelCase/PascalCase. Bạn phải xác định một giao diện có tên IFoo trong tệp IFoo.hal. Tệp này chỉ có thể chứa các định nghĩa cho giao diện IFoo (giao diện INAME phải nằm trong INAME.hal).

Hàm

Đối với tên hàm, đối số và tên biến trả về, hãy dùng lowerCamelCase. Ví dụ:

open(INfcClientCallback clientCallback) generates (int32_t retVal);
oneway pingAlive(IFooCallback cb);

Tên trường cấu trúc và hợp nhất

Đối với tên trường struct hoặc union, hãy dùng lowerCamelCase. Ví dụ:

struct FooReply {
    vec<uint8_t> replyData;
}

Nhập tên

Tên loại đề cập đến các định nghĩa về cấu trúc hoặc liên kết, định nghĩa về loại enum và typedef. Đối với những tên này, hãy dùng UpperCamelCase/PascalCase. Ví dụ:

enum NfcStatus : int32_t {
    /*...*/
};
struct NfcData {
    /*...*/
};

Giá trị enum

Giá trị enum phải là UPPER_CASE_WITH_UNDERSCORES. Khi truyền các giá trị enum làm đối số hàm và trả về các giá trị đó dưới dạng giá trị trả về của hàm, hãy sử dụng kiểu enum thực tế (không phải kiểu số nguyên cơ bản). Ví dụ:

enum NfcStatus : int32_t {
    HAL_NFC_STATUS_OK               = 0,
    HAL_NFC_STATUS_FAILED           = 1,
    HAL_NFC_STATUS_ERR_TRANSPORT    = 2,
    HAL_NFC_STATUS_ERR_CMD_TIMEOUT  = 3,
    HAL_NFC_STATUS_REFUSED          = 4
};

Lưu ý: Loại cơ bản của một loại enum được khai báo rõ ràng sau dấu hai chấm. Vì không phụ thuộc vào trình biên dịch, nên việc sử dụng kiểu enum thực tế sẽ rõ ràng hơn.

Đối với tên đủ điều kiện của các giá trị enum, dấu hai chấm được dùng giữa tên loại enum và tên giá trị enum:

PACKAGE-NAME::UDT[.UDT[.UDT[…]]:ENUM_VALUE_NAME

Không được có dấu cách trong tên đủ điều kiện. Chỉ sử dụng tên đủ điều kiện khi cần thiết và bỏ qua các phần không cần thiết. Ví dụ:

android.hardware.foo@1.0::IFoo.IFooInternal.FooEnum:ENUM_OK

Bình luận

Đối với một dòng nhận xét, //, /* *//** */ là đủ.

// This is a single line comment
/* This is also single line comment */
/** This is documentation comment */
  • Sử dụng /* */ cho bình luận. Mặc dù HIDL hỗ trợ // cho phần nhận xét, nhưng bạn không nên dùng vì chúng không xuất hiện trong đầu ra được tạo.
  • Sử dụng /** */ cho tài liệu đã tạo. Bạn chỉ có thể áp dụng các chú thích này cho các khai báo về loại, phương thức, trường và giá trị enum. Ví dụ:
    /** Replied status */
    enum TeleportStatus {
        /** Object entirely teleported. */
        OK              = 0,
        /** Methods return this if teleportation is not completed. */
        ERROR_TELEPORT  = 1,
        /**
         * Teleportation could not be completed due to an object
         * obstructing the path.
         */
        ERROR_OBJECT    = 2,
        ...
    }
  • Bắt đầu nhận xét nhiều dòng bằng /** trên một dòng riêng. Sử dụng * ở đầu mỗi dòng. Kết thúc chú thích bằng */ trên một dòng riêng biệt, căn chỉnh các dấu hoa thị. Ví dụ:
    /**
     * My multi-line
     * comment
     */
  • Thông báo cấp phép và nhật ký thay đổi phải bắt đầu một dòng mới bằng /* (một dấu hoa thị), sử dụng * ở đầu mỗi dòng và đặt */ trên dòng cuối cùng (các dấu hoa thị phải thẳng hàng). Ví dụ:
    /*
     * Copyright (C) 2017 The Android Open Source Project
     * ...
     */
    
    /*
     * Changelog:
     * ...
     */

Nhận xét về tệp

Bắt đầu mỗi tệp bằng thông báo cấp phép thích hợp. Đối với các HAL cốt lõi, đây phải là giấy phép Apache của AOSP trong development/docs/copyright-templates/c.txt. Hãy nhớ cập nhật năm và sử dụng chú thích nhiều dòng theo kiểu /* */ như đã giải thích ở trên.

Bạn có thể đặt một dòng trống sau thông báo về giấy phép, sau đó là thông tin về nhật ký thay đổi/phiên bản. Sử dụng nhận xét nhiều dòng theo kiểu /* */ như đã giải thích ở trên, đặt dòng trống sau nhật ký thay đổi, sau đó theo dõi bằng khai báo gói.

Bình luận về việc cần làm

TODO phải chứa chuỗi TODO viết hoa toàn bộ, theo sau là dấu hai chấm. Ví dụ:

// TODO: remove this code before foo is checked in.

Bạn chỉ được phép sử dụng nhận xét TODO trong quá trình phát triển; những nhận xét này không được xuất hiện trong các giao diện đã xuất bản.

Nhận xét về giao diện và hàm (chuỗi tài liệu)

Sử dụng /** */ cho chuỗi tài liệu nhiều dòng và một dòng. Đừng dùng // cho chuỗi tài liệu.

Docstring cho các giao diện phải mô tả cơ chế chung của giao diện, lý do thiết kế, mục đích, v.v. Docstring cho các hàm phải dành riêng cho hàm (tài liệu cấp gói nằm trong tệp README trong thư mục gói).

/**
 * IFooController is the controller for foos.
 */
interface IFooController {
    /**
     * Opens the controller.
     *
     * @return status HAL_FOO_OK if successful.
     */
    open() generates (FooStatus status);

    /** Close the controller. */
    close();
};

Bạn phải thêm @params và @returns cho mỗi tham số/giá trị trả về:

  • Bạn phải thêm @param cho từng thông số. Sau đó là tên của tham số rồi đến chuỗi tài liệu.
  • Bạn phải thêm @return cho mỗi giá trị trả về. Sau đó là tên của giá trị trả về rồi đến chuỗi tài liệu.

Ví dụ:

/**
 * Explain what foo does.
 *
 * @param arg1 explain what arg1 is
 * @param arg2 explain what arg2 is
 * @return ret1 explain what ret1 is
 * @return ret2 explain what ret2 is
 */
foo(T arg1, T arg2) generates (S ret1, S ret2);

Quy tắc định dạng

Các quy tắc định dạng chung bao gồm:

  • Độ dài dòng. Mỗi dòng văn bản phải dài tối đa 100 cột.
  • Khoảng trắng. Không có khoảng trắng ở cuối dòng; dòng trống không được chứa khoảng trắng.
  • Không gian so với thẻ. Chỉ sử dụng dấu cách.
  • Kích thước thụt đầu dòng. Sử dụng 4 dấu cách cho các khối và 8 dấu cách cho các dòng ngắt
  • Chống đỡ. Ngoại trừ giá trị chú thích, dấu ngoặc nhọn mở nằm trên cùng dòng với mã trước đó, nhưng dấu ngoặc nhọn đóng và dấu chấm phẩy sau đó chiếm toàn bộ dòng. Ví dụ:
    interface INfc {
        close();
    };

Khai báo gói

Khai báo gói phải ở đầu tệp sau thông báo về giấy phép, phải chiếm toàn bộ dòng và không được thụt lề. Các gói được khai báo bằng định dạng sau (để biết cách định dạng tên, hãy xem Tên gói):

package PACKAGE-NAME;

Ví dụ:

package android.hardware.nfc@1.0;

Khai báo hàm

Tên hàm, tham số, generates và giá trị trả về phải nằm trên cùng một dòng nếu vừa. Ví dụ:

interface IFoo {
    /** ... */
    easyMethod(int32_t data) generates (int32_t result);
};

Nếu chúng không vừa trên cùng một dòng, hãy cố gắng đặt các tham số và giá trị trả về ở cùng cấp thụt lề và phân biệt generate để giúp người đọc nhanh chóng xem các tham số và giá trị trả về. Ví dụ:

interface IFoo {
    suchALongMethodThatCannotFitInOneLine(int32_t theFirstVeryLongParameter,
                                          int32_t anotherVeryLongParameter);
    anEvenLongerMethodThatCannotFitInOneLine(int32_t theFirstLongParameter,
                                             int32_t anotherVeryLongParameter)
                                  generates (int32_t theFirstReturnValue,
                                             int32_t anotherReturnValue);
    superSuperSuperSuperSuperSuperSuperLongMethodThatYouWillHateToType(
            int32_t theFirstVeryLongParameter, // 8 spaces
            int32_t anotherVeryLongParameter
        ) generates (
            int32_t theFirstReturnValue,
            int32_t anotherReturnValue
        );
    /* method name is even shorter than 'generates' */
    foobar(AReallyReallyLongType aReallyReallyLongParameter,
           AReallyReallyLongType anotherReallyReallyLongParameter)
        generates (ASuperLongType aSuperLongReturnValue, // 4 spaces
                   ASuperLongType anotherSuperLongReturnValue);
}

Thông tin chi tiết bổ sung:

  • Dấu ngoặc đơn mở luôn nằm trên cùng dòng với tên hàm.
  • Không có dấu cách giữa tên hàm và dấu ngoặc đơn mở.
  • Không có khoảng trống giữa dấu ngoặc đơn và tham số ngoại trừ khi có dấu xuống dòng giữa chúng.
  • Nếu generates nằm trên cùng dòng với dấu ngoặc đóng trước đó, hãy sử dụng khoảng trắng đứng trước. Nếu generates nằm trên cùng một dòng với dấu ngoặc đơn mở tiếp theo, hãy thêm một khoảng trắng.
  • Căn chỉnh tất cả các tham số và giá trị trả về (nếu có thể).
  • Mức thụt lề mặc định là 4 dấu cách.
  • Các tham số được xuống dòng sẽ được căn chỉnh với tham số đầu tiên trên dòng trước, nếu không, các tham số này sẽ có khoảng thụt đầu dòng là 8 dấu cách.

Chú thích

Hãy sử dụng định dạng sau cho chú thích:

@annotate(keyword = value, keyword = {value, value, value})

Sắp xếp chú thích theo thứ tự bảng chữ cái và sử dụng dấu cách xung quanh dấu bằng. Ví dụ:

@callflow(key = value)
@entry
@exit

Đảm bảo chú thích chiếm toàn bộ dòng. Ví dụ:

/* Good */
@entry
@exit

/* Bad */
@entry @exit

Nếu chú giải không vừa trên cùng một dòng, hãy thụt lề 8 dấu cách. Ví dụ:

@annotate(
        keyword = value,
        keyword = {
                value,
                value
        },
        keyword = value)

Nếu toàn bộ mảng giá trị không vừa trên cùng một dòng, hãy đặt dấu ngắt dòng sau dấu ngoặc nhọn mở { và sau mỗi dấu phẩy bên trong mảng. Đặt dấu ngoặc đơn đóng ngay sau giá trị cuối cùng. Đừng đặt dấu ngoặc nhọn nếu chỉ có một giá trị.

Nếu toàn bộ mảng giá trị có thể nằm trên cùng một dòng, thì đừng dùng dấu cách sau dấu ngoặc nhọn mở và trước dấu ngoặc nhọn đóng, đồng thời dùng một dấu cách sau mỗi dấu phẩy. Ví dụ:

/* Good */
@callflow(key = {"val", "val"})

/* Bad */
@callflow(key = { "val","val" })

KHÔNG được có dòng trống giữa chú thích và khai báo hàm. Ví dụ:

/* Good */
@entry
foo();

/* Bad */
@entry

foo();

Khai báo enum

Hãy sử dụng các quy tắc sau đây cho khai báo enum:

  • Nếu các khai báo enum được chia sẻ với một gói khác, hãy đặt các khai báo trong types.hal thay vì nhúng bên trong một giao diện.
  • Sử dụng dấu cách trước và sau dấu hai chấm, cũng như dấu cách sau loại cơ bản trước dấu ngoặc mở.
  • Giá trị enum cuối cùng có thể không có dấu phẩy thừa.

Khai báo cấu trúc

Sử dụng các quy tắc sau cho khai báo cấu trúc:

  • Nếu các khai báo struct được chia sẻ với một gói khác, hãy đặt các khai báo trong types.hal thay vì nhúng bên trong một giao diện.
  • Sử dụng dấu cách sau tên loại cấu trúc trước dấu ngoặc nhọn mở.
  • Căn chỉnh tên trường (không bắt buộc). Ví dụ:
    struct MyStruct {
        vec<uint8_t>   data;
        int32_t        someInt;
    }

Khai báo mảng

Đừng đặt dấu cách giữa những nội dung sau:

  • Loại phần tử và dấu ngoặc vuông mở.
  • Dấu ngoặc vuông mở và kích thước mảng.
  • Kích thước mảng và dấu ngoặc vuông đóng.
  • Dấu ngoặc vuông đóng và dấu ngoặc vuông mở tiếp theo, nếu có nhiều hơn một phương diện.

Ví dụ:

/* Good */
int32_t[5] array;

/* Good */
int32_t[5][6] multiDimArray;

/* Bad */
int32_t [ 5 ] [ 6 ] array;

Vectơ

Đừng đặt dấu cách giữa những nội dung sau:

  • vec và dấu ngoặc góc mở.
  • Dấu ngoặc góc mở và loại phần tử (Ngoại lệ: loại phần tử cũng là vec).
  • Loại phần tử và dấu ngoặc nhọn đóng (Ngoại lệ: loại phần tử cũng là vec).

Ví dụ:

/* Good */
vec<int32_t> array;

/* Good */
vec<vec<int32_t>> array;

/* Good */
vec< vec<int32_t> > array;

/* Bad */
vec < int32_t > array;

/* Bad */
vec < vec < int32_t > > array;