Controle de versões da interface

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.

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.
  • 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 como android.hardware.bar@1.0::S e é encontrada em bar/1.0/types.hal (porque types.hal é automaticamente importados).
  • IFooCallback é interpolado como android.hardware.bar@1.0::IFooCallback usando a regra 2, mas não foi encontrado porque bar/1.0/IFooCallback.hal não foi importado automaticamente (como types.hal). Assim, a regra 3 resolve android.hardware.foo@1.0::IFooCallback, que é importado via import 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 as types.hal)
  • types.hal de android.hardware.baz@1.0::types As interfaces em android.hardware.baz@1.0 não são importadas.
  • IQux.hal e types.hal de android.hardware.qux@1.0
  • Quuz de android.hardware.quuz@1.0 (supondo que Quuz é definido em types.hal, todo o O arquivo types.hal é analisado, mas tipos diferentes de Quuz 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).
OU
Regra B

Todas as afirmações a seguir são verdadeiras:

  1. "A versão secundária anterior é válida": package@major.(minor-1) deve ser definida e seguir a mesma regra A (nenhuma package@major.0 a package@major.(minor-2) estiverem definidas) ou a regra B (se for um aumento de @major.(minor-2));
    (
    ) E
    (
    )
  2. "Herdar pelo menos uma interface com o mesmo nome": existe um interface package@major.minor::IFoo, que estende package@major.(minor-1)::IFoo (se o pacote anterior tiver uma interface);
    de
    E
    de
  3. "Nenhuma interface herdada com um nome diferente": ela não deve existir package@major.minor::IBar que se estende package@major.(minor-1)::IBaz, em que IBar e IBaz são dois nomes diferentes. Se houver uma interface com o mesmo nome, package@major.minor::IBar precisa estender package@major.(minor-k)::IBar para que nenhum IBar exista com um k menor.

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ório hardware/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:

  1. Todas as interfaces de nível superior do pacote pai são herdadas de pelas interfaces na pacote filho.
  2. Novas interfaces também podem ser adicionadas ao novo pacote (sem restrições sobre relações com outras interfaces em outros pacotes).
  3. 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.