Esta página fornece um guia sobre a arquitetura do sistema de preferências do usuário do SDV e instruções para implementar um serviço e um cliente controláveis pelo usuário.
Visão geral da arquitetura
O sistema de preferências do usuário separa o armazenamento e o gerenciamento das configurações do usuário da aplicação e da imposição dessas configurações. Os principais termos arquitetônicos estão resumidos na tabela:
| Recurso | Descrição | Papel | Responsabilidade |
|---|---|---|---|
| Serviço controlável pelo usuário | Um pacote de serviços SDV padrão que gerencia um domínio específico (por exemplo, HVAC, cadeirinhas de carro, áudio) | Fornece o estado pretendido das capacidades e restrições do hardware ou subsistema que ele controla. |
|
| Agente de preferências do usuário | O orquestrador central | Fornece armazenamento centralizado, gerenciamento de perfil de usuário e hub de notificações. |
|
| Cliente de preferências do usuário | Um app ou serviço que fornece uma interface do usuário, por exemplo, um app de IVI com uma HMI ou outra lógica que precisa interagir com as preferências do usuário | Interage com os usuários, mostra configurações e inicia solicitações de mudança. |
|
Implementar um serviço controlável pelo usuário
Para informar ao agente de preferências do usuário quais chaves existem, os tipos de dados, os valores padrão e as restrições (por exemplo, valores mínimos e máximos), seu pacote de serviços precisa interagir com a interface UserPreferencesRegistryService. Quando
o serviço é iniciado, ele precisa registrar as configurações que expõe. O agente chama
RequestSettingsChange no seu serviço quando um usuário tenta modificar uma configuração
(ou ao trocar de usuário).
Siga as etapas nesta seção para permitir que as preferências do usuário (e um usuário com HMI) controlem seu serviço.
Defina interfaces de serviço no arquivo VSIDL:
- Como um servidor, implemente
com.sdv.google.user_preferences.user_controllable.UserControllableService. Isso permite que o agente envie solicitações de mudança. - Como cliente, consuma
com.sdv.google.user_preferences.UserPreferencesRegistryService. Isso registra suas configurações na inicialização.
O exemplo a seguir mostra
service_bundle.vsidlpara um serviço controlável pelo usuário:service_bundle { name: "MyFeatureService" server { service: "com.sdv.google.user_preferences.user_controllable.UserControllableService" } client { service: "com.sdv.google.user_preferences.UserPreferencesRegistryService" } }- Como um servidor, implemente
Registre as configurações na inicialização no proto de chave
user_preferences_registry_service.proto:- Conecte-se a
UserPreferencesRegistryService. - Crie uma instância de
RegisterSettingsRequest. - Defina uma instância de
SettingsGroup(uma coleção lógica de configurações). Para cada configuração, defina:
* **Key:** Unique string ID (for example, `TEMPERATURE`) * **Kind:** `PER_USER` (stored per user profile) or `SHARED` (global) * **Default value:** Initial value if no user preference exists * **Constraints:** (optional) Validation rules (for example, Min 16, Max 32 for HVAC).Chame o método
RegisterSettings().
O exemplo a seguir está em Rust conceitual:
let temperature_setting = SettingDefinition { name: "TEMPERATURE".to_string(), kind: SettingKind::PER_USER.into(), default_value: Value::Int64(22), // Default 22 degrees constraint: Some(Constraints::Int64Constraints(Int64Constraints { min_value: Some(16), max_value: Some(32), ..Default::default() })), ..Default::default() }; registry_client.RegisterSettings(&RegisterSettingsRequest { group_name: "HVAC".to_string(), version: "1.0".to_string(), settings_definitions: vec![temperature_setting], }).await?;- Conecte-se a
Gerenciar solicitações de mudança de configurações em
user_controllable_service.proto:- Implemente o RPC
RequestSettingsChange. - Valide se os valores solicitados são válidos no contexto atual (por exemplo, se o hardware está pronto).
- Aplique uma lógica específica para fazer a mudança (por exemplo, mova o assento, mude a velocidade do ventilador).
- Retorne os valores aplicados em
RequestSettingsChangeResponse: - Se você aceitou a mudança, retorne o novo valor.
- Se você rejeitou ou fixou o valor, retorne o valor definido (ou mantido).
O exemplo a seguir está em Rust conceitual:
async fn RequestSettingsChange( &self, _caller_id: ServiceFqin, request: &RequestSettingsChangeRequest ) -> SdvResult<RequestSettingsChangeResponse> { let mut applied_settings = Vec::new(); for setting in &request.settings { if self.hardware.set_value(setting.key, setting.value).is_ok() { // Change accepted applied_settings.push(setting.clone()); } else { // Change rejected, return current actual value let current_val = self.hardware.get_value(setting.key); applied_settings.push(create_setting(setting.key, current_val)); } } Ok(RequestSettingsChangeResponse { settings: applied_settings, }) }- Implemente o RPC
Implemente a RPC
FactoryResetpara reverter o subsistema a um estado limpo:- Redefina todas as configurações para os valores padrão (conforme definido na configuração do código).
- Chame
UpdateSettingsno serviço de registro para informar ao agente que os valores foram alterados externamente (pela redefinição, não por uma solicitação do usuário).
Compatibilidade com versões anteriores e evolução de esquema
O esquema das configurações definidas por um serviço controlável pelo usuário é considerado uma interface pública. Essa interface é consumida pelo agente de preferências do usuário e vários clientes (por exemplo, HMIs), que podem ter diferentes cronogramas de lançamento. Portanto, manter uma compatibilidade com versões anteriores estrita é essencial para evitar instabilidade do sistema e falhas do cliente.
Mudanças compatíveis
Um serviço controlável pelo usuário só pode introduzir mudanças compatíveis no esquema de configurações. Essas mudanças garantem que os clientes mais antigos continuem operando corretamente sem atualizações:
Adicione uma nova configuração opcional com um valor padrão razoável:
- Essa configuração é opcional. Portanto, os clientes precisam verificar a presença dela para oferecer suporte a diferentes versões do serviço.
- Os clientes vão ignorar essa configuração se não tiverem sido atualizados para reconhecê-la.
- Se não houver uma preferência do usuário para a nova configuração, o agente usará o valor padrão fornecido.
Mudanças incompatíveis
As seguintes mudanças são consideradas significativas e proibidas porque comprometem imediatamente clientes mais antigos:
Remova uma configuração atual.
Mudar o significado, a unidade ou o tipo de dados de uma configuração atual (por exemplo, mudar de um número inteiro que representa a temperatura em Celsius para um número de ponto flutuante que representa a pressão em Pascal).
Mude o valor padrão de uma configuração existente. O principal motivo para proibir mudanças no valor padrão é a possível complicação com a migração de dados persistentes. O agente armazena configurações com base no esquema de registro do serviço. Mudar um valor padrão exigiria uma lógica complexa para migrar todos os perfis de usuário atuais para o novo padrão ou correria o risco de usar um valor tecnicamente incorreto para usuários que nunca definiram explicitamente a preferência.
Processar mudanças interruptivas necessárias
Se uma mudança interruptiva for necessária, o serviço não poderá modificar a configuração existente. A abordagem para gerenciar essa transição e manter a compatibilidade está principalmente na lógica de serviço controlável pelo usuário:
- Crie uma nova configuração com a definição, a chave ou a unidade desejada.
- Descontinue a configuração atual registrando-a para compatibilidade. Use essa nova configuração ao desenvolver novos clientes.
- Aconselhe os novos clientes a implementar a lógica de compatibilidade na lógica de negócios do serviço controlável pelo usuário.
A implementação de RequestSettingsChange do serviço precisa garantir que uma mudança
em uma configuração seja refletida na versão descontinuada e que uma mudança na
versão seja refletida na configuração.
Exemplo de lógica de compatibilidade:
Um serviço descontinua a configuração antiga
TEMPERATURE_C(Celsius) e introduzTEMPERATURE_K(Kelvin).Quando o agente envia uma solicitação para atualizar a configuração new:
O serviço recebe uma solicitação de
TEMPERATURE_K(por exemplo, 295,15 K).A lógica do serviço converte isso para Celsius (22 °C) e, internamente, atualiza e mantém os dois valores para manter a consistência dos clientes legados.
Quando o agente envia uma solicitação para a configuração descontinuada de um cliente legado:
- O serviço recebe uma solicitação de
TEMPERATURE_C(por exemplo, 24 °C). - O serviço converte isso em Kelvin (297,15 K) e, internamente, atualiza e mantém os dois valores.
- O serviço recebe uma solicitação de
Essa estratégia de gravação dupla garante que todos os clientes, independente da programação de lançamento, leiam dados consistentes e precisos, evitando falhas causadas por incompatibilidades de lançamento externo.
Implementar um cliente
Para implementar um cliente (por exemplo, uma HMI) que interage com o agente de preferências do usuário:
Defina seu pacote de serviços do cliente para interagir com interfaces específicas de preferências do usuário. No arquivo VSIDL:
- Como um servidor, implemente
com.sdv.google.user_preferences.view.ChangeNotifierpara permitir que o agente envie ao cliente notificações em tempo real sobre mudanças nas configurações. - Como cliente, use
com.sdv.google.user_preferences.UserPreferencesManagementServicepara solicitar modificações nas configurações e assinar atualizações. - Como cliente, use
com.sdv.google.user_preferences.UserPreferencesAdminServicepara gerenciar perfis de usuário (por exemplo, criar, selecionar, excluir, redefinir para a configuração de fábrica).
O exemplo a seguir mostra um arquivo
service_bundle.vsidlpara um cliente:service_bundle { name: "MyHmiClient" server { service: "com.sdv.google.user_preferences.view.ChangeNotifier" } client { service: "com.sdv.google.user_preferences.UserPreferencesManagementService" } client { service: "com.sdv.google.user_preferences.UserPreferencesAdminService" } }Adicione as políticas de autorização de acordo com a necessidade. Para permitir a comunicação do agente de preferências do usuário, crie um cliente para os servidores especificados. Exemplo:
server { service: "com.sdv.google.user_preferences.view.ChangeNotifier" allow_all_channels: true } client { service: "com.sdv.google.user_preferences.UserPreferencesManagementService" allow_all_channels: true } client { service: "com.sdv.google.user_preferences.UserPreferencesAdminService" allow_all_channels: true }- Como um servidor, implemente
É possível mudar as configurações de solicitação em
UserPreferencesManagementService:- Conecte-se a
UserPreferencesManagementService. - Crie uma instância de
RequestSettingsChangeRequest, especificando oSettingsGroupIde as configurações desejadas. - Opcional. Defina
ChangePersistencePolicycomoPERSISTENT_CHANGE(padrão) ouNON_PERSISTENT_CHANGE. - Chame o método
RequestSettingsChange().
O exemplo a seguir está em Rust conceitual:
management_client.RequestSettingsChange(&RequestSettingsChangeRequest { settings_group_id: Some(SettingsGroupId { service_fqin: hvac_service_fqin.to_string(), name: "HVAC".to_string(), ..Default::default() }).into(), settings: vec![Setting { key: "TEMPERATURE".to_string(), value: Some(Value::Int64(24)), ..Default::default() }], change_persistence_policy: ChangePersistencePolicy::PERSISTENT_CHANGE.into(), ..Default::default() }).await?;- Conecte-se a
Inscreva-se para receber notificações sobre mudanças nas configurações com
user_preferences_management_service.protoechange_notifier.proto:- Implemente a RPC
OnSettingsChangeda interfaceChangeNotifierno pacote de serviços. O agente de preferências do usuário invoca esse método quando uma mudança ocorre. - Conecte-se a
UserPreferencesManagementService. - Crie um
SubscribeToSettingsChangeAndGetSettingsRequestespecificando oSettingsGroupIdque você quer monitorar. - Para retornar o estado das configurações e enviar atualizações futuras pela
implementação de
OnSettingsChange, chameSubscribeToSettingsChangeAndGetSettings().
O exemplo a seguir de implementação da interface
ChangeNotifierestá em Rust conceitual:#[async_trait] impl ChangeNotifier for MyHmiServiceImpl { async fn OnSettingsChange( &self, _caller_id: ServiceFqin, request: &OnSettingsChangeRequest, ) -> SdvResult<OnSettingsChangeResponse> { // Process the active_settings, pending_changes, and persisted_settings // Update your UI or internal state accordingly. info!("Received settings change for group: {}", request.settings_group_id.name); // ... Ok(OnSettingsChangeResponse::new()) } }O exemplo a seguir de inscrição está em Rust conceitual:
management_client.SubscribeToSettingsChangeAndGetSettings(&SubscribeToSettingsChangeAndGetSettingsRequest { settings_group_id: Some(SettingsGroupId { service_fqin: hvac_service_fqin.to_string(), name: "HVAC".to_string(), ..Default::default() }).into(), ..Default::default() }).await?;- Implemente a RPC
Opcional: clientes que gerenciam perfis de usuário (por exemplo, um app de configurações dedicado) podem usar
user_preferences_admin_service.protopara interagir com o serviço de administrador:- Conecte-se a
UserPreferencesAdminService. - Use RPCs como
CreateUser,SelectUser,DeleteUser,FactoryReseteListUserspara gerenciar perfis de usuário.
O exemplo a seguir de criação de um usuário está em Rust conceitual:
admin_service_client.CreateUser(&CreateUserRequest { user: Some(User { id: 1, flags: UserFlags::DRIVER.value(), ..Default::default() }).into(), ..Default::default() }).await?;- Conecte-se a
Descubra as configurações e os usuários disponíveis pelo serviço de administrador com
user_preferences_admin_service.proto:- Para receber uma lista de todos os usuários registrados, chame
ListUsers()noUserPreferencesAdminService. - Para receber as configurações de um usuário específico, chame
GetUserSettings(user_id)noUserPreferencesAdminService.
O exemplo a seguir de listagem de usuários e obtenção de configurações está em Rust conceitual:
// List all users let list_users_response = admin_service_client.ListUsers(&ListUsersRequest::new()).await?; info!("Available users: {:?}", list_users_response.users); // Get settings for a specific user (for example, user with ID 1) if let Some(user_id) = list_users_response.users.first().map(|u| u.id) { let get_settings_response = admin_service_client.GetUserSettings(&GetUserSettingsRequest { user_id, ..Default::default() }).await?; info!("Settings for user {}: {:?}", user_id, get_settings_response.groups); }- Para receber uma lista de todos os usuários registrados, chame
Resumo do fluxo
- O cliente inicia e se conecta aos serviços de gerenciamento e administração do agente.
- O cliente descobre e se inscreve em grupos de configurações e mostra o estado inicial.
- O cliente envia um
RequestSettingsChangeao agente. - O agente envia uma notificação
OnSettingsChangeao cliente com configurações e estado atualizados.
Para um exemplo completo, consulte HMIService em @samples/user_preferences/v1/.
Implementar o agente
Um pacote de serviços iniciado pelo orquestrador implementa o agente de preferências do usuário. Para sua conveniência, uma implementação de referência é fornecida.
Além disso, este pacote de serviços de amostra fornece o arquivo user_preferences_sample.vsidl
para o agente:
package: "com.sdv.oem.user_preferences"
service_bundle {
name: "UserPreferencesServiceBundle"
server {
service: "com.sdv.google.user_preferences.UserPreferencesAdminService"
}
server {
service: "com.sdv.google.user_preferences.UserPreferencesManagementService"
}
server {
service: "com.sdv.google.user_preferences.UserPreferencesRegistryService"
}
client {
service: "com.sdv.google.user_preferences.view.ChangeNotifier"
}
client {
service: "com.sdv.google.user_preferences.user_controllable.UserControllableService"
}
}
A implementação pode usar o middleware gerado e
precisa ser compilada em um rust_ffi_shared referenciado pelo
pacote de serviços que implementa o agente de preferências do usuário:
sdv_service_bundle_metadata {
# This name must match the agent's Bundle Name
name: "UserPreferencesServiceBundle"
version_number: 1
version_name: "1"
native_library_path: "lib64/<USER-PREFERENCES-FFI-LIB>.so"
orchestration_config_path: "etc/user_preferences_service_bundle/<USER-PREFERENCES-ORCHESTRATION>.textproto"
authorization_policy_path: "etc/user_preferences_service_bundle/permissions.textproto"
}
O arquivo política de autorização .textproto do cliente exige as permissões necessárias:
client {
service: "com.sdv.google.user_preferences.UserPreferencesManagementService"
allow_all_channels: true
}
client {
service: "com.sdv.google.user_preferences.UserPreferencesRegistryService"
allow_all_channels: true
}
client {
service: "com.sdv.google.user_preferences.UserPreferencesAdminService"
allow_all_channels: true
}
Apêndice: principais conceitos
Esta seção descreve alguns conceitos importantes.
Usuários
Cada usuário do veículo (classe User) pode criar uma conta para armazenar as preferências. As preferências do usuário são compatíveis apenas com contas de motoristas de veículos.
Os motoristas convidados podem criar contas temporárias que são excluídas automaticamente após
um uso.
message User {
// Required.
// A unique ID for the user.
int32 id = 1;
// Bit flags that define properties of user. Integer values have to be powers of 2 as they are used as
// a bit mask.
enum UserFlags {
// Due to Protobuff requirement to have the first enum option set to 0,
// Assign 0 to an unused flag
UNSET = 0x0;
// Marks the user as vehicle driver
DRIVER = 0x01;
// Ephemeral users have non-persistent state, once another user is selected
// the profile is deleted automatically
EPHEMERAL = 0x02;
}
// Required.
// Bitmask for the user flags defined above
int32 flags = 2;
}
Grupos de configurações
Os grupos de configurações (classe SettingsGroup) organizam e gerenciam configurações relacionadas.
Cada componente configurável do veículo define as configurações como grupos, que consistem em uma lista de configurações representadas como pares de chave-valor. Por exemplo, veículos com bancos elétricos podem definir um grupo de configurações para o banco do motorista e outro para o passageiro da frente, em que ambos os grupos contêm configurações com nomes idênticos.
message SettingsGroup {
// Required.
// The identifier for this group.
SettingsGroupId id = 1;
// Required.
// The version number of schema used by this setting group.
string version = 2;
// Required.
// The list of settings within the setting group.
repeated Setting settings = 3;
}
Configurações
A configuração, a mensagem (classe Setting) representa uma única configuração em
SettingsGroup. Essa mensagem consiste em uma chave, que é o nome da
configuração, e um valor, que pode ser de vários tipos.
Este exemplo mostra como uma configuração é representada com uma chave e um valor:
// A key value pair representing a setting within a UserControllableService SettingsGroup.
message Setting {
// Required.
// A name that uniquely identifies a setting within a SettingsGroup.
string key = 1;
// Required.
// New value of the setting.
oneof value {
bool bool = 2;
float float = 3;
int32 int32 = 4;
int64 int64 = 5;
bytes blob = 6;
int32 enum = 7;
}
}
Definições de configuração
As definições de configuração (classe SettingDefinition) servem como modelos para configurações individuais em um grupo de configurações. Elas especificam as características fundamentais da configuração, como se ela é compartilhada entre todos os usuários, específica para cada usuário ou gerenciada externamente (transparência).
É importante lembrar que as definições também definem restrições no valor da configuração, como valores mínimos e máximos, incrementos permitidos ou um conjunto restrito de opções. Essas restrições levam à integridade de dados e consistência para cada configuração.
// The definition of a setting, along with its properties such as type and constraints
message SettingDefinition {
// Required.
SettingKind kind = 1;
// Required.
SettingWithConstraints setting_with_constraints = 2;
}
enum SettingKind {
// A setting which is applied for all vehicle users.
SHARED = 0;
// Store the value of the setting for each vehicle user separately.
PER_USER = 1;
// UserControllableService is fully responsible for the storage of PASSTHROUGH setting.
// User Preferences only notifies UserControllableService when the value is explicitly set by
// user. User Preferences does not attempt to request changes based on user change or service
// registration.
PASSTHROUGH = 2;
};
message SettingWithConstraints {
// Required
Setting setting = 1;
// Required.
// Defines restrictions on the setting's value
oneof constraints {
FloatConstraints float_constraints = 2;
Int32Constraints int32_constraints = 3;
Int64Constraints int64_constraints = 4;
EnumConstraints enum_constraints = 5;
}
}
message Int32Constraints {
// The minimum value that a setting can have.
optional int32 min_value = 1;
// The maximum value that a setting can have.
optional int32 max_value = 2;
// The step by which a setting's value can be increased or decreased.
optional int32 step = 3;
}
message Int64Constraints {
// The minimum value that a setting can have.
optional int64 min_value = 1;
// The maximum value that a setting can have.
optional int64 max_value = 2;
// The step by which a setting's value can be increased or decreased.
optional int64 step = 3;
}
message FloatConstraints {
// The minimum value that a setting can have.
optional float min_value = 1;
// The maximum value that a setting can have.
optional float max_value = 2;
// The step by which a setting's value can be increased or decreased.
optional float step = 3;
}
message EnumConstraints {
// Required.
// List of unique values sorted in ascending order.
repeated int32 possible_values = 1;
}
Exemplo de sequência de eventos quando um usuário atualiza uma configuração
Este exemplo ilustra a sequência de eventos que ocorrem quando um usuário usa o sistema de infoentretenimento no veículo (IVI, na sigla em inglês) do Android Automotive OS (AAOS) para atualizar uma configuração:
Figura 1. Eventos quando um usuário muda uma configuração.
Exemplo de sequência de eventos quando um veículo está sendo ligado
Este exemplo ilustra como as preferências do usuário do SDV aplicam as configurações preferidas de um usuário quando um veículo é ligado:
Figura 2. Eventos quando um veículo está sendo ligado.