Guia de estilo de código

O estilo de código HIDL se assemelha ao código C++ no framework Android, com recuos de quatro espaços e nomes de arquivos em maiúsculas e minúsculas. Declarações de pacotes, importações e docstrings são semelhantes aos do Java, com pequenas modificações.

Os exemplos a seguir para IFoo.hal e types.hal ilustram estilos de código HIDL e fornecem links rápidos para detalhes sobre cada estilo (IFooClientCallback.hal, IBar.hal e IBaz.hal foram omitidos).

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;
};

Convenções de nomenclatura

Os nomes de funções, variáveis e arquivos precisam ser descritivos. Evite abreviações em excesso. Tratar acrônimos como palavras (por exemplo, use INfc em vez de INFC).

Estrutura de diretórios e nomenclatura de arquivos

A estrutura de diretórios vai aparecer assim:

  • ROOT-DIRECTORY
    • MODULE
      • SUBMODULE (opcional, pode ter mais de um nível)
        • VERSION
          • Android.mk
          • IINTERFACE_1.hal
          • IINTERFACE_2.hal
          • IINTERFACE_N.hal
          • types.hal (opcional)

Em que:

  • ROOT-DIRECTORY é:
    • hardware/interfaces para pacotes HIDL principais.
    • vendor/VENDOR/interfaces para pacotes de fornecedores, em que VENDOR se refere a um fornecedor de SoC ou a um OEM/ODM.
  • MODULE precisa ser uma palavra em minúsculas que descreva o subsistema (por exemplo, nfc). Se mais de uma palavra for necessária, use SUBMODULE aninhado. Pode haver mais de um nível de aninhamento.
  • VERSION precisa ser exatamente a mesma versão (principal.secundária) descrita em Versões.
  • IINTERFACE_X precisa ser o nome da interface com UpperCamelCase/PascalCase (por exemplo, INfc) conforme descrito em Nomes de interface.

Exemplo:

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

Observação:todos os arquivos precisam ter permissões não executáveis (no Git).

Nomes de pacote

Os nomes de pacotes precisam usar o seguinte formato de nome totalmente qualificado (FQN) (conhecido como PACKAGE-NAME):

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

Em que:

  • PACKAGE é o pacote que mapeia para o ROOT-DIRECTORY. Em particular, PACKAGE é:
    • android.hardware para pacotes HIDL principais (mapeamento para hardware/interfaces).
    • vendor.VENDOR.hardware para pacotes de fornecedores, em que VENDOR se refere a um fornecedor de SoC ou um OEM/ODM (mapeamento para vendor/VENDOR/interfaces).
  • MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION são exatamente os mesmos nomes de pastas na estrutura descrita em Estrutura de diretórios.
  • Os nomes de pacotes precisam estar em minúsculas. Se tiverem mais de uma palavra, elas precisam ser usadas como submódulos ou escritas em snake_case.
  • Não use espaços.

O FQN é sempre usado em declarações de pacote.

Versões

As versões precisam ter o seguinte formato:

MAJOR.MINOR

As versões MAJOR e MINOR precisam ser um único número inteiro. O HIDL usa regras de controle de versão semântico.

Importações

Uma importação tem um dos três formatos a seguir:

  • Importações de pacotes inteiros: import PACKAGE-NAME;
  • Importações parciais: import PACKAGE-NAME::UDT; ou, se o tipo importado estiver no mesmo pacote,import UDT;
  • Importações somente de tipos: import PACKAGE-NAME::types;

O PACKAGE-NAME segue o formato em Nomes de pacotes. O types.hal do pacote atual (se existir) é importado automaticamente. Não faça a importação de forma explícita.

Nomes totalmente qualificados (FQNs)

Use nomes totalmente qualificados para uma importação de tipo definida pelo usuário apenas quando necessário. Omita PACKAGE-NAME se o tipo de importação estiver no mesmo pacote. Um FQN não pode conter espaços. Exemplo de um nome totalmente qualificado:

android.hardware.nfc@1.0::INfcClientCallback

Em outro arquivo em android.hardware.nfc@1.0, consulte a interface acima como INfcClientCallback. Caso contrário, use apenas o nome totalmente qualificado.

Agrupamento e ordenação de importações

Use uma linha em branco após a declaração do pacote (antes das importações). Cada importação precisa ocupar uma única linha e não pode ter recuo. Agrupe as importações na seguinte ordem:

  1. Outros pacotes android.hardware (use nomes totalmente qualificados).
  2. Outros pacotes vendor.VENDOR (use nomes totalmente qualificados).
    • Cada fornecedor precisa ser um grupo.
    • Ordenar os fornecedores em ordem alfabética.
  3. Importações de outras interfaces no mesmo pacote (use nomes simples).

Use uma linha vazia entre os grupos. Dentro de cada grupo, classifique as importações em ordem alfabética. Exemplo:

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;

Nomes de interfaces

Os nomes de interface precisam começar com um I, seguido por um nome UpperCamelCase/PascalCase. Uma interface com o nome IFoo precisa ser definida no arquivo IFoo.hal. Esse arquivo pode conter definições apenas para a interface IFoo (a interface INAME precisa estar em INAME.hal).

Funções

Para nomes de funções, argumentos e variáveis de retorno, use lowerCamelCase. Exemplo:

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

Nomes de campos de struct e união

Para nomes de campos de struct ou união, use lowerCamelCase. Exemplo:

struct FooReply {
    vec<uint8_t> replyData;
}

Nomes de tipo

Os nomes de tipos se referem a definições de struct ou união, definições de tipo de enumeração e typedefs. Para esses nomes, use UpperCamelCase/PascalCase. Exemplos:

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

Valores de enumeração

Os valores de enumeração precisam ser UPPER_CASE_WITH_UNDERSCORES. Ao transmitir valores de enumeração como argumentos de função e retorná-los como retornos de função, use o tipo de enumeração real (não o tipo inteiro subjacente). Exemplo:

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
};

Observação:o tipo subjacente de um tipo de enumeração é declarado explicitamente após os dois-pontos. Como não depende do compilador, usar o tipo de enumeração real é mais claro.

Para nomes totalmente qualificados de valores de enumeração, um dois-pontos é usado entre o nome do tipo de enumeração e o nome do valor de enumeração:

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

Não pode haver espaços em um nome totalmente qualificado. Use um nome totalmente qualificado apenas quando necessário e omita partes desnecessárias. Exemplo:

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

Comentários

Para um comentário de linha única, //, /* */ e /** */ são adequados.

// This is a single line comment
/* This is also single line comment */
/** This is documentation comment */
  • Use /* */ para comentários. Embora o HIDL seja compatível com // para comentários, não é recomendável usá-los porque eles não aparecem na saída gerada.
  • Use /** */ para documentação gerada. Elas podem ser aplicadas somente a declarações de tipo, método, campo e valor de enumeração. Exemplo:
    /** 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,
        ...
    }
  • Comece comentários de várias linhas com /** em uma linha separada. Use * no início de cada linha. Termine o comentário com */ em uma linha separada, alinhando os asteriscos. Exemplo:
    /**
     * My multi-line
     * comment
     */
  • O aviso de licenciamento e os registros de mudanças precisam começar uma nova linha com /* (um único asterisco), usar * no início de cada linha e colocar */ na última linha sozinho (os asteriscos precisam estar alinhados). Exemplo:
    /*
     * Copyright (C) 2017 The Android Open Source Project
     * ...
     */
    
    /*
     * Changelog:
     * ...
     */

Comentários em arquivos

Comece cada arquivo com o aviso de licenciamento adequado. Para HALs principais, essa deve ser a licença Apache do AOSP em development/docs/copyright-templates/c.txt. Não se esqueça de atualizar o ano e usar comentários de várias linhas no estilo /* */, conforme explicado acima.

Você também pode colocar uma linha em branco após o aviso de licença, seguida por um changelog/informações de controle de versão. Use comentários de várias linhas no estilo /* */, conforme explicado acima, coloque a linha vazia após o changelog e siga com a declaração do pacote.

Comentários TODO

Os TODOs precisam incluir a string TODO com todas as letras em caixa alta, seguidas por dois pontos. Exemplo:

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

Os comentários TODO são permitidos apenas durante o desenvolvimento e não podem existir em interfaces publicadas.

Comentários de interface e função (docstrings)

Use /** */ para docstrings de várias linhas e de uma linha. Não use // para docstrings.

As docstrings das interfaces precisam descrever mecanismos gerais da interface, lógica de design, propósito etc. As docstrings das funções precisam ser específicas para a função. A documentação no nível do pacote fica em um arquivo README no diretório do pacote.

/**
 * 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();
};

Adicione @params e @returns para cada parâmetro/valor de retorno:

  • @param precisa ser adicionado a cada parâmetro. Ele precisa ser seguido pelo nome do parâmetro e pela docstring.
  • @return precisa ser adicionado a cada valor de retorno. Ele deve ser seguido pelo nome do valor de retorno e pela docstring.

Exemplo:

/**
 * 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);

Regras de formatação

As regras gerais de formatação incluem:

  • Comprimento da linha. Cada linha de texto precisa ter no máximo 100 colunas.
  • Espaços em branco. Não há espaços em branco no final das linhas. As linhas vazias não podem conter espaços em branco.
  • Espaços x guias. Use apenas espaços.
  • Tamanho do recuo. Use 4 espaços para blocos e 8 espaços para uniões de linha.
  • Apoio. Com exceção dos valores de anotação, uma chave aberta fica na mesma linha do código anterior, mas uma chave fechada e o ponto e vírgula a seguir ocupam a linha inteira. Exemplo:
    interface INfc {
        close();
    };

Declaração de pacote

A declaração do pacote precisa estar na parte de cima do arquivo após o aviso de licença, ocupar a linha inteira e não ter recuo. Os pacotes são declarados usando o seguinte formato. Para formatação de nomes, consulte Nomes de pacotes:

package PACKAGE-NAME;

Exemplo:

package android.hardware.nfc@1.0;

Declarações de função

O nome da função, os parâmetros, generates e os valores de retorno devem estar na mesma linha se couberem. Exemplo:

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

Se eles não couberem na mesma linha, tente colocar parâmetros e valores de retorno no mesmo nível de indentação e distinga generate para ajudar o leitor a ver rapidamente os parâmetros e valores de retorno. Exemplo:

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);
}

Mais detalhes:

  • Um parêntese aberto sempre fica na mesma linha do nome da função.
  • Não há espaços entre o nome da função e o parêntese de abertura.
  • Não há espaços entre os parênteses e os parâmetros exceto quando há quebras de linha entre eles.
  • Se generates estiver na mesma linha do parêntese de fechamento anterior, use um espaço antes. Se generates estiver na mesma linha que o próximo parêntese aberto, adicione um espaço.
  • Alinhe todos os parâmetros e valores de retorno (se possível).
  • O recuo padrão é de quatro espaços.
  • Os parâmetros agrupados são alinhados aos primeiros parâmetros da linha anterior. Caso contrário, eles têm um recuo de oito espaços.

Anotações

Use o seguinte formato para anotações:

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

Classifique as anotações em ordem alfabética e use espaços ao redor dos sinais de igual. Exemplo:

@callflow(key = value)
@entry
@exit

Verifique se uma anotação ocupa a linha inteira. Exemplos:

/* Good */
@entry
@exit

/* Bad */
@entry @exit

Se as anotações não couberem na mesma linha, recue com oito espaços. Exemplo:

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

Se toda a matriz de valores não couber na mesma linha, coloque quebras de linha depois das chaves abertas { e depois de cada vírgula dentro da matriz. Coloque o parêntese de fechamento imediatamente após o último valor. Não coloque chaves se houver apenas um valor.

Se toda a matriz de valores puder caber na mesma linha, não use espaços após as chaves de abertura e antes das chaves de fechamento. Use um espaço após cada vírgula. Exemplos:

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

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

NÃO pode haver linhas vazias entre as anotações e a declaração de função. Exemplos:

/* Good */
@entry
foo();

/* Bad */
@entry

foo();

Declarações de enumeração

Use as seguintes regras para declarações de enumeração:

  • Se as declarações de enumeração forem compartilhadas com outro pacote, coloque-as em types.hal em vez de incorporar em uma interface.
  • Use um espaço antes e depois dos dois-pontos e um espaço após o tipo subjacente antes da chave de abertura.
  • O último valor de enumeração não pode ter uma vírgula extra.

Declarações de struct

Use as seguintes regras para declarações de struct:

  • Se as declarações de struct forem compartilhadas com outro pacote, coloque-as em types.hal em vez de incorporar em uma interface.
  • Use um espaço após o nome do tipo de struct e antes da chave de abertura.
  • Alinhe os nomes dos campos (opcional). Exemplo:
    struct MyStruct {
        vec<uint8_t>   data;
        int32_t        someInt;
    }

Declarações de matriz

Não coloque espaços entre os seguintes elementos:

  • Tipo de elemento e colchete de abertura.
  • Colchete aberto e tamanho da matriz.
  • Tamanho da matriz e colchete de fechamento.
  • Colchete de fechamento e o próximo colchete de abertura, se houver mais de uma dimensão.

Exemplos:

/* Good */
int32_t[5] array;

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

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

Vetores

Não coloque espaços entre os seguintes elementos:

  • vec e sinal de menor que.
  • Sinal de menor que e tipo de elemento (Exceção: o tipo de elemento também é um vec).
  • Tipo de elemento e colchete angular de fechamento (Exceção: o tipo de elemento também é um vec).

Exemplos:

/* 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;