Google 在 WatchFace、WatchConfig 和配套应用程序上修复授权错误(5005、5000 和 5015 错误)

Google Fit authorization bugs (with 5005, 5000, and 5015 errors) on WatchFace, WatchConfig, and companion app

我正在寻找关于可怕的 5005 错误的一些新想法:"Status code denotes that an unknown error occurred while trying to obtain an OAuth token." 当我的 Watch Face 试图连接一个由各种 Fitness API 构建的 Google API秒。这一切都适用于我的本地测试以及当我下载 运行 我的 RELEASE(测试版)版本时。但是,当我的第一个测试人员尝试它时,他在尝试连接 API.

时得到了错误代码

更新 2:我现在已经找到了正在发生的事情,所以我将在此处列出我的最终工作代码,然后在答案中讨论我发现的内容。

我在 3 种不同的场合连接到 Google API:Watch Face(裁判计时器:https://play.google.com/store/apps/details?id=com.pipperpublishing.soccerrefpro)、关联的 Watch Config 和配套应用在手机上 (phone)。

表盘:

    private GoogleApiClient buildGoogleClient() {
        final GoogleApiClient googleApiClient;
        final GoogleApiClient.Builder googleApiClientBuilder = new GoogleApiClient.Builder(RefWatchFaceService.this);

        //Common components of the GoogleClient
        googleApiClientBuilder
                .addApi(Wearable.API)
                .addApi(Fitness.SESSIONS_API)    //set Session for each period)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .useDefaultAccount();

        if (RefWatchUtil.isRefWatchPro()) {
            googleApiClientBuilder
                    .addApi(Fitness.RECORDING_API)  //records low power information
                    .addApiIfAvailable(Fitness.HISTORY_API
                        ,new Scope(Scopes.FITNESS_ACTIVITY_READ)
                        ,new Scope(Scopes.FITNESS_LOCATION_READ)
                    )
                    .addApiIfAvailable(Fitness.SENSORS_API
                        ,new Scope(Scopes.FITNESS_ACTIVITY_READ)
                        ,new Scope(Scopes.FITNESS_LOCATION_READ)
                    )
                    ;
        }
        googleApiClient = googleApiClientBuilder.build();
        return googleApiClient;
    }

请注意,我没有请求 HISTORY_API 的 WRITE 权限。当我稍后尝试在 Google Fit 商店中插入 SPEED 和 LOCATION 健身数据时,我使用此代码:

private void insertFitnessDataSetBatch(final DataSet batchDataSet) {
    final long batchStartTimeMillis = batchDataSet.getDataPoints().get(0).getTimestamp(TimeUnit.MILLISECONDS);
    final long batchEndTimeMillis;
    long tempEndTimeMillis = batchDataSet.getDataPoints().get(batchDataSet.getDataPoints().size()-1).getTimestamp(TimeUnit.MILLISECONDS);
    //It's possible that this is called with one data point, in which case the Fitness insert will choke on same start and end time
    if (tempEndTimeMillis > batchStartTimeMillis) {
        batchEndTimeMillis = tempEndTimeMillis;
    } else {
        batchEndTimeMillis = batchStartTimeMillis + 1;
    }


    try {
        Fitness.HistoryApi.insertData(mGoogleApiClient, batchDataSet)
                .setResultCallback(new ResultCallback<Status>() {
                    @Override
                    public void onResult(@NonNull Status status) {
                        //Sometimes there is an error but the data was inserted anyway
                        readInsertedFitnessData(batchStartTimeMillis, batchEndTimeMillis, batchDataSet.getDataType());
                        if (!status.isSuccess()) {
                            Log.d(TAG, String.format("Inserting data type %s returned status Code %d (%s)",
                                    batchDataSet.getDataType().getName(), status.getStatusCode(), status.getStatusMessage()));
                        }
                    }
                });
    } catch (RuntimeException e) {
        Log.e(TAG, String.format("There was a runtime exception inserting the data set batch for type %s: %s",
                batchDataSet.getDataType().getName(), e.getLocalizedMessage()));
    }

}

记住这个回读代码,因为我会在我的回答中引用结果。

观看配置 这里的区别在于 Watch Config 扩展了 FragmentActivity:

private GoogleApiClient buildGoogleClient() {
    final GoogleApiClient googleApiClient;
    final GoogleApiClient.Builder googleApiClientBuilder = new GoogleApiClient.Builder(this);

    //Common components of the GoogleClient
    googleApiClientBuilder
            .addApi(Wearable.API)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .useDefaultAccount();

    if (RefWatchUtil.isRefWatchPro()) {
        googleApiClientBuilder
                .addApiIfAvailable(Fitness.HISTORY_API
                ,new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE)
                ,new Scope(Scopes.FITNESS_LOCATION_READ_WRITE)
                );
    }
    googleApiClient = googleApiClientBuilder.build();
    return googleApiClient;
}

注意这里我要求 READ_WRITE 范围,尽管实际上我没有在配置中引用 HISTORY_API(或任何 Fitness Api)。但是,用户必须进入 Watch COnfig 才能打开我的设置(KEY_FITNESS_? 下面),该设置控制读取 Watch Face 中的传感器数据。

最后, 手机

private GoogleApiClient buildGoogleClient() {
    final GoogleApiClient.Builder googleApiClientBuilder;
    googleApiClientBuilder = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApiIfAvailable(Wearable.API); //just in case you are using this without a Wear device
    if (RefWatchUtil.isRefWatchPro()) {
        googleApiClientBuilder
            //.addApiIfAvailable(Fitness.SESSIONS_API)
            .addApiIfAvailable(Fitness.HISTORY_API,    //to read Location and other data per game
                    new Scope(Scopes.FITNESS_ACTIVITY_READ),
                    new Scope(Scopes.FITNESS_LOCATION_READ))
            .useDefaultAccount();
    }
    return googleApiClientBuilder.build();
}

这是我用上面的代码测试的结果。首先,我进入 Google 帐户页面并确保我的应用程序已从连接的应用程序中撤销。

如果我启动移动应用程序,我将被要求:

  • 用于 Soccer Referee Pro 的 Google 帐户
  • 允许该应用查看位置和Activity历史记录
  • 授予读取日历权限(在别处使用) 最终当我想显示一个现场覆盖图时:
  • 授予访问精细位置权限

但是,在我的测试中我故意没有进入移动应用程序。相反,当我启动 Watch Face 时: - 我被要求授予 Access Fine Location 权限(我触发了 Config Activity 意图来处理这个 check/grant)

然后我进入 Watch Config 设置周期长度并打开我的健身收集标志。 Watch Config 从不要求任何权限,并且 以 READ_WRITE 权限默默地成功

当我在表盘上完成数据收集后,我得到以下输出:

? D/RefWatchFace:: Listener registered for data Type com.google.speed
? D/RefWatchFace:: Listener registered for data Type com.google.location.sample
? D/RefWatchFace:: Detected -1 Activity datapoints; inserting 10 (including created ones)
? D/RefWatchFace::  inserting final 10 activity segments (including created ones)
? D/RefWatchFace:: Detected 10 Speed datapoints; inserting in 1 batches
? D/RefWatchFace::  inserting final 10 Speed points (including created ones)
? D/RefWatchFace:: Detected 16 Location datapoints; inserting in 1 batches
? D/RefWatchFace::  inserting final 16 Location points (including created ones)
? D/RefWatchFace:: Checking inserted fitness data for com.google.activity.segment in this batch time 1468440718000-1468441013000
? D/RefWatchFace:: Successfully inserted Session Wed, Jul 13; 1:11PM: Period 1
? D/RefWatchFace:: Checking inserted fitness data for com.google.speed in this batch time 1468440716000-1468440734000
? D/RefWatchFace:: Checking inserted fitness data for com.google.location.sample in this batch time 1468440709575-1468440750000
? D/RefWatchFace:: After insert; read-back found 9 data points for data type com.google.activity.segment
? D/RefWatchFace:: After insert; read-back found 10 data points for data type com.google.speed
? D/RefWatchFace:: After insert; read-back found 16 data points for data type com.google.location.sample

换句话说,我从来没有被要求授予 WRITE 权限,但它还是写了。

在我的第二次测试中,我删除了 Watch Config 应用程序中的 WRITE 权限,并重新运行了 Watch Face。现在 所有 插入 return 5005(未知错误)状态代码,但实际上插入了位置数据(不是速度或Activity 数据)。

在我的第三次测试中,我将 WRITE 权限移至表盘 Google Api 代码。在这种情况下,.connect 立即失败并出现 5005 错误。

结论:在我看来,这里至少存在一个安全漏洞,以及 Wear 端的不一致行为和完全无用的错误。

我终于从 Google 那里得到了关于 useDefaultAccount 如何在幕后工作的答案,这会影响上面 written/read 哪个健身帐户。这是我提出的问题:

我真的需要以下问题的答案,这个问题几个月来一直困扰着我的测试。我正在使用 Wea​​r 表盘上的 Fitness Apis。我在 Google 客户端生成器中使用 "useDefaultAccount"。但是在这种情况下哪个帐户是默认帐户? - 用于下载应用程序的帐户(在这种情况下,当您从开发机器安装应用程序时使用的是哪个帐户) - null/unset - 这是您在连接时选择配套应用程序的帐户。 - 这是在 Google Fit 中选择的当前帐户 - 你不能在 Wear 中使用 useDefaultAccount faces/apps

以及 Google 的回答(感谢 Gustavo Moura): 这是我对逻辑的理解(它没有很好的记录,我们打算在不久的将来清理它): 1. 如果有配套应用并且用户已登录 Fit 并在那里选择了一个帐户,我们会将该帐户同步到 Wear 设备并使用它。 2. 如果没有配套应用(或用户未登录),但用户安装并启用了 Google Fit,并选择了一个 Fit 帐户,我们会将该帐户同步到 Wear设备并使用它。 3. 如果没有配套应用,也没有 Google Fit,但 Wear 设备有另一个具有类似行为的内置应用(如摩托罗拉手表上的 Moto Body),我们将使用该应用中的帐户。 4. 如果以上none为真,还有一些API即使没有账户也可以使用(比如计步器记录和查询)。这是特殊的,因为在此模式下访问的任何数据都将仅是手表本地的数据,并且永远不会同步到任何其他设备或服务器。