Androidはワンツーパンチ 三歩進んで二歩下がる

プログラミングやどうでもいい話

代々木ドッツへの行き方(シェアオフィス、コワーキングスペースConnecting The Dots Yoyogi)

※当エントリーはもくもく会に来てくれる方に情報を提供するために書いたもので、Connecting The Dotsを運営されているインクルードさまとは関係ありません。
2017/1/14の情報なので、古くなっている可能性があります。ご注意下さい。
引用やリンクはご自由にどうぞ( ´∀`)

f:id:sakura_bird1:20161119104703j:plain

公式サイト

dots.bz

所在地

〒151‐0053 東京都渋谷区代々木1-29-5 4F
JR線 代々木駅西口 徒歩1分
都営地下鉄大江戸線 代々木駅 徒歩1分
小田急線 南新宿駅 徒歩5分

JR代々木駅からの行き方

サマリー

  1. JR代々木駅の西口から出ます
  2. 降りてすぐ前にある交番の後ろに赤茶っぽいビルがあります。代々木教会 国際英語学校と書いてあります。
  3. ビルの前の信号を渡ります。
  4. 入ってすぐのガラスの扉の内側にエレベーターがあります。
  5. エレベーターで4階に行きます。
  6. 降りてすぐガラス扉があります。左側にタッチスクリーンがあり、「TOUCH TO CALL」と書いてあります。
  7. 「TOUCH TO CALL」を押して開けてもらいます。鍵がかかってなければ自分で開けて入る場合もあります。

写真

  1. JR代々木駅の西口から出ます

f:id:sakura_bird1:20161126110237j:plain

  1. 降りてすぐ前にある交番の後ろに赤茶っぽいビルがあります。代々木教会 国際英語学校と書いてあります。

f:id:sakura_bird1:20170114101414j:plain

  1. ビルの前の信号を渡ります。

f:id:sakura_bird1:20161126110430j:plain

  1. ふと後ろを振り返ると南新宿のNTTドコモ代々木ビルが見えます。

f:id:sakura_bird1:20161126110531j:plain

  1. ビルの入口です。

f:id:sakura_bird1:20170114101517j:plain

f:id:sakura_bird1:20161126110558j:plain

  1. 入ってすぐのガラスの扉の内側にエレベーターがあります。奥には三菱東京UFJ銀行のATMがあります。

f:id:sakura_bird1:20161129204457j:plain

  1. エレベーターで4階に行きます。

f:id:sakura_bird1:20161126110625j:plain

  1. 降りてすぐガラス扉があります。左側にタッチスクリーンがあり、「TOUCH TO CALL」と書いてあります。

f:id:sakura_bird1:20161126110702j:plain

  1. 「TOUCH TO CALL」を押して開けてもらいます。鍵がかかってなければ自分で開けて入る場合もあります。

f:id:sakura_bird1:20161126110708j:plain

  1. 入ってすぐのエリアがフリーエリアです。

f:id:sakura_bird1:20170114101930j:plain

f:id:sakura_bird1:20170114101927j:plain

  1. 奥の蜂の巣状のエリアは月額会員さんの使うエリアです。

f:id:sakura_bird1:20170114101938j:plain

  1. トイレは入ってすぐ右にあります。青い目印があるドアは男子トイレ、赤い目印があるドアは女子トイレの入り口です。

f:id:sakura_bird1:20161119104707j:plain

以上です。







ご不要なお酒、高く売れるって知ってました?

今夜、お泊まり女子会できます。【バリアン女子会】

LocationManagerはもう古い!Google Service の Location APIを使って現在位置を取得する

しばらく使っていない分野のAPIっていつの間にか非推奨になってたりしますよね。
変化の激しいAndroid開発で全ての変化に付いていくのは至難の業だと思います。
恥ずかしいんですが、この度久しぶりに位置情報の取得方法を調べましたら結構前に時代が変わっていました。

f:id:sakura_bird1:20161124215822p:plain

かつてAndroidで位置情報を使ったコードを実装すると言えばandroid.location 以下のAPIを使用するということでした。

LocationManagerインスタンスを作ってrequestLocationUpdatesのメソッドを使って位置情報を取得し、LocationListenerでコールバックを受け取って画面などの値を更新などとやっていましたね。

現在デベロッパーサイトのandroid.locationにアクセスすると、以下のように注意書きがあります。

(デベロッパーサイトは英語ですが和訳を載せます)

このAPIは、Androidの場所にアクセスするための推奨方法ではありません。
Google Playサービスの一部であるGoogle Location Services APIは、アプリに位置情報を追加するための好ましい方法です。これは、よりシンプルなAPI、より高い精度、低消費電力のジオフェンシングなどを提供します。現在android.location APIを使用している場合は、できるだけ早くGoogle Location Services APIに切り替えることを強くおすすめします。

Google Play serviceの location APIは数年前からありましたが、Androidフレームワークの方はdeprecatedにはなっていないもののLocation APIの方を強くおすすめされるようになってます。
2013年のGoogle I/Oで発表があったようなので少なくとも2年以上は時代に遅れていたようです。
Google I/Oは一応チェックしているので、当時大いに首を縦に振りながら納得したかもしれないのですが、
3歩歩くと忘れるので忘れました(´-﹏-`;)


参考
Google Play Service Analysis (4) – Choice between Google Play Location Service and Android Location Serviceantoniohongkr.wordpress.com


本エントリではLocation APIについての調査と導入方法、基本的な使用方法について記述していきます。
ランタイムパーミッションやライブラリや設定の事前チェックの実装まで含めるとなかなか複雑です。

Location APIだと何がいいのか

  1. 位置が正確
  2. ほとんどの場合、バッテリーのパフォーマンスと適切な精度が向上する
  3. Fused Location Providerという仕組みが導入され、精度のパラメータにもとづいてロケーションソースを適切に使用する
  4. Geofencing APIが追加され、ユーザーが指定の範囲に出入りした時にアプリに通知することができる
  5. Activity Recognition APIが追加され、ユーザーの行動を認識できる(徒歩なのか、車移動なのか、など)


位置情報の取得にはアプリの要件によって条件が変わってきます。
正確さ命!5秒毎に位置取得!という要件では電力消費とトレードオフとなります。
Location APIには要件に合わせた最適化をやってくれるので複雑なコードを書かなくてもよいです(๑•̀ㅂ•́)و✧

導入

このクラスはGoogle Play Serviceのクライアントライブラリを使用します。
セットアップはこちらの公式サイトもご覧ください。
Android 2.3以上で使用できます。

Set Up Google Play Services  |  Google APIs for Android  |  Google Developers

Google Play Serviceは65,536メソッドの制限を考えると、必要な機能だけ導入するほうがよいでしょう。
Google Location and Activity Recognition APIはplay-servicesの後ろに「-location」と指定します。
appモジュールのbuild.gradleに次のように記述します。10.0.0となっているところは最新のバージョン番号を書きましょう。

    dependencies {
        compile 'com.google.android.gms:play-services-location:10.0.0'
    }

Google Play Service APKがユーザーの端末にはいっているかチェックする

Google Play Service APKは(日本で発売されている一般的なAndroidスマートフォンには入っていますが)全ての端末に入っているかは確実でなく、アップデートが必要な場合でもアップデートされていない場合があります。
GoogleApiClientクラスのインスタンスを作成する時に OnConnectionFailedListener をセットしましょう。
バイスが適切なGoogle Play Serviceライブラリを持っていない場合 onConnectionFailed()にエラー結果となって返されます。

GoogleApiAvailability クラスのisGooglePlayServicesAvailable()メソッドでチェックするのもよいです。

GoogleApiAvailability  |  Google APIs for Android  |  Google Developers

例えば次のように記述すると、ユーザーの端末のGoogle Play Service apkが有効かどうかチェックして、エラー内容に合わせたダイアログを表示してくれます。

        int resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
        if (resultCode != ConnectionResult.SUCCESS) {
            GoogleApiAvailability.getInstance().getErrorDialog((Activity) context, resultCode, SOME_REQUEST_CODE).show();
        }

f:id:sakura_bird1:20161126165423p:plain

パーミッション宣言

AndroidManifest.xmlに位置情報のパーミッションを宣言します。
ACCESS_FINE_LOCATIONはGPS_PROVIDERとNETWORK_PROVIDERを使用する場合に指定します。
ACCESS_COARSE_LOCATIONはNETWORK_PROVIDERのみを使用する場合に指定します。
ACCESS_FINE_LOCATIONを指定したら、ACCESS_COARSE_LOCATIONの指定は必要ありません。

  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

ビルドバージョンがAndroid6.0(SDK 23)以上の場合実行時にユーザーからパーミッションの許可を得る

Android6.0以上はRuntime Permissionが導入されています。実行時にユーザーからパーミッションの許可を得ないとSecurityExceptionが発生してクラッシュします。サンプルコードにはパーミッションの許諾のコードが入っていますのでご参考にどうぞ。

developer.android.com

PermissionsDispatcherという素晴らしいOSSライブラリもあるので、使ってみると幸せになれるかもです。
github.com

Location APIを使って位置情報を取得する

Google Play Servicesに接続する

まずonCreate()あたりのタイミングでGoogleApiClientを生成します。この時にLocationServices.APIを追加します。
GoogleApiClient.ConnectionCallbacksとGoogleApiClient.OnConnectionFailedListener を実装します。

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        LocationListener {

(中略)
    protected synchronized void buildGoogleApiClient() {
        Log.i(TAG, "Building GoogleApiClient");
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
        createLocationRequest();
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        // 接続時の処理を記述します
    }

    @Override
    public void onConnectionSuspended(int i) {
        // 何らかの理由で接続が無くなった場合の処理を記述します
        mGoogleApiClient.connect();
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        // 接続が失敗した場合の処理を記述します。ConnectionResult のエラーコードから失敗理由がわかります。
        Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + connectionResult.getErrorCode());
    }

(中略)

接続と接続解除をonStart(),onStop()あたりのタイミングで行います。

protected void onStart() {
    mGoogleApiClient.connect();
    super.onStart();
}

protected void onStop() {
    mGoogleApiClient.disconnect();
    super.onStop();
}

LocationRequestをセットアップする

位置情報取得の際の正確さのレベル、更新間隔など要件に合わせたパラメータをセットします。
公式サイトで全てのオプションをご確認ください。
LocationRequest  |  Google APIs for Android  |  Google Developers


Priorityの指定では電力消費と正確さのオプションが4つありますのでご紹介します。

Priority 特徴
PRIORITY_BALANCED_POWER_ACCURACY 精度は100メートル。精度は粗い。消費電力はより少ない。ネットワークの位置情報を使う可能性が高い。ロケーションプロバイダの選択は、使用可能なソースなど、他の多くの要因に依存する。
PRIORITY_HIGH_ACCURACY 精度は最も正確。消費電力は多い。GPSを使用して位置を特定する可能性が高くなる。
PRIORITY_LOW_POWER 精度は10km。都市レベル。消費電力は少ない。
PRIORITY_NO_POWER 消費電力はごくわずか。利用可能な場合は場所の更新を受信する必要がある。この設定では、アプリは場所の更新をトリガーするのではなく、他のアプリによってトリガーされた場所を受け取る。

実装例

        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

端末の位置情報アクセスが有効になっているかチェックする

端末の設定で位置情報が無効にされている場合、事前にチェックして有効にしてもらう必要があります。

GPSWi-Fiスキャンなどの適切なシステム設定を有効にする必要があります。デバイスGPSなどのサービスを直接有効にするのではなく、必要な精度/消費電力と更新間隔を指定して、デバイスが自動的にシステム設定を適切に変更します。これらの設定は、LocationRequestデータオブジェクトによって定義されます。

ここよりGoogle翻訳です。

上記の引用は微妙によくわからない説明なのですが、次のようなコードを書くと位置情報設定の状態を取得→有効にする必要があればダイアログを表示するという処理を事前に行うことが出来ます
参考 Googleサンプルのgitリポジトリ(位置情報設定)
https://github.com/googlesamples/android-play-location/tree/master/LocationSettings


1. LocationSettingsRequest.Builderのインスタンスを作成しLocationRequestを追加します

LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
     .addLocationRequest(mLocationRequest);

2. 設定の状態を取得します

PendingResult<LocationSettingsResult> result =
         LocationServices.SettingsApi.checkLocationSettings(mGoogleClient, builder.build());

3. 取得した状態によって処理を進めます

        result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
            @Override
            public void onResult(@NonNull LocationSettingsResult locationSettingsResult) {
                final Status status = locationSettingsResult.getStatus();

                switch (status.getStatusCode()) {
                    case LocationSettingsStatusCodes.SUCCESS:
                        // 設定が有効になっているので現在位置を取得する
                        if (ContextCompat.checkSelfPermission(
MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, MainActivity.this);
                        }
                        break;
                    case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                        // 設定が有効になっていないのでダイアログを表示する
                        try {
                            // Show the dialog by calling startResolutionForResult(),
                            // and check the result in onActivityResult().
                            status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
                        } catch (IntentSender.SendIntentException e) {
                            // Ignore the error.
                        }
                        break;
                    case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                        // Location settings are not satisfied. However, we have no way
                        // to fix the settings so we won't show the dialog.
                        break;
                }
            }
        });


    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_CHECK_SETTINGS:
                switch (resultCode) {
                    case Activity.RESULT_OK:
                        startLocationUpdates();
                        break;
                    case Activity.RESULT_CANCELED:
                        break;
                }
                break;
        }
    }

設定画面の位置情報をOffにすると、次のようなダイアログを表示してくれます。
OKを押すと設定がOnになります。(その後上記のコードでは位置情報を取得開始するようにしています。)
f:id:sakura_bird1:20161127121846p:plain

現在位置を取得する

最新の位置を取得する場合LocationServices.FusedLocationApi.requestLocationUpdatesメソッドを使用します。
位置情報のコールバックには、com.google.android.gms.location.LocationListenerを実装します。
既に取得済みの最新の位置を取得するにはLocationServices.FusedLocationApi.getLastLocationメソッドを使用します。

      LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, MainActivity.this);

    @Override
    public void onLocationChanged(Location location) {
        Log.i(TAG, "onLocationChanged");
        // 取得した位置情報を使用して処理を記述します
        mCurrentLocation = location;
        mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
        updateUI();
        Toast.makeText(this, getResources().getString(R.string.location_updated_message), Toast.LENGTH_SHORT).show();
    }

サンプルでは10秒おきに位置情報を取得しているのですが、消費電力を考慮しonPause()で取得処理をストップし、onResume()で再開しています。

    @Override
    public void onResume() {
        super.onResume();
        if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates) {
            startLocationUpdates();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // Stop location updates to save battery, but don't disconnect the GoogleApiClient object.
        if (mGoogleApiClient.isConnected()) {
            stopLocationUpdates();
        }
    }

サンプルコード

長くなりましたが、これらのサンプルはまとめると以下のようになります。
当エントリーではGoogleのサンプルを元にnullチェックやパーミッションチェックなどを独自に加えています。
ボタンを押すと10秒間隔で位置情報を更新します。

Githubにもプロジェクト全体をアップロードしてあります。
github.com

画面イメージはこのようになります。
f:id:sakura_bird1:20161127213628p:plain

package com.sakurafish.exam.location.api;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.databinding.DataBindingUtil;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResult;
import com.google.android.gms.location.LocationSettingsStatusCodes;
import com.sakurafish.exam.location.api.databinding.ActivityMainBinding;

import java.text.DateFormat;
import java.util.Date;

/**
 * Retrieve current location using Google Play Services Location API
 * Based on "https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates"
 */
public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        LocationListener {

    protected static final String TAG = "location-updates-sample";
    /**
     * 10秒間隔で位置情報を更新。実際には多少頻度が多くなるかもしれない。
     */
    public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;

    /**
     * 最速の更新間隔。この値より頻繁に更新されることはない。
     */
    public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
            UPDATE_INTERVAL_IN_MILLISECONDS / 2;


    private final static String REQUESTING_LOCATION_UPDATES_KEY = "requesting-location-updates-key";
    private final static String LOCATION_KEY = "location-key";
    private final static String LAST_UPDATED_TIME_STRING_KEY = "last-updated-time-string-key";

    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private static final int REQUEST_CHECK_SETTINGS = 10;

    private ActivityMainBinding mBinding;
    private GoogleApiClient mGoogleApiClient;
    private LocationRequest mLocationRequest;
    private Location mCurrentLocation;
    private Boolean mRequestingLocationUpdates;
    private String mLastUpdateTime;
    private String mLatitudeLabel;
    private String mLongitudeLabel;
    private String mLastUpdateTimeLabel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        mLatitudeLabel = getResources().getString(R.string.latitude_label);
        mLongitudeLabel = getResources().getString(R.string.longitude_label);
        mLastUpdateTimeLabel = getResources().getString(R.string.last_update_time_label);
        mRequestingLocationUpdates = false;
        mLastUpdateTime = "";

        updateValuesFromBundle(savedInstanceState);
        buildGoogleApiClient();
    }

    private void updateValuesFromBundle(Bundle savedInstanceState) {
        Log.i(TAG, "Updating values from bundle");
        if (savedInstanceState != null) {
            if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) {
                mRequestingLocationUpdates = savedInstanceState.getBoolean(
                        REQUESTING_LOCATION_UPDATES_KEY);
                setButtonsEnabledState();
            }

            if (savedInstanceState.keySet().contains(LOCATION_KEY)) {
                mCurrentLocation = savedInstanceState.getParcelable(LOCATION_KEY);
            }
            if (savedInstanceState.keySet().contains(LAST_UPDATED_TIME_STRING_KEY)) {
                mLastUpdateTime = savedInstanceState.getString(LAST_UPDATED_TIME_STRING_KEY);
            }
            updateUI();
        }
    }

    protected synchronized void buildGoogleApiClient() {
        Log.i(TAG, "Building GoogleApiClient");
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
        createLocationRequest();
    }

    protected void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }

    public void startUpdatesButtonHandler(View view) {
        clearUI();
        if (!isPlayServicesAvailable(this)) return;
        if (!mRequestingLocationUpdates) {
            mRequestingLocationUpdates = true;
        } else {
            return;
        }

        if (Build.VERSION.SDK_INT < 23) {
            setButtonsEnabledState();
            startLocationUpdates();
            return;
        }
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            setButtonsEnabledState();
            startLocationUpdates();
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.ACCESS_FINE_LOCATION)) {
                showRationaleDialog();
            } else {
                ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
            }
        }
    }

    public void stopUpdatesButtonHandler(View view) {
        if (mRequestingLocationUpdates) {
            mRequestingLocationUpdates = false;
            setButtonsEnabledState();
            stopLocationUpdates();
        }
    }

    private void startLocationUpdates() {
        Log.i(TAG, "startLocationUpdates");

        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
                .addLocationRequest(mLocationRequest);
        // 現在位置の取得の前に位置情報の設定が有効になっているか確認する
        PendingResult<LocationSettingsResult> result =
                LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());
        result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
            @Override
            public void onResult(@NonNull LocationSettingsResult locationSettingsResult) {
                final Status status = locationSettingsResult.getStatus();

                switch (status.getStatusCode()) {
                    case LocationSettingsStatusCodes.SUCCESS:
                        // 設定が有効になっているので現在位置を取得する
                        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, MainActivity.this);
                        }
                        break;
                    case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                        // 設定が有効になっていないのでダイアログを表示する
                        try {
                            status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
                        } catch (IntentSender.SendIntentException e) {
                            // Ignore the error.
                        }
                        break;
                    case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                        // Location settings are not satisfied. However, we have no way
                        // to fix the settings so we won't show the dialog.
                        break;
                }
            }
        });
    }

    private void setButtonsEnabledState() {
        if (mRequestingLocationUpdates) {
            mBinding.startUpdatesButton.setEnabled(false);
            mBinding.stopUpdatesButton.setEnabled(true);
        } else {
            mBinding.startUpdatesButton.setEnabled(true);
            mBinding.stopUpdatesButton.setEnabled(false);
        }
    }

    private void clearUI() {
        mBinding.latitudeText.setText("");
        mBinding.longitudeText.setText("");
        mBinding.lastUpdateTimeText.setText("");
    }

    private void updateUI() {
        if (mCurrentLocation == null) return;

        mBinding.latitudeText.setText(String.format("%s: %f", mLatitudeLabel,
                mCurrentLocation.getLatitude()));
        mBinding.longitudeText.setText(String.format("%s: %f", mLongitudeLabel,
                mCurrentLocation.getLongitude()));
        mBinding.lastUpdateTimeText.setText(String.format("%s: %s", mLastUpdateTimeLabel,
                mLastUpdateTime));
    }

    protected void stopLocationUpdates() {
        Log.i(TAG, "stopLocationUpdates");
        // The final argument to {@code requestLocationUpdates()} is a LocationListener
        // (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html).
        LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    setButtonsEnabledState();
                    startLocationUpdates();
                } else {
                    if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
                        mRequestingLocationUpdates = false;
                        Toast.makeText(MainActivity.this, "このアプリの機能を有効にするには端末の設定画面からアプリの位置情報パーミッションを有効にして下さい。", Toast.LENGTH_SHORT).show();
                    } else {
                        showRationaleDialog();
                    }
                }
                break;
            }
        }
    }

    private void showRationaleDialog() {
        new AlertDialog.Builder(this)
                .setPositiveButton("許可する", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ActivityCompat.requestPermissions(MainActivity.this,
                                new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
                    }
                })
                .setNegativeButton("しない", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "位置情報パーミッションが許可されませんでした。", Toast.LENGTH_SHORT).show();
                        mRequestingLocationUpdates = false;
                    }
                })
                .setCancelable(false)
                .setMessage("このアプリは位置情報の利用を許可する必要があります。")
                .show();
    }

    public static boolean isPlayServicesAvailable(Context context) {
        // Google Play Service APKが有効かどうかチェックする
        int resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
        if (resultCode != ConnectionResult.SUCCESS) {
            GoogleApiAvailability.getInstance().getErrorDialog((Activity) context, resultCode, 2).show();
            return false;
        }
        return true;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_CHECK_SETTINGS:
                switch (resultCode) {
                    case Activity.RESULT_OK:
                        startLocationUpdates();
                        break;
                    case Activity.RESULT_CANCELED:
                        break;
                }
                break;
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override
    public void onResume() {
        super.onResume();
        isPlayServicesAvailable(this);

        // Within {@code onPause()}, we pause location updates, but leave the
        // connection to GoogleApiClient intact.  Here, we resume receiving
        // location updates if the user has requested them.

        if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates) {
            startLocationUpdates();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // Stop location updates to save battery, but don't disconnect the GoogleApiClient object.
        if (mGoogleApiClient.isConnected()) {
            stopLocationUpdates();
        }
    }

    @Override
    protected void onStop() {
        stopLocationUpdates();
        mGoogleApiClient.disconnect();

        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        Log.i(TAG, "onConnected");
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        if (mCurrentLocation == null) {
            mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
            updateUI();
        }

        if (mRequestingLocationUpdates) {
            startLocationUpdates();
        }
    }

    @Override
    public void onLocationChanged(Location location) {
        Log.i(TAG, "onLocationChanged");
        mCurrentLocation = location;
        mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
        updateUI();
        Toast.makeText(this, getResources().getString(R.string.location_updated_message), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onConnectionSuspended(int i) {
        // The connection to Google Play services was lost for some reason. We call connect() to
        // attempt to re-establish the connection.
        Log.i(TAG, "Connection suspended");
        mGoogleApiClient.connect();
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        // Refer to the javadoc for ConnectionResult to see what error codes might be returned in
        // onConnectionFailed.
        Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + connectionResult.getErrorCode());
    }

    public void onSaveInstanceState(Bundle savedInstanceState) {
        savedInstanceState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY, mRequestingLocationUpdates);
        savedInstanceState.putParcelable(LOCATION_KEY, mCurrentLocation);
        savedInstanceState.putString(LAST_UPDATED_TIME_STRING_KEY, mLastUpdateTime);
        super.onSaveInstanceState(savedInstanceState);
    }
}

何かお気づきの点がありましたらお気軽にお知らせ下さい。



ネコ活@東京キャットガーディアンで一日ボランティア体験してきました

こんにちは。さくらです。
私は現在飼っていないのですが、猫に対する愛は常に心の底に流れています!
といってもまた猫を迎えるという気になれず、ささやかな寄付やお友達のお宅の可愛いにゃんこさんと遊ぶ程度しか猫の世界に接していません。
このまま猫の世界と交差することなく遠くで眺めているだけなのかなーと思うと、それも寂しかったりします。
猫の世界に対する方針(なんだそれ笑)が決定せず、デモデモダッテとグズグズしている状態が現状です。
猫を飼うということは、あと20年生きるという決意をすることなんですよね。猫の寿命は長いと20年近くなりますから。
前のにゃんこ3匹は拾われて、うちに連れてこられて、考える暇がなかったですからね。
今の私にとっては重い決断です。

f:id:sakura_bird1:20161122172619j:plain


そんなこととは別にネットの海をぶらぶらしてたら見つけました。

tcg.ldblog.jp


一日だけ気軽にボランティア出来るし、猫さん達も見られるらしい、といい感じではありませんか!
さっそくブログ記事にあるメールアドレス宛にメールを送って参加登録しました。
返信で当日用意することなど、わかりやすくご案内下さいました。


このネコ活を開催している「東京キャットガーディアン」という団体は一体何でしょうか。
実は過去こちらに寄付した記事を書いています。

http://sakura-bird1.hatenablog.com/entry/2016/07/31/173612sakura-bird1.hatenablog.com


殺処分ゼロを目指して、多くの猫を譲渡する活動を中心として保護猫カフェやペット用品リサイクル販売他
様々な活動をされています。
詳しくはこちらで

東京キャットガーディアンについて - NPO法人 東京キャットガーディアン〜子猫の里親募集〜


当日は猫の毛などが付いてもよいような服装で行ってきました。
10人ぐらいは他にもボランティア参加の方が来ていました。
みんな女性でした(*‘ω‘ *)

私が参加した11月22日は火曜日なのでスカイシェルターという猫カフェスペースはおやすみなのですが、
今日だけ特別に、とネコ活の私達をお招きいただきました。
ちなみにお休みの日はまだ人に慣れていない子を出したり、色々とあるそうです。

ボランティアの作業内容は、掃除に使用する古タオルを小さくカットすることでした。
猫さんが足元をウロウロしてるし、他の方の話を聞いてたりしたらあっという間でした。

f:id:sakura_bird1:20161122172834j:plain

上の写真はゴミではありません。
カットしたタオルの一部です。この袋3つ分ぐらいは出来たのではないかな。
それにしても全員猫好きであるという空間はいいですね。
楽しかったです。
もっとお手伝いしたくなりました。
また行きます。今度行くときは早く行って猫カフェで猫とまったり戯れたいです。

今月の寄付はシェルターで寄付した2000円です。いつもより少ない(;´∀`)
ちょっと収入減りますんで、今後寄附金額少なくなるかもです( ;∀;)

f:id:sakura_bird1:20161122172548j:plain


実際は写真に写っていないところにいっぱい猫がいます。


【追記】ネコ活の記事はこちらにもございます。よろしければごらんください。
sakura-bird1.hatenablog.com
sakura-bird1.hatenablog.com