Inicializar a casa no Android

Antes de usar qualquer uma das APIs Home para Android, é necessário inicializar a casa no seu app. Nesta etapa, você vai criar uma instância singleton de Home para o contexto local.

Apenas uma instância de Home deve estar ativa por vez.

Esse é o ponto de entrada para as APIs Home e também envolve declarar quais características e tipos de dispositivo você pretende usar com as APIs Device & Structure e Automation. Se você está começando a usar o ecossistema do Google Home e não sabe quais atributos ou tipos de dispositivo registrar, sugerimos alguns dos mais comuns neste guia.

Criar uma instância do Home

Para começar, importe estes pacotes para o app:

import android.content.Context
import com.google.home.FactoryRegistry
import com.google.home.HomeConfig
import com.google.home.Home

Para inicializar as APIs Home:

  1. Acesse uma referência ao contexto Application. Esse contexto não depende de nenhum ciclo de vida da atividade e vai durar enquanto o app estiver ativo. Para isso, chame getApplicationContext() em uma Activity ou Service:

    val context = getApplicationContext()
    
  2. Crie uma instância FactoryRegistry com todos os atributos e tipos de dispositivo que você pretende usar no app.

    Para este guia, sugerimos alguns tipos comuns (tipos de dispositivo de luz, tomada, sensor, interruptor e termostato, traços de presença e do Assistente para automações), caso você não tenha certeza do que precisa. Para saber mais, consulte Registro de atributos e tipos de dispositivo.

    val registry = FactoryRegistry(
      traits = listOf(
                AirQuality,
                AreaAttendanceState,
                AreaPresenceState,
                AssistantBroadcast,
                AssistantFulfillment,
                BooleanState,
                ColorControl,
                ExtendedColorControl,
                FlowMeasurement,
                IlluminanceMeasurement,
                LevelControl,
                Notification,
                OccupancySensing,
                OnOff,
                RelativeHumidityMeasurement,
                Switch,
                TemperatureMeasurement,
                Thermostat),
      types = listOf(
                AirQualitySensorDevice,
                ColorDimmerSwitchDevice,
                ColorTemperatureLightDevice,
                ContactSensorDevice,
                DimmableLightDevice,
                DimmablePlugInUnitDevice,
                DimmerSwitchDevice,
                ExtendedColorLightDevice,
                FlowSensorDevice,
                GenericSwitchDevice,
                HumiditySensorDevice,
                LightSensorDevice,
                OccupancySensorDevice,
                OnOffLightDevice,
                OnOffLightSwitchDevice,
                OnOffPluginUnitDevice,
                OnOffSensorDevice,
                SpeakerDevice,
                TemperatureSensorDevice,
                ThermostatDevice))
    

    As instruções de importação para cada trait e tipo de dispositivo registrado aqui são obrigatórias (o Android Studio vai solicitar que você as adicione).

  3. Instancie um HomeConfig usando o contexto de corrotina Dispatchers.IO e sua instância de registro.

    val homeConfig = HomeConfig(
            coroutineContext = Dispatchers.IO,
            factoryRegistry = registry)
    
  4. Por fim, crie a instância singleton de Home, que é o ponto de entrada para as APIs, usando o contexto e o HomeConfig.

    val homeManager: HomeClient = Home.getClient(context, homeConfig)
    

Para evitar erros com sessões inválidas, é importante que apenas uma instância singleton de Home seja criada, envolvendo-a em uma declaração de objeto.

Por exemplo, o app de exemplo faz isso da seguinte maneira:

internal object HomeClientModule {
  @Provides
  @Singleton
  fun provideHomeClient(@ApplicationContext context: Context): HomeClient {
    return Home.getClient(
      context,
      HomeConfig(
        coroutineContext = IODispatcherModule.provideIoDispatcher(),
        factoryRegistry = registry,
      ),
    )
  }
}

Se você quiser que os fluxos de interface hospedados pelo Google, como a tela de permissão, sejam renderizados em um idioma específico, chame setLocale() na instância HomeClient antes de solicitar permissões. Para mais detalhes, consulte Configurar a localidade do app.

Login do Google iniciado pelo app

Talvez você queira gerenciar as autenticações do Google do usuário no seu app. Isso permite usar a mesma conta de usuário em vários Serviços do Google, como Google Home, Drive, Maps etc.

Com o Login do Google iniciado pelo app, é possível receber uma instância HomeClient explicitamente vinculada a um usuário específico, ignorando o seletor de Contas do Google e a tela de permissão quando a conta já está autorizada.

Além disso, essa abordagem impede que os usuários vejam duas telas diferentes de seleção de conta: uma do login do app e outra do Google Home.

Para fazer isso, consulte Autenticar usuários com o recurso Fazer login com o Google e siga estas etapas:

Criar um ID do cliente de aplicativo da Web OAuth

  1. Abra o console do Google Cloud.
    • Acesse a página "Credenciais" do console do Google Cloud.
    • Selecione um projeto ou crie um novo.
  2. Configurar a tela de permissão OAuth (se ainda não tiver feito isso)
    • Antes de criar credenciais, verifique se a tela de permissão OAuth está configurada com os detalhes do app, incluindo os URLs da Política de Privacidade e dos Termos de Serviço.
  3. Crie um ID do cliente OAuth (tipo de aplicativo da Web)
    • Na página "Credenciais", clique em + CREATE CREDENTIALS e selecione ID do cliente OAuth no menu suspenso.
    • Em Tipo de aplicativo, selecione Aplicativo da Web.
    • Insira um nome para o cliente da Web (por exemplo, "My App Web Backend").
    • Clique em "Criar".
  4. Recuperar o ID do cliente
    • Após a criação, o console vai mostrar o novo ID do cliente. Esse é o valor que você vai usar no seu app Android (por exemplo, "{project number}-.....apps.googleusercontent.com").
    • Recomendamos que você armazene o ID do cliente externamente (por exemplo, em build.gradle) em vez de codificá-lo diretamente.

Instanciar uma solicitação de Login do Google

Use o ID do app da Web para criar uma solicitação de Login do Google:

// Your Google Cloud console Web Client ID for Google Sign-In
val serverClientId = BuildConfig.DEFAULT_WEB_CLIENT_ID

// Build the request for Google ID token
val googleIdOption = GetGoogleIdOption.Builder()
    .setFilterByAuthorizedAccounts(false) // Show all Google Accounts on the device
    .setServerClientId(serverClientId) // embed WebClientID in token
    .build()

// Build the GetCredentialRequest
val request = GetCredentialRequest.Builder().addCredentialOption(googleIdOption).build()

Criar o fluxo do recurso Fazer login com o Google

Para implementar o fluxo de login, use CredentialManager para executar uma solicitação Sign in with Google. Depois que o usuário selecionar uma conta, extraia o e-mail dele do token de ID do Google resultante para criar um android.accounts.Account. Essa conta é usada para inicializar uma instância de HomeClient especificamente vinculada a esse usuário conectado.

  try {
    // CredentialManager is responsible for interacting with various credential providers on the device
    val credentialManager = CredentialManager.create(context)
    // Credential returns when user has selected an account and the getCredential call completes
    val result = credentialManager.getCredential(context = context, request = request)
    val credential = result.credential

    if (
      credential is CustomCredential &&
      credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
    ) {
      try {
        val googleCredential = GoogleIdTokenCredential.createFrom(credential.data)
        googleCredential.id.let { userEmail ->
          Log.i(TAG, "Email found in Google ID Token: $email")
          /*
           Why "com.google"?
           The string "com.google" is a standard identifier used in Android's android.accounts.
           Account system to represent accounts managed by Google. This is often used when
           interacting with Android's Account Manager or when using Google-specific APIs. So,
           even if the email ends in "@gmail.com", the underlying account type or provider is
           still considered "com.google" within the Android system.
          */
          val account = Account(userEmail, "com.google")
          Log.d(TAG,"Switched account to : $userEmail")
          // Get the new Home Client Instance with the userEmail
        }
        Log.i(TAG, "Account switch complete. Emitting navigation event.")
      } catch (e: Exception) {
        Log.e(TAG,"Could not convert CustomCredential to Google ID Token", e)
      }
    }
  } catch (e: Exception) {
    Log.e(TAG, "Google Sign-In failed with unexpected error", e)
  }

Acessar uma nova instância do HomeClient

Siga as mesmas etapas descritas em Criar uma instância de Home, mas, em vez de chamar Home.getClient(context, homeConfig) na Etapa 4, chame Home.getClient(context, userAccount, homeConfig), em que o segundo parâmetro é um Lazy<UserAccount>. Isso retorna uma instância de HomeClientWithProvidedAccount, uma subclasse de HomeClient, que está explicitamente vinculada à Conta do Google especificada:

val client =
     Home.getClient(
       context = context.applicationContext,
       account =
         lazy {
         // 1. Create the Account object.
           val androidAccount = Account(userEmail,
                                        GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE)
         // 2. Wrap it in UserAccount.GoogleAccount.
           UserAccount.GoogleAccount(androidAccount)
         },
       homeConfig = HomeConfig()
     )

Se o usuário especificado não estiver autorizado, solicite a permissão dele chamando os seguintes métodos na instância HomeClientWithProvidedAccount:

  1. registerActivityResultCallerForPermissions() com uma referência ao ActivityResultCaller que você quer usar.
  2. requestPermissions(). Isso abre a tela de permissão da GHP, em que o usuário pode conceder permissão.

Você pode criar um HomeClient com um UserAccount e chamar requestPermissions() com forcePermissionFlow definido como ForcePermissionFlow.FORCE_LAUNCH para iniciar a tela de permissão novamente e permitir que o usuário atualize as permissões:

val client =
     Home.getClient(
       context = context.applicationContext,
       account =
         lazy {
              UserAccount.GoogleAccount(androidAccount)
         },
       homeConfig = HomeConfig()
     )

client.registerActivityResultCallerForPermissions(this)
client.requestPermissions(forcePermissionFlow = ForcePermissionFlow.FORCE_LAUNCH)

Para mostrar a tela de permissão em um idioma específico, chame client.setLocale() antes de chamar requestPermissions(). Consulte Configurar a localidade do app para mais detalhes e API Permissions para mais informações sobre como gerenciar as permissões das APIs do Google Home.

Atualize toda a atividade com o novo HomeClient

Depois de ter uma nova instância de HomeClient, atualize toda a atividade para renovar a assinatura e buscar as estruturas, os dispositivos e outros dados pertinentes associados a essa conta de usuário.

Registro de atributos e tipos de dispositivo

A classe FactoryRegistry ajuda os desenvolvedores a otimizar o tamanho binário do app, permitindo que eles indiquem explicitamente quais características e tipos de dispositivo são usados pelo app.

As permissões e o registro de fábrica são dissociados. Portanto, as características e os tipos não registrados que estão disponíveis para o app usando permissões, mas não estão incluídos no registro de fábrica, não podem ser acessados usando a API Automation nem são retornados nas chamadas de método em massa traits() ou types().

Os grupos de dispositivos são representados pela entidade DeviceGroup, que é diferente de HomeDevice. Para consultar e interagir com grupos de dispositivos, inclua DeviceGroup na lista entityTypes ao inicializar o FactoryRegistry, junto com as características Group, GroupManagement e GroupMembership.

Para mais informações, consulte Grupos de dispositivos.

Configurar a localidade do app

Use setLocale() e getLocale() na instância HomeClient para configurar e recuperar a preferência de idioma do app. A localidade precisa ser uma tag de idioma BCP-47 válida, como "en-US", "en-GB" ou "ja".

Quando você define uma localidade em HomeClient, as APIs do Google Home usam esse idioma para fluxos de interface hospedados pelo Google, incluindo a interface de permissões, o comissionamento do Matter e os fluxos de remoção de dispositivos. Se você não definir uma localidade, esses fluxos vão usar como padrão a localidade do app ou do dispositivo.

  • Chamar setLocale() antes que o usuário faça login e conceda permissões armazena a localidade na memória e a aplica ao fluxo inicial da interface de permissões. Depois que o usuário faz login e uma estrutura é disponibilizada, o SDK sincroniza a localidade com a nuvem. Se o usuário já tiver uma localidade salva na nuvem para seu app, a configuração da nuvem terá precedência na sincronização inicial.
  • Chamar setLocale() enquanto o usuário está conectado atualiza a localidade na memória e a mantém na nuvem para esse usuário e app.
// Set the locale using a BCP-47 language tag
coroutineScope.launch {
  homeManager.setLocale("ja")
}

// Retrieve the configured locale, or null if not set
val currentLocale: String? = homeManager.getLocale()

Sincronizar com a seleção de idioma por app do Android

Se o app for compatível com a seleção de idioma por app do Android usando LocaleManager, chame homeManager.setLocale() antes de atualizar LocaleManager.applicationLocales. A atualização de applicationLocales recria o Activity ativo, o que pode cancelar uma corrotina em andamento se você chamar setLocale() depois.

coroutineScope.launch {
  val languageTag = "fr-CA"

  // 1. Persist the locale to HomeClient first
  homeManager.setLocale(languageTag)

  // 2. Update Android's per-app locale (recreates the Activity)
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    context.getSystemService(LocaleManager::class.java).applicationLocales =
      LocaleList.forLanguageTags(languageTag)
  }
}