O HIDL exige que todas as interfaces escritas nele tenham um controle de versões. Após uma HAL é publicada, fica congelada e quaisquer alterações adicionais devem ser feitas em um nova versão da interface. Embora uma determinada interface publicada não possa ser modificado, ele pode ser estendido por outra interface.
Estrutura do código HIDL
O código HIDL é organizado em regras definidas pelo usuário tipos, interfaces e pacotes:
- Tipos definidos pelo usuário (UDTs). O HIDL dá acesso a um conjunto de tipos de dados primitivos, que podem ser usados para compor tipos mais complexos via estruturas, uniões e enumerações. Os UDTs são transmitidos para métodos de do Google e podem ser definidas no nível de um pacote (comum a todos ou localmente a uma interface.
- Interfaces. Como um elemento básico do HIDL, uma interface consiste em declarações de UDT e método. As interfaces também podem herdar outra interface.
- Pacotes. organiza as interfaces HIDL relacionadas e os dados
em que operam. Um pacote é identificado por um nome e uma versão
inclui o seguinte:
- Arquivo de definição de tipo de dados chamado
types.hal
. - Zero ou mais interfaces, cada uma no próprio arquivo
.hal
.
- Arquivo de definição de tipo de dados chamado
O arquivo de definição de tipo de dados types.hal
contém apenas UDTs (todos
UDTs no nível do pacote são mantidos em um único arquivo). Representações no destino
estão disponíveis para todas as interfaces no pacote.
Conceito de controle de versões
Um pacote HIDL (como android.hardware.nfc
), depois de ser
publicado para uma determinada versão (como 1.0
), é imutável;
não pode ser alterado. Modificações nas interfaces do pacote ou em quaisquer
as mudanças nos UDTs só podem ocorrer em outro pacote.
No HIDL, o controle de versões é aplicado no nível do pacote, não da interface, e todas as interfaces e UDTs de um pacote compartilham a mesma versão. Empacotar as versões seguem as semânticas controle de versões sem o nível do patch e os componentes de metadados de build. Em um objeto pacote fornecido, uma promoção de versão secundária implica a nova versão do o pacote é compatível com versões anteriores do pacote antigo e um principal version implica que a nova versão do pacote não está compatível com versões anteriores do pacote antigo.
Conceitualmente, um pacote pode se relacionar com outro de várias maneiras:
- Nem um pouco.
- Extensibilidade compatível com versões anteriores no nível do pacote. Isso
ocorre para novos uprevs de versão secundária (próxima revisão incrementada) de um pacote;
o novo pacote tem o mesmo nome e versão principal que o pacote antigo, mas uma
e em uma versão secundária mais alta. Funcionalmente, o novo pacote é um superconjunto do antigo
, ou seja:
- Interfaces de nível superior do pacote principal estão presentes no novo pacote,
embora as interfaces possam ter novos métodos, novos UDTs de interface local
extensão de nível de interface descrita abaixo) e novos UDTs em
types.hal
: - Também é possível adicionar novas interfaces ao novo pacote.
- Todos os tipos de dados do pacote principal estão presentes no novo pacote e podem ser tratados pelos métodos (possivelmente reimplementados) do pacote antigo.
- Novos tipos de dados também podem ser adicionados para uso por novos métodos de reformulação interfaces já existentes ou novas.
- Interfaces de nível superior do pacote principal estão presentes no novo pacote,
embora as interfaces possam ter novos métodos, novos UDTs de interface local
extensão de nível de interface descrita abaixo) e novos UDTs em
- Extensibilidade compatível com versões anteriores no nível da interface. A nova
também pode estender o pacote original por consistindo em operações lógicas
interfaces que simplesmente oferecem uma funcionalidade adicional, e não a principal.
Para este propósito, as seguintes opções podem ser recomendadas:
- As interfaces do novo pacote precisam recorrer aos tipos de dados do pacote antigo .
- As interfaces do novo pacote podem estender as interfaces de um ou mais pacotes pacotes.
- Estender a incompatibilidade original com versões anteriores. Esta é uma o uprev da versão principal do pacote e não é necessária qualquer correlação entre os dois. Na medida que existe, pode ser expressada com uma combinação de tipos da versão mais antiga do pacote e a herança de um subconjunto interfaces de pacotes antigos.
Estruturação de interfaces
Para uma interface bem estruturada, adicionar novos tipos de funcionalidades que não fazem parte do design original devem exigir uma modificação no HIDL interface gráfica do usuário. Por outro lado, se você puder ou esperar fazer uma mudança em ambos os lados a interface que apresenta novas funcionalidades sem alterá-la a interface não será estruturada.
O Treble oferece suporte a componentes de sistema e de fornecedor compilados separadamente em que os
O vendor.img
em um dispositivo e o system.img
podem ser
compilada separadamente. Todas as interações entre vendor.img
e
system.img
deve ser definida de forma explícita e completa para que possa
continuam funcionando por muitos anos. Isso inclui muitas plataformas de API,
é o mecanismo de IPC que o HIDL usa para comunicação entre processos no
Limite de system.img
/vendor.img
.
Requisitos
Todos os dados transmitidos pelo HIDL precisam ser explicitamente definidos. Para garantir que uma e o cliente podem continuar a trabalhar juntos mesmo quando compilados separadamente ou desenvolvidos de forma independente, os dados devem aderir ao seguinte requisitos:
- Pode ser descrito diretamente no HIDL (usando structs enums etc.) com nomes semânticos e significados.
- Podem ser descritas por uma norma pública, como ISO/IEC 7816.
- Pode ser descrito por um padrão ou layout físico de hardware.
- Podem ser dados opacos (como chaves públicas, IDs etc.), se necessário.
Se dados opacos forem usados, eles precisarão ser lidos apenas por um lado do HIDL
interface gráfica do usuário. Por exemplo, se o código vendor.img
der um componente na
Use system.img
para uma mensagem de string ou vec<uint8_t>
dados, esses dados não podem ser analisados pelo próprio system.img
. pode
ser retornados apenas para vendor.img
para interpretar. Quando
transmitindo um valor de vendor.img
para o código do fornecedor em
system.img
ou em outro dispositivo, o formato e a forma dos dados
deve ser interpretada devem ser descritas com exatidão e ainda fazer parte do
do BigQuery.
Diretrizes
Você deve conseguir escrever uma implementação ou cliente de uma HAL usando apenas os arquivos .hal (ou seja, você não precisa consultar a fonte do Android nem os arquivos normas). Recomendamos especificar o comportamento exato necessário. Declarações como como "uma implementação pode fazer A ou B" incentivar as implementações a se tornarem entrelaçada com os clientes com quem são desenvolvidas.
Layout do código HIDL
O HIDL inclui pacotes principais e de fornecedores.
As interfaces HIDL principais são as especificadas pelo Google. Os pacotes a que pertencem
para começar com android.hardware.
e são nomeados por subsistema,
possivelmente com níveis aninhados de nomes. Por exemplo, o pacote NFC tem o nome
android.hardware.nfc
e o pacote da câmera está
android.hardware.camera
. Em geral, um pacote principal tem o nome
android.hardware.
[name1
].[name2
]....
Os pacotes HIDL têm uma versão além do nome. Por exemplo, o pacote
android.hardware.camera
pode estar na versão 3.4
. isso é
importante, já que a versão de um pacote afeta sua colocação na árvore de origem.
Todos os pacotes principais são colocados em hardware/interfaces/
no
sistema de build. O pacote
android.hardware.
[name1
].[name2
]...
na versão $m.$n
está abaixo
hardware/interfaces/name1/name2/
.../$m.$n/
; pacote
A versão 3.4
do android.hardware.camera
está no diretório
hardware/interfaces/camera/3.4/.
Existe um mapeamento codificado
entre o prefixo de pacote android.hardware.
e o caminho
hardware/interfaces/
Pacotes não principais (fornecedor) são aqueles produzidos pelo fornecedor do SoC ou ODM. A
O prefixo de pacotes não principais é vendor.$(VENDOR).hardware.
, em que
$(VENDOR)
refere-se a um fornecedor de SoC ou OEM/ODM. Esse valor é mapeado para o caminho
vendor/$(VENDOR)/interfaces
na árvore (esse mapeamento também está
codificado).
Nomes de tipo definidos pelo usuário totalmente qualificados
No HIDL, todo UDT tem um nome totalmente qualificado que consiste no nome do UDT,
o nome do pacote em que o UDT está definido e a versão do pacote. A
O nome totalmente qualificado é usado somente quando as instâncias do tipo são declaradas e
e não onde o tipo em si está definido. Por exemplo, suponha que o pacote
A versão 1.0
do android.hardware.nfc,
define um struct
chamado NfcData
. No local da declaração (seja em
types.hal
ou em uma declaração de interface), a declaração
simplesmente:
struct NfcData { vec<uint8_t> data; };
Ao declarar uma instância desse tipo (seja em uma estrutura de dados ou como parâmetro de método), use o nome do tipo totalmente qualificado:
android.hardware.nfc@1.0::NfcData
A sintaxe geral é
PACKAGE@VERSION::UDT
, em que:
PACKAGE
é o nome separado por ponto de um pacote HIDL (por exemplo,android.hardware.nfc
).VERSION
é a versão principal.secundária separada por pontos formato do pacote (por exemplo,1.0
).UDT
é o nome separado por pontos de um UDT HIDL. Como o HIDL oferece suporte a UDTs aninhados, e interfaces HIDL podem conter UDTs (um tipo de aninhada), os pontos são usados para acessar os nomes.
Por exemplo, se a declaração aninhada a seguir tiver sido definida no diretório
Arquivo de tipos na versão do pacote android.hardware.example
1.0
:
// types.hal package android.hardware.example@1.0; struct Foo { struct Bar { // … }; Bar cheers; };
O nome totalmente qualificado para Bar
é
android.hardware.example@1.0::Foo.Bar
. Se, além de estar
do pacote acima, a declaração aninhada estava em uma interface chamada
IQuux
:
// IQuux.hal package android.hardware.example@1.0; interface IQuux { struct Foo { struct Bar { // … }; Bar cheers; }; doSomething(Foo f) generates (Foo.Bar fb); };
O nome totalmente qualificado para Bar
é
android.hardware.example@1.0::IQuux.Foo.Bar
.
Em ambos os casos, Bar
só pode ser chamado de Bar
.
no escopo da declaração de Foo
. Na embalagem
da interface, consulte Bar
via Foo
:
Foo.Bar
, como na declaração do método doSomething
acima. Como alternativa, você pode declarar o método de forma mais detalhada como:
// IQuux.hal doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);
Valores de enumeração totalmente qualificados
Se um UDT for do tipo enum, cada valor desse tipo terá um
nome totalmente qualificado que começa com o nome totalmente qualificado do tipo de enum;
seguido de dois pontos e do nome do valor do tipo enumerado. Por exemplo:
assumir pacote android.hardware.nfc,
versão 1.0
define um tipo de enumeração NfcStatus
:
enum NfcStatus { STATUS_OK, STATUS_FAILED };
Ao se referir a STATUS_OK
, o nome totalmente qualificado é:
android.hardware.nfc@1.0::NfcStatus:STATUS_OK
A sintaxe geral é
PACKAGE@VERSION::UDT:VALUE
,
em que:
PACKAGE@VERSION::UDT
é o exatamente o mesmo nome totalmente qualificado para o tipo de enumeração.VALUE
é o nome do valor.
Regras de inferência automática
Um nome UDT totalmente qualificado não precisa ser especificado. Um nome UDT pode omita com segurança o seguinte:
- O pacote, por exemplo,
@1.0::IFoo.Type
- O pacote e a versão, por exemplo,
IFoo.Type
O HIDL tenta completar o nome usando regras de interferência automática (regra menor número significa maior prioridade).
Regra 1
Se nenhum pacote e versão forem fornecidos, uma pesquisa de nome local será feita. Exemplo:
interface Nfc { typedef string NfcErrorMessage; send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m); };
NfcErrorMessage
é pesquisado localmente, e o typedef
acima dele. NfcData
também é pesquisado localmente, mas como é
não definidas localmente, as regras 2 e 3 serão usadas. @1.0::NfcStatus
fornece uma versão. Portanto, a regra 1 não se aplica.
Regra 2
Se a regra 1 falhar e um componente do nome totalmente qualificado estiver ausente
(pacote, versão ou pacote e versão), o componente é preenchido automaticamente com
informações do pacote atual. Em seguida, o compilador HIDL procura nas
arquivo atual (e todas as importações) para encontrar o nome totalmente qualificado preenchido automaticamente.
Usando o exemplo acima, considere a declaração de ExtendedNfcData
foram feitas no mesmo pacote (android.hardware.nfc
) com o mesmo
versão (1.0
) como NfcData
, desta forma:
struct ExtendedNfcData { NfcData base; // … additional members };
O compilador HIDL preenche o nome do pacote e o nome da versão do
pacote atual para produzir o nome UDT totalmente qualificado
android.hardware.nfc@1.0::NfcData
: Como o nome existe na
pacote atual (supondo que ele seja importado corretamente), ele é usado para o
declaração de serviço.
Um nome no pacote atual será importado somente se um dos seguintes itens for verdadeiro:
- Ela é importada explicitamente com uma instrução
import
. - Está definido em
types.hal
no pacote atual
O mesmo processo é seguido se NfcData
foi qualificado apenas por
o número da versão:
struct ExtendedNfcData { // autofill the current package name (android.hardware.nfc) @1.0::NfcData base; // … additional members };
Regra 3
Se a regra 2 não produzir uma correspondência (o UDT não está definido no
pacote), o compilador HIDL verifica se há uma correspondência em todos os pacotes importados.
Usando o exemplo acima, suponha que ExtendedNfcData
esteja declarado em
versão 1.1
do pacote android.hardware.nfc
;
1.1
importa 1.0
como deveria (consulte
Extensões no nível do pacote) e a definição
especifica apenas o nome UDT:
struct ExtendedNfcData { NfcData base; // … additional members };
O compilador procura qualquer UDT chamado NfcData
e encontra um em
android.hardware.nfc
na versão 1.0
, resultando em um
UDT totalmente qualificada de android.hardware.nfc@1.0::NfcData
. Se mais
for encontrada uma correspondência para um determinado UDT parcialmente qualificado, o compilador HIDL
gera um erro.
Exemplo
Usando a regra 2, um tipo importado definido no pacote atual é favorecido sobre um tipo importado de outro pacote:
// 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
é interpolado comoandroid.hardware.bar@1.0::S
e é encontrada embar/1.0/types.hal
(porquetypes.hal
é automaticamente importados).IFooCallback
é interpolado comoandroid.hardware.bar@1.0::IFooCallback
usando a regra 2, mas não foi encontrado porquebar/1.0/IFooCallback.hal
não foi importado automaticamente (comotypes.hal
). Assim, a regra 3 resolveandroid.hardware.foo@1.0::IFooCallback
, que é importado viaimport android.hardware.foo@1.0;
).
type.hal
Cada pacote HIDL contém um arquivo types.hal
com UDTs
que são compartilhados entre todas as interfaces participantes do pacote. Tipos de HIDL
são sempre públicos; independentemente de uma UDT ser declarada
types.hal
ou em uma declaração de interface, esses tipos são
acessíveis fora do escopo em que são definidos. types.hal
o objetivo não é descrever a API pública de um pacote, mas sim hospedar UDTs
usada por todas as interfaces dentro do pacote. Devido à natureza do HIDL, todos os UDTs
fazem parte da interface.
types.hal
consiste em UDTs e instruções import
.
Como o types.hal
é disponibilizado para todas as interfaces do
(é uma importação implícita), essas instruções import
são
no nível do pacote por definição. Os UDTs no types.hal
também podem incorporar
UDTs e interfaces importadas.
Por exemplo, para uma 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;
Os seguintes itens são importados:
android.hidl.base@1.0::IBase
(implicitamente)android.hardware.foo@1.0::types
(implicitamente)- Tudo em
android.hardware.bar@1.0
(incluindo todos) e astypes.hal
) types.hal
deandroid.hardware.baz@1.0::types
As interfaces emandroid.hardware.baz@1.0
não são importadas.IQux.hal
etypes.hal
deandroid.hardware.qux@1.0
Quuz
deandroid.hardware.quuz@1.0
(supondo queQuuz
é definido emtypes.hal
, todo o O arquivotypes.hal
é analisado, mas tipos diferentes deQuuz
não são importadas).
Controle de versões no nível da interface
Cada interface dentro de um pacote reside em seu próprio arquivo. O pacote que
à qual a interface pertence é declarada no topo da interface usando o
Instrução package
. Seguindo a declaração do pacote, zero ou mais
importações no nível da interface (parcial ou pacote inteiro) podem ser listadas. Exemplo:
package android.hardware.nfc@1.0;
No HIDL, as interfaces podem herdar de outras interfaces usando o
extends
palavra-chave. Para que uma interface estenda outra, ela
precisa ter acesso a ele usando uma instrução import
. O nome
interface sendo estendida (a interface base) segue as regras para type-name
explicada acima. Uma interface só pode herdar de uma interface.
O HIDL não é compatível com herança múltipla.
Os exemplos de controle de versão de uprev abaixo usam o seguinte pacote:
// 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); }
Regras de uprev
Para definir um pacote package@major.minor
, inclua A ou todo o elemento B.
precisa ser verdadeiro:
Regra A | "É uma versão secundária inicial": todas as versões secundárias anteriores,
package@major.0 , package@major.1 , ...,
Não é possível definir package@major.(minor-1) .
|
---|
Regra B | Todas as afirmações a seguir são verdadeiras:
|
---|
Devido à regra A:
- O pacote pode começar com qualquer número de versão secundária (por exemplo,
android.hardware.biometrics.fingerprint
começa em@2.1
. - O requisito "
android.hardware.foo@1.0
não está definido" significa o diretóriohardware/interfaces/foo/1.0
nem deveria existir.
Entretanto, a regra A não afeta um pacote com o mesmo nome, mas um
versão principal diferente (por exemplo,
android.hardware.camera.device
tem @1.0
e
@3.2
definido. @3.2
não precisa interagir com
@1.0
. Portanto, @3.2::IExtFoo
pode estender
@1.0::IFoo
.
Se o nome do pacote for diferente,
A package@major.minor::IBar
pode ser estendida de uma interface com
nome diferente (por exemplo, android.hardware.bar@1.0::IBar
pode
estendem android.hardware.baz@2.2::IBaz
). Se uma interface não
declarar explicitamente um supertipo com a palavra-chave extend
, ele
estende android.hidl.base@1.0::IBase
(exceto IBase
por conta própria).
B.2 e B.3 devem ser seguidas ao mesmo tempo. Por exemplo, mesmo se
android.hardware.foo@1.1::IFoo
prolonga
android.hardware.foo@1.0::IFoo
para passar a regra B.2, se uma
android.hardware.foo@1.1::IExtBar
extensão
android.hardware.foo@1.0::IBar
, esse ainda não é um uprev válido.
Interfaces Uprev
Para aumentar a receita android.hardware.example@1.0
(definido acima) para
@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); }
Este é um import
no nível de pacote da versão 1.0
do
android.hardware.example
em types.hal
. Embora não seja novidade
Os UDTs são adicionados na versão 1.1
do pacote, as referências a UDTs em
versão 1.0
ainda são necessários, por isso a importação no nível do pacote
em types.hal
. (O mesmo efeito poderia ter sido alcançado com uma
importação no nível da interface em IQuux.hal
.
Em extends @1.0::IQuux
, na declaração de
IQuux
, especificamos a versão de IQuux
que está sendo
herdada (a desambiguação é necessária porque IQuux
é usado para
declarar uma interface e herdar de uma interface). Como as declarações são
que herdam todos os atributos de pacote e versão no local do
a desambiguação precisa estar no nome da interface base.
também poderia ter usado o UDT totalmente qualificado, mas isso seria
redundantes.
A nova interface IQuux
não declara novamente o método
fromFooToBar()
herda de @1.0::IQuux
; ele simplesmente
lista o novo método que adiciona fromBarToFoo()
. Em HIDL, herdadas
métodos não podem ser declarados novamente nas interfaces filhas.
a interface IQuux
não pode declarar a fromFooToBar()
explicitamente.
Convenções de uprev
Às vezes, os nomes de interface precisam renomear a interface estendida. Recomendamos extensões de tipo enumerado, structs e uniões têm o mesmo nome que elas estendem a menos que sejam diferentes o suficiente para justificar um novo nome. Exemplos:
// 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 };
Se um método puder ter um novo nome semântico (por exemplo,
fooWithLocation
). Caso contrário, ele deve ser
com o mesmo nome da extensão. Por exemplo, o método
O foo_1_1
em @1.1::IFoo
pode substituir a funcionalidade
do método foo
em @1.0::IFoo
se não houver uma
nome alternativo.
Controle de versão no nível do pacote
O controle de versões do HIDL ocorre no nível do pacote. depois que um pacote é publicado, ele é imutável (o conjunto de interfaces e UDTs não pode ser alterado). Os pacotes podem se relacionam entre si de várias maneiras, todas expressas por uma combinação de herança no nível da interface e criação de UDTs por composição.
No entanto, um tipo de relacionamento é estritamente definido e precisa ser aplicado: Herança compatível com versões anteriores no nível do pacote. Nesse cenário, O pacote parent é o pacote que é herdado de e o child é o pacote que estende o pai. Nível do pacote regras de herança compatíveis com versões anteriores são as seguintes:
- Todas as interfaces de nível superior do pacote pai são herdadas de pelas interfaces na pacote filho.
- Novas interfaces também podem ser adicionadas ao novo pacote (sem restrições sobre relações com outras interfaces em outros pacotes).
- Novos tipos de dados também podem ser adicionados para uso por novos métodos de reformulação interfaces já existentes ou novas.
Essas regras podem ser implementadas usando a herança no nível da interface HIDL e o UDT composição, mas exigem conhecimento meta para conhecer essas relações constituam uma extensão de pacote compatível com versões anteriores. Esse conhecimento é inferido da seguinte forma:
Se um pacote atender a esse requisito, o hidl-gen
aplicará
regras de compatibilidade com versões anteriores.