sos の 作業メモ

プログラミングや英会話学習、マイルや旅行、日常生活など。最近はWebFormなASP.NETのお守りがお仕事です。

日々の生活にhappyをプラスする|ハピタス Gポイント

Google Play servicesの新機能 - Activity Recognition

2013/07/08 追記

クラスの実装例の記事を書きました



というわけで、I/O 2013 で発表されたLocationのもう一つの新機能。正直、これはやられました…

うちでもクライアントサイドのエンジンを作りかけて…いやなんでもないです。

ActivityRecognition

ユーザーの行動の状態を判定してくれる機能。静止、徒歩、自転車、自動車を判定してくれます。

大まかな処理の流れ

  • ActivityRecognitionClientのインスタンスを作ってconnect()
  • serviceにonConnected()したら、callback用のPendingIntentを指定してrequestActivityUpdate()を実行(ここのActivityはandroidのActivityの意味じゃないです)
  • 指定した間隔で状態が判定され、callback用のIntentが呼び出されるので、そこで処理する

Recognitionサービス自体は他のアプリと共用なので、指定したよりも短い間隔でcallbackが呼ばれるかもしれないのと、callbackはバックグラウンドで処理されるのが望ましいので、ServiceクラスでIntentを処理するようにしろとのことです。

DocumentにはIntentServiceで例示されていましたが、ServiceにしてonStartCommandでIntentを処理する方がよいような気がします(好みの問題なのでしょうけど)…とか書きましたが、stopServiceするのが面倒なのでやっぱりIntentServiceにしとくのが無難なのかも?

パーミッション

AndroidManifest.xmlで、以下のパーミッションの使用を宣言する必要があります。

<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>

LocationRequest

Documentにはちょろっとしか記述されてませんが、位置情報があるとより正確な判定になるようです。特にGPSのがよさげ。

サンプル

Start/Stop用のボタンをつけたActivityです。(レイアウトは省略)

public class SecondActivity extends Activity
    implements GooglePlayServicesClient.OnConnectionFailedListener {

private LocationClient mLocClient = null;
private ActivityRecognitionClient mRecognitionClient = null;
private PendingIntent mRecognitionCallback = null;

LocationListener mLocListener = new LocationListener() {
    @Override
    public void onLocationChanged(Location loc) {
        Log.d("location", "loc: " + loc.getTime() + " " + loc.getLatitude() + " " + loc.getLongitude());
    }
};

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    setContentView(R.layout.activity_second);

    if (savedInstanceState == null) {
        if (mLocClient == null) {
            mLocClient = new LocationClient(this, new GooglePlayServicesClient.ConnectionCallbacks() {
                @Override
                public void onConnected(Bundle bundle) {
                    Log.d("location", "onConnected(): " + bundle);
                    LocationRequest req = LocationRequest.create();
                    req.setFastestInterval(1000);
                    req.setInterval(1000);
                    req.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
                    mLocClient.requestLocationUpdates(req, mLocListener);
                }

                @Override
                public void onDisconnected() {
                    Log.d("location", "onDisconnected()");
                    mLocClient.removeLocationUpdates(mLocListener);
                }
            }, this);
            mLocClient.connect();
        }
    }
}

@Override
protected void onDestroy() {
    stopRecognition();
    if (mLocClient != null) {
        mLocClient.disconnect();
    }
    super.onDestroy();
}

private void stopRecognition() {
    Log.d("recgnition", "stopRecognition()");
    if (mRecognitionCallback != null) {
        mRecognitionClient.removeActivityUpdates(mRecognitionCallback);
        mRecognitionCallback = null;
    }
    if (mRecognitionClient != null) {
        mRecognitionClient.disconnect();
        mRecognitionClient = null;
    }
}

public void onClick_start(View view) {
    Button btn = (Button) findViewById(R.id.second_btn_start);
    if (mRecognitionClient == null) {
        btn.setText("Stop");
        mRecognitionClient = new ActivityRecognitionClient(this, new GooglePlayServicesClient.ConnectionCallbacks() {
            @Override
            public void onConnected(Bundle bundle) {
                Log.d("recgnition", "onConnected(): " + bundle);
                Intent intent = new Intent(SecondActivity.this, MyRecognitionService.class);
                intent.setAction(MyRecognitionService.ACTION_STRING);
                mRecognitionCallback = PendingIntent.getService(SecondActivity.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                mRecognitionClient.requestActivityUpdates(3000, mRecognitionCallback);
            }

            @Override
            public void onDisconnected() {
                Log.d("recgnition", "onDisconnected()");
                if (mRecognitionCallback != null){
                    mRecognitionClient.removeActivityUpdates(mRecognitionCallback);
                    mRecognitionCallback = null;
                }
            }
        }, this);
        mRecognitionClient.connect();
    } else {
        btn.setText("Start");
        stopRecognition();
    }
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
    Log.d("test.play", "onConnectionFailed()" + connectionResult);
}
}

こっちが呼び出されるサービス側

public class MyRecognitionService extends IntentService {
public static final String ACTION_STRING = "jp.tfv.test.play.ACTION_STRING";

public MyRecognitionService(String name) {
    super(name);
}

public MyRecognitionService() {
    super("MyRecognitionService");
}

@Override
protected void onHandleIntent(Intent intent) {
    Log.d("recognition", "onHandleIntent: " + intent);
    if (ACTION_STRING.equals(intent.getAction())) {
        if (ActivityRecognitionResult.hasResult(intent)) {
            ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent);
            DetectedActivity detectedActivity = result.getMostProbableActivity();
            String msg;
            switch (detectedActivity.getType()) {
            case DetectedActivity.IN_VEHICLE:
                msg = "くるま";
                break;
            case DetectedActivity.ON_BICYCLE:
                msg = "自転車";
                break;
            case DetectedActivity.ON_FOOT:
                msg = "徒歩";
                break;
            case DetectedActivity.STILL:
                msg = "静止";
                break;
            case DetectedActivity.UNKNOWN:
                msg = "不明";
                break;
            case DetectedActivity.TILTING:
                msg = "傾いた";
                break;
            default:
                msg = "";
                break;
            }
            Log.d("recognition", "detedtedActivity: " + detectedActivity.getType() + " " + msg);
        }
    }
}
}

動作テスト結果

京都はあいにく雨模様だったので、会社を出て傘をさして駐車場まで歩いて行き、くるっとKRPの周りを車でまわって帰って来ました。

徒歩、自動車、静止がそれなりに判定されて、わりといい感じ。屋内ではまったく駄目ですが、そのうち改善されそうな予感も。


Geofenceと組み合わせて、自転車でこのエリアに入ったとかが楽に判定できるようになったりと、いろいろ応用範囲が広がりますね。

いやぁこんなのが無料で提供されるようになるとは…しかし、いろんなデータをグーグルに握られるということだけは忘れないようにしないとですね。