Android Wearの影に隠れてあまり話題を聞かないGoogle Fit。
何ができるのかドキュメントを読んでまとめました
初期設定の方法も後ろの方に書いてあります。
Google Fit
Google Fitとは、 フィットネスデータ(Fitness Data)をデバイスやアプリをまたいで一元管理できる機能 です。
Google Fitを使うにあたって、開発者が留意しておかなければならないことがあります。下記の5つです。
- なぜそのデータを扱うのかの説明をすること。
- データの削除の要求には誠実に答えること。
- Google Fitからデータを読み込んだ場合は、同じようにGoogle Fitにデータを書き込むこと。
- Google Fit APIをフィットネス以外の目的では使ってならないこと。例えば医療保存、生体認証、販売や広告など。
- Google Fitを利用する際は利用規約をよく読んでから使うこと。Google fit APIを利用した場合はその規約に同意したものとする。
できること
- ウェアラブルデバイスやその他のセンサーからの データを保存 できる
- 他のアプリによって生成された データにアクセス できる
- ウェアラブルデバイスやその他のセンサーが更新された際も データは保持 される
アーキテクチャ
次の4つのコンポーネントで構成されています。
- The fitness store
- フィットネスデータを保存
- The sensor framework
- 各種センサーをGoogle Fit APIsを用いてfitness storeへ簡単にアクセスできるフレームワーク
- Permissions and user controls
- データのアクセス権限とオプトイン
- Google Fit APIs
- Android用APIとREST API
上記の4つの構成で、 Platformを選ばず Google Fitを利用できます。
Android Apis
Android用に用意されているのは次の5本のAPIです。
Sensors API
- デバイスに備わっているセンサーにアクセスしてデータを受け取るためのAPI。センサーの生データを リアルタイムに受け取ることが可能 です。
Recording API
- データをfitness storeに保存するためのAPI。送りたいデータを購読(Subscribe)することでバッテリー効率の良い送信が可能になります。 購読している間は自動 でfitness storeへ送られます。
History API
- fitness storeのデータにアクセスするAPI。InsertやDelete、Readingなどのような、 大量のデータを扱う 際に用います。
Session API
- 行った運動の期間の情報をつけるAPI。その運動の終了時や、Google Fitの外からimportするデータにも Sessionを付け加える ことができます。また、他のアプリのSessionをIntentで受け取ったり、Broadcastを飛ばすこともできます。
Bluetooth Low Energy API
- BLEデバイスに接続するためのAPI。 GATT profileに対応しているBLEデバイス のサーチ、およびBLEデバイスのデータにアクセスできます。
Fitness Data Types
com.google.~
という名前空間でデータ型が用意されています。独自のものも定義できます。
このデータ型が、 fitness storeに保存されたり、各種センサーから取得するデータ です。
データは、瞬間値と統計値の2種類あり、データ型は3種類に分かれています。
また、データの内容によりPermissionが異なります。
瞬間値は、頭にTYPE_
、統計値は、頭にAGGREGATE_
のプレフィックスがつきます。
Permission
GoogleApiClient
を作成する際のScope
です。
頭にFITNESS_
のプレフィックスがつきます。
データの内容、およびアクセスタイプによって異なります。
複数要求する際は、複数記述します。
...
private void buildFitnessClient() {
// Create the Google API Client
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addScope(new Scope(Scopes.FITNESS_LOCATION_READ_WRITE))
.addScope(new Scope(Scopes.FITNESS_BODY_READ_WRITE))
...
Public data types
3種類あるデータ型のうちの一つです。
標準的なデータ型で、どのフィットネスアプリからでも読み書きできます。
com.google.step_count.delta
だと、最後の読み込み時(History API)からどれだけ歩いたか
を表します。PermissionはActivity
で、単位はsteps (int—count)
です。
Private custom data types
3種類あるデータ型のうちの一つです。
アプリ固有のデータ型を定義できますが条件があります。
- 既存のものに似たものがないか
- 明瞭な名前であるか
- 名前は正しくデータを表しているか
- プレフィックスにパッケージネームをつける
他のアプリとは共有できないデータ になるので、使いどころが限られる気がします。
例えば、com.sakebook.android.sample.fit_count_smoke
などが考えられます。
共有したい場合は、次のShareable data types
を使うか、Googleへcustom data
を申請して許可を得ないといけません。
現在許可されているリストを見る限り、個人では難しそうです。
Shareable data types
3種類あるデータ型のうちの一つです。
[Fitness#ConfigApi
](http://developer.android.com/reference/com/google/android/gms/fitness/ConfigApi.html#readDataType(com.google.android.gms.common.api.GoogleApiClient, java.lang.String))経由でどのアプリからでも取得できるデータです。
NIKEのFuelBandのNIKEFUEL
などがあります。
Session
Session API
で扱うことになる情報です。Sessionはメタデータであり、 実際のフィットネスデータを含めません 。
-
start-timeとend-timeで成り立っており、後からfitness storeへアクセスする際にわかりやすいように良い名前をつけましょう。
-
Sessionは必須ではない ですが、自身のアプリで取得したデータなどをわかりやすくするためにも、つけたほうが良いです。自身でメタデータを実装しなくても良いなどの利点もあります。
-
Sessionは 自身が生成されたアプリのパッケージネーム を持っています。
運動中にインターバルを設けたい場合は、com.google.activity.segment
というSessionを用います。
例えばランニングをしていて、数分休憩したのちに再開する場合などは、数分休憩という状態をcom.google.activity.segment
で表現できます。
アプリ・デバイス連携
他のフィットネスアプリや対応しているデバイスとの連携についてです。
データも、自身が生成されたアプリのパッケージネームを持つことができるので、 複数のアプリのデータが存在しても正しく自身のアプリのデータをfitness storeへ送れます。
他のフィットネスアプリのIntent受け取り
AndroidManifest.xml
に記述が必要です。
...
<intent-filter>
<action android:name="vnd.google.fitness.VIEW" />
<data android:mimeType="vnd.google.fitness.data_type/com.google.step_count.cumulative" />
<data android:mimeType="vnd.google.fitness.data_type/com.google.step_count.delta" />
</intent-filter>
...
例では2つしかデータ型を書いていないですが、実際はcustom data typeも含め、すべてのデータ型をサポートすることを推奨しています。
一度に受け取れるデータは一つだけですが、アプリ間で相互に円滑な連携をさせるなのかなと思いました。
受け取ったIntentには次の値がBundleされています。
com.google.gms.fitness.start_time
com.google.gms.fitness.end_time
com.google.gms.fitness.data_source
com.google.gms.fitness.session
以下のように受け取ります。
...
void onCreate (Bundle savedInstanceState) {
...
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
String supportedType = DataType.getMimeType(DataType.TYPE_STEP_COUNT_DELTA);
if (Intent.ACTION_VIEW.equals(action) &&
supportedType.compareTo(type) == 0) {
// Get the intent extras
long startTime = Fitness.getStartTime(intent, TimeUnit.MILLISECONDS);
long endTime = Fitness.getEndTime(intent, TimeUnit.MILLISECONDS);
DataSource dataSource = DataSource.extract(intent);
Session session = Session.extract(intent);
// Show the session in your app
...
}
}
...
他のフィットネスアプリへデータを表示させる
HistoryApi#ViewIntentBuilder
を用いてIntentを作成します。
...
// Inside your activity
long startTime = ...;
long endTime = ...;
DataSource dataSource = ...;
Intent fitIntent = new HistoryApi.ViewIntentBuilder(this)
.setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS)
.setDataSource(dataSource)
.setPreferredApplication("com.example.app") // optional
.build();
...
Sessionのブロードキャスト受け取り
startとendそれぞれ異なるReceiverで受け取ります。
- start
...
<receiver android:name="com.example.SessionStartBroadcastReceiver" >
<intent-filter>
<action android:name="com.google.android.gms.fitness.session_start" />
<data android:mimeType="vnd.google.fitness.activity_type/running" />
</intent-filter>
</receiver>
...
上の例ではrunning開始のactivity_type
を受け取っていますが、様々なtypeがあります。
- end
...
<receiver android:name="com.example.SessionEndBroadcastReceiver" >
<intent-filter>
<action android:name="com.google.android.gms.fitness.session_end" />
<data android:mimeType="vnd.google.fitness.activity_type/running" />
</intent-filter>
</receiver>
...
receiverとactionが微妙に異なっています。
GATTに対応していないBLEデバイスの連携
GATTに対応しているBLEデバイスはBluetooth Low Energy API
で接続ができます。それ以外のGATTに対応していないBLEデバイスも、Google FitのSensorService
を実装したクラスを持つアプリを作ることで、連携できるようになります。
しかも、そのアプリが端末にあれば、端末内の他のフィットネスアプリでもそのBLEデバイスを使えるように、Google Fitがよしなにしてくれるようです。
初期設定
一通り説明したので、ここから初期設定に移ります。
Google Developer Console
- Google Developer Consoleで登録
- プロジェクトを作成
-
APIと認証
->API
からFitness API
をONにしてください。- リクエストの制限は
86,400 リクエスト数/日
です。
- リクエストの制限は
- Client IDの作成
-
APIと認証
->認証情報
からOAuth
の方を選択 - アプリケーションの種類は
インストールされているアプリケーション
を選択 - インストールされているアプリケーションの種類は
Android
を選択 - 適切なパッケージ名を入力
- APKの署名に使うkeystoreのSHA1を入力
- 開発用として
debug.keystore
を用いる -
keytool
コマンドでSHA1を確認
- 開発用として
$ keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v
キーストアのパスワードを入力してください:
#android と入力
別名: androiddebugkey
作成日: 2013/04/01
エントリ・タイプ: PrivateKeyEntry
証明書チェーンの長さ: 1
証明書[1]:
所有者: CN=Android Debug, O=Android, C=US
発行者: CN=Android Debug, O=Android, C=US
シリアル番号: xxxxxxxx
有効期間の開始日: Mon Apr 01 23:44:47 JST 2013終了日: Wed Mar 25 23:44:47 JST 2043
証明書のフィンガプリント:
MD5: xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
SHA1: yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy
SHA256: zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz
署名アルゴリズム名: SHA1withRSA
バージョン: 3
- ディープリンクは利用しないので無効で構いません。
3.同意画面の作成
-
APIと認証
->同意画面
を選択- 適当なサービス名をつけて、保存してください。
プロジェクト設定
1.Moduleのbuilg.gradle
に追加
-
GooglePlay Services6.1以降
が必須ですが、6.5から分割されたので、折角なのでそちらを用います。
...
dependencies {
compile fileTree(dir: 'libs', include:
compile 'com.google.android.gms:play-services-fitness:6.5.+'
}
2.AndroidManifest.xml
にmeta-dataを追加
...
<application
...
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
...
3.チュートリアルにあるように、起動時のActivityにFitness API
を利用するための同意画面表示用、および接続のコードを追加します。
public class MainActivity extends ActionBarActivity {
private static final int REQUEST_OAUTH = 1;
/**
* Track whether an authorization activity is stacking over the current activity, i.e. when
* a known auth error is being resolved, such as showing the account chooser or presenting a
* consent dialog. This avoids common duplications as might happen on screen rotations, etc.
*/
private static final String AUTH_PENDING = "auth_state_pending";
private boolean authInProgress = false;
private GoogleApiClient mClient = null;
private static final String TAG = BuildConfig.APPLICATION_ID;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
authInProgress = savedInstanceState.getBoolean(AUTH_PENDING);
}
buildFitnessClient();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onStart() {
super.onStart();
// Connect to the Fitness API
Log.i(TAG, "Connecting...");
mClient.connect();
}
@Override
protected void onStop() {
super.onStop();
if (mClient.isConnected()) {
mClient.disconnect();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_OAUTH) {
authInProgress = false;
if (resultCode == RESULT_OK) {
// Make sure the app is not already connected or attempting to connect
if (!mClient.isConnecting() && !mClient.isConnected()) {
mClient.connect();
}
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(AUTH_PENDING, authInProgress);
}
/**
* Build a {@link GoogleApiClient} that will authenticate the user and allow the application
* to connect to Fitness APIs. The scopes included should match the scopes your app needs
* (see documentation for details). Authentication will occasionally fail intentionally,
* and in those cases, there will be a known resolution, which the OnConnectionFailedListener()
* can address. Examples of this include the user never having signed in before, or having
* multiple accounts on the device and needing to specify which account to use, etc.
*/
private void buildFitnessClient() {
// Create the Google API Client
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.API)
.addScope(new Scope(Scopes.FITNESS_LOCATION_READ))
.addConnectionCallbacks(
new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected!!!");
// Now you can make calls to the Fitness APIs.
// Put application specific code here.
}
@Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
}
)
.addOnConnectionFailedListener(
new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed. Cause: " + result.toString());
if (!result.hasResolution()) {
// Show the localized error dialog
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
MainActivity.this, 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization dialog is displayed to the user.
if (!authInProgress) {
try {
Log.i(TAG, "Attempting to resolve failed connection");
authInProgress = true;
result.startResolutionForResult(MainActivity.this,
REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG,
"Exception while starting resolution activity", e);
}
}
}
}
)
.build();
}
}
次のようなエラーがでる場合があります。
Connection failed. Cause: ConnectionResult{statusCode=unknown status code 5005, resolution=null}
これは、Google Developer Consoleで、同意画面が作成されていない可能性があります。一度確認して、保存してみてください。
これで一通り動かすことができます。実装部分が少ないですが、以上です。
Developer Challenge
現在、 Google Fit 開発者コンテスト というものが開催されているようです。
応募の締め切りが2015年2月17日なので、まだまだ時間は有ります。
入賞すればかなり大々的にGoogleからPushされるほか、スマートデバイスがいくつかもらえるようです。
新規以外にも、アップデートも対象になるようなので、わりと敷居が低い様に感じました。
興味をもった方は参加してみてはいかがでしょうか?
では、ほんとに以上です。
参考
Google Fit Platform Overview / Google Developers
Android Fit Samples / GitHub
Bluetooth のプロファイルについて調べたことのまとめ / Over&Out その後
How to get Google Play Services 6.5 and use granular dependency management / stackoverflow
Google Fit開発者コンテスト / Google Developers