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

泪湿孤枕 提交于 2019-11-27 08:47:27

问题


I am looking for some fresh ideas on the dreaded 5005 error: "Status code denotes that an unknown error occurred while trying to obtain an OAuth token." when my Watch Face tries to connect a Google API built with various Fitness APIs. This all works in my local testing AND when I download and run my RELEASE (beta) version. However, when my first tester tried it, he gets that error code when trying to connect the API.

UPDATE2: I have now tracked down what is going on, so I'm going to list my final working code here and then talk in the answer about what I discovered.

There are 3 different occasions where I connect to the Google API: the Watch Face (a referee timer: https://play.google.com/store/apps/details?id=com.pipperpublishing.soccerrefpro), the associated Watch Config, and the companion app on the Mobile (phone).

Watch Face:

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

Notice that I don't ask for WRITE permission for the HISTORY_API. When I later try to insert SPEED and LOCATION fitness data in the Google Fit store, I use this code:

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

}

Remember this read-back code, because I'll reference the results in my answer.

Watch Config The difference here is that the Watch Config extends 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;
}

Notice here I ask for READ_WRITE scopes, although in reality I don't reference the HISTORY_API (or any Fitness Api) in the Config. However, the user has to go into the Watch COnfig to turn on my setting (KEY_FITNESS_? below) which controls reading Sensor data in the Watch Face.

Finally, Mobile

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

回答1:


I finally got an answer from Google on how useDefaultAccount works behind the scenes and this affects which Fit account is being written/read above. Here's the question I posed:

I really need an answer on the following question which has been messing my testing up for months. I am using the Fitness Apis from my Wear watch face. I use "useDefaultAccount" in the Google client builder. But which account is the default in this case? - the account used to download the app (in which case what account is used when you install the app from your dev machine) - it's null/unset - it's the account you pick your companion app when you connect. - it's the current account selected in Google Fit - you can't use useDefaultAccount in Wear faces/apps

And the answer from Google (thanks Gustavo Moura): Here's how I understand the logic (it's not very well documented and we intend to clean it up in the near future): 1. if there's a companion app and the user has signed into Fit and picked an account there, we'll sync that account to the Wear device and use it. 2. if there's no companion app (or the user hasn't signed into it), but the user has Google Fit installed and enabled, and has selected an account with Fit, we'll sync that account to the Wear device and use it. 3. if there's no companion app and no Google Fit, but the Wear device has another built-in app that has similar behavior (like Moto Body on Motorola watches), we'll use the account from that app. 4. if none of the above is true, there are still some APIs that will work even without an account (like step counter recording and querying). This is special in that any data accessed in this mode will be data local to the watch only and it will never be synced to any other devices or servers.




回答2:


Here's the result of my testing with the above code. First I went into the Google Accounts page and made sure that my app was revoked from the Connected Apps.

If I start the Mobile app, I will get asked for:

  • the Google account to use for Soccer Referee Pro
  • Allow the app to View Location and Activity History
  • Grant the Read Calendar permission (used elsewhere) and eventually when I want to display a field coverage map:
  • Grant Access Fine Location permission

However, in my testing I deliberately didn't go into the Mobile app. Instead when I start the Watch Face: - I get asked to grant the Access Fine Location permission (I fire the Config Activity intent to handle this check/grant)

I then go into the Watch Config to set the period length and turn on my Fitness collection flags. The Watch Config never asks for any permissions and succeeds silently with the READ_WRITE permissions.

When I finish my data collection on the watch face, I get the following output:

? 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

In other words, at no point was I asked to grant WRITE permissions, but it wrote anyway.

In my second test, I removed the WRITE permissions in the Watch Config app, and reran the Watch Face. Now all the inserts return a 5005 (Unknown Error) status code, but the Location data is actually inserted (not the Speed or Activity data).

In my third test, I move the WRITE permissions to the Watch Face Google Api code. In this case, the .connect fails immediately with a 5005 error.

Conclusion: It seems to me there is at least one security hole here, as well as inconsistent behavior and completely unhelpful errors on the Wear side.



来源:https://stackoverflow.com/questions/38255110/google-fit-authorization-bugs-with-5005-5000-and-5015-errors-on-watchface-w

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!