在 Android 上初始化住宅

在使用任何 Home API for Android 之前,您必须在 应用中初始化 Home。在此步骤中,您将为 Local Context 创建 Home 的单例实例。

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

这是 Home API 的入口点,还涉及声明您打算在 Device & Structure API 和 Automation API 中使用哪些特征和设备类型。如果您刚开始接触 Google Home 生态系统,不确定要注册哪些特征或设备类型,我们在本指南中建议了一些最常见的特征或设备类型。

创建 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 生命周期,只要您的应用处于活动状态,它就会一直存在。您可以在 Activity 或 Service 中调用 getApplicationContext() 来获取它:

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

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

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

    需要为在此处注册的每个特征和设备类型添加导入语句(Android Studio 应该会提示您添加这些语句)。

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

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

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

为避免因会话无效而出现错误,请务必仅创建 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 客户端输入一个名称(例如“我的应用 Web 后端”)。
    • 点击“创建”。
  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>。这将返回 HomeClientWithProvidedAccount(HomeClient 的子类)的一个实例,该实例明确与指定的 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 权限请求页面,用户可以在其中授予权限。

您可以创建一个带有 UserAccount 的 HomeClient,然后调用 requestPermissions()(将 forcePermissionFlow 设置为 ForcePermissionFlow.FORCE_LAUNCH),以再次启动权限请求页面,让用户更新其权限授予:

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

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

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

使用新的 HomeClient 刷新整个 activity

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

注册特征和设备类型

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

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