在 Android 上初始化住宅

在使用任何适用于 Android 的 Home API 之前,您必须在应用中初始化 Home。在此步骤中,您将为本地上下文创建 Home单例实例。

一次只能有一个 Home 实例处于活动状态。

这是 Home API 的入口点,还涉及声明您打算将哪些特征和设备类型与设备和结构 API 以及自动化 API 搭配使用。如果您刚开始使用 Google Home 生态系统,并且不确定要注册哪些特征或设备类型,我们已在本指南中建议了一些最常见的特征或设备类型。

创建住宅实例

首先,将以下软件包导入到您的应用中:

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

如需初始化 Home API,请执行以下操作:

  1. 获取对 Application 上下文的引用。此上下文不依赖于任何 activity 生命周期,只要应用处于有效状态,此上下文就会一直有效。您可以在 ActivityService 中调用 getApplicationContext() 来获取它:

    val context = getApplicationContext()
    
  2. 创建一个 FactoryRegistry 实例,其中包含您打算在应用中使用的所有特征和设备类型。

    在本指南中,我们推荐了一些常见的设备类型(灯、插座、传感器、开关和温控器设备类型,用于自动化的 presence 和 Assistant 特征),以防您不确定自己需要什么。如需了解详情,请参阅注册特征和设备类型

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

    必须添加此处注册的每个单独特征和设备类型的 import 语句(Android Studio 应该会提示您添加这些语句)。

  3. 使用 Dispatchers.IO 协程上下文和您的注册表实例实例化 HomeConfig

    val homeConfig = HomeConfig(
            coroutineContext = Dispatchers.IO,
            factoryRegistry = registry)
    
  4. 最后,使用上下文和 HomeConfig 创建 Homesingleton 实例,这是 API 的入口点。

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

为避免因会话无效而导致错误,请务必通过将其封装在 object 声明中,确保仅创建 Home单例实例

例如,示例应用就是这样做的:

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

应用发起的 Google 登录

您可能需要在应用内管理用户的 Google 身份验证。这样一来,您就可以在各种 Google 服务(例如 Google Home、云端硬盘、地图等)中使用同一用户账号。

借助应用发起的 Google 登录,您可以获取与特定用户明确关联的 HomeClient 实例,从而在账号已获得授权的情况下绕过 Google 账号选择器和同意屏幕。

此外,这种方法还可以防止用户看到两个不同的账号选择界面:一个来自应用的登录界面,另一个来自 Google Home。

为此,您必须参阅使用“通过 Google 账号登录”功能对用户进行身份验证,并完成以下步骤:

创建 OAuth Web 应用客户端 ID

  1. 打开 Google Cloud 控制台
    • 前往 Google Cloud 控制台的“凭据”页面。
    • 选择现有项目或创建新项目。
  2. 配置 OAuth 权限请求页面(如果尚未配置)
    • 在创建凭据之前,请确保 OAuth 权限请求页面已配置应用的详细信息,包括隐私权政策和服务条款网址。
  3. 创建 OAuth 客户端 ID(Web 应用类型)
    • 在“凭据”页面上,点击 + CREATE CREDENTIALS,然后从下拉菜单中选择 OAuth 客户端 ID
    • 对于应用类型,选择 Web 应用
    • 为您的 Web 客户端输入一个名称(例如,“My App Web Backend”)。
    • 点击“创建”。
  4. 检索客户端 ID
    • 创建完成后,控制台将显示新的客户端 ID。这是您将在 Android 应用中使用的值(例如“{project number}-.....apps.googleusercontent.com”)
    • 建议您将客户端 ID 存储在外部(例如,在 build.gradle 中),而不是直接对其进行硬编码

实例化 Google 登录请求

使用 Web 应用 ID 创建 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()

创建“使用 Google 账号登录”流程

如需实现登录流程,请使用 CredentialManager 执行 Sign in with Google 请求。用户选择账号后,从生成的 Google ID 令牌中提取其电子邮件地址,以创建 android.accounts.Account。然后,系统会使用此账号初始化专门与相应登录用户相关联的 HomeClient 实例。

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

获取新的 HomeClient 实例

按照创建 Home 实例中所述的相同步骤操作,但不要在第 4 步中调用 Home.getClient(context, homeConfig),而是调用 Home.getClient(context, userAccount, homeConfig),其中第二个形参是 Lazy<UserAccount>。这会返回一个 HomeClient 的子类 HomeClientWithProvidedAccount 的实例,该实例明确与指定的 Google 账号相关联:

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()
     )

如果指定用户未获得授权,请通过对 HomeClientWithProvidedAccount 实例调用以下方法来提示用户授予权限:

  1. registerActivityResultCallerForPermissions(),并引用您要使用的 ActivityResultCaller
  2. requestPermissions()。这会显示 GHP 许可屏幕,用户可以在其中授予许可。

您可以创建一个包含 UserAccountHomeClient,然后使用 forceLaunch==true 调用 requestPermissions() 以再次启动许可界面,从而允许用户更新其权限授予:

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

client.registerActivityResultCallerForPermissions(this)
client.requestPermissions(forceLaunch= true)

如需详细了解如何管理 Home API 权限,请参阅 Permissions API

使用新的 HomeClient 刷新整个 activity

获得新的 HomeClient 实例后,您必须刷新整个 activity,才能重新订阅并提取与相应用户账号关联的完整结构、设备和其他相关数据。

注册特征和设备类型

FactoryRegistry 类可让开发者明确指明其应用使用的特征和设备类型,从而帮助开发者优化应用二进制文件大小。

请注意,权限和出厂注册表是分离的。因此,使用权限可供应用使用但未包含在出厂注册表中的未注册特征和类型无法通过 Automation API 访问,也不会在批量 traits()types() 方法调用中返回。