dnackのブログ

コード書く仕事の間にコード書いてる知命目前のおっさんです。

Prism/XamarinでAndroidアプリの作成(6):Permissionが欲しい

Permission

前回のBLEのデバイススキャンを試したが、位置情報のパーミションをアプリ設定からつけてあげる必要があった。 これはとても面倒なので、BLE使う前に権限を確認して、なければユーザに権限を付けてもらうようにしてみた。 ここは今回の主題ではないので、あっさりと行きたい。というわけで出来上がったソースコードはこちら。 (※3分間クッキングみたいになってきたな…)

他は全然変わらないので、ButtonAClickedAsync( )の中だけですが

        private async void ButtonAClickedAsync( )
        {
            TextMessage.Value = "XXXX";
            var ble = CrossBluetoothLE.Current;
            var adapter = CrossBluetoothLE.Current.Adapter;
            var deviceList = new List<IDevice>();
            
            //State
            Debug.WriteLine("★★★★BLE Stete:"+ble.State);

            //Permission
            // 権限の確認
            var permissionStatus = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
            Debug.WriteLine("★★★★PermissionStatus of location service " + permissionStatus);

            // 権限がなければ権限ダイアログを出してユーザに付与してもらう。
            if (permissionStatus !=  PermissionStatus.Granted )
            {
                permissionStatus = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
            }

            // 権限がもらえなかったら何もしないで終わる。
            if( permissionStatus != PermissionStatus.Granted)
            {
                return;
            }

            //scan device 
            adapter.DeviceDiscovered += (s, a) =>
            {
                deviceList.Add(a.Device);
                Debug.WriteLine("★★★★Found:"+a.Device.Id.ToString());
            };

            adapter.ScanTimeout = 10000;
            
            await adapter.StartScanningForDevicesAsync();

            foreach(var device in deviceList)
            {
                Debug.WriteLine("★★★★:ID" + device.Id.ToString() +":Name:" + device.Name +":RSSI:" + device.Rssi.ToString() +":State:" + device.State.ToString());
            }
        }

このメソッド以外ではPermissions クラスを使うためにusing Xamarin.Essentials;のusing句を追加している。 //Permissionから//scan deviceの間が追加したコードになる。 権限があるか確認に行って(CheckStatusAsync())、権限がなかったらユーザに権限ダイアログを出して(RequestAsync()) ユーザに権限付けてもらえなかったら処理を終わって(return)、 そうでなければ前回書いたデバイススキャンの処理へ行くというコードになっている。 一度アプリ設定から権限を却下して

f:id:dnack:20210908212136p:plain:w120

起動して、ボタンを押してみると、こんなダイアログが出る。

f:id:dnack:20210908212210p:plain:w120

よかったよかった。

これで終われば話は平和なんだが、いや終わってもいいのだが、気になる話がある。

Android11での仕様変更

developer.android.com

曰く、「Android 11 以降では、デバイスにインストールされたアプリの全期間に、同じ権限に対してユーザーが何度も [許可しない] をタップした場合、アプリがその権限を再度リクエストしても、ユーザーにシステム権限ダイアログが表示されることはありません。」 何事かというと、上のキャプチャ画面のような権限ダイアログで「許可しない」を何度も選ぶと今度から権限ダイアログが表示されなくなるらしい。 もちろん、その場合でもアプリ設定から権限付ければ問題なく使えるわけだが、ユーザにそれを促すような仕組みが求められる。 特にBLEの場合は、使いたいのは通信機能なのにいきなり位置情報の権限を求められてよくわからないユーザは「いやダメだろ」って却下しちゃいそうだしなぁ。。

というわけで、上のリンクで「権限の拒否を処理する」もご覧あれと書いていたので読んでみる。

developer.android.com

権限がなかった場合、アクセス許可が必要な理由を説明する必要があるかを確認して、 (これが要するにそのまま進めると権限ダイアログが出ないか出るかということなのだろうか) その必要があれば何か表示して教えてあげなさいっていうことのようですね。 Xamarinのドキュメントの該当箇所はここか。

docs.microsoft.com

これ、”一般的な使用法”のところにほぼそのまま使えるコード書いてるからこれを拝借してくるか。 Xamarinのドキュメントにしてもdeveloper.androidのドキュメントにしても、 ちゃんと読めばちゃんと書いてあるんだよなぁ。ちゃんと読めば()

というわけでこれをパックて書いたコードがこちら。 変更したのはButtonAClickedAsync( ) と追加したCheckAndRequestLocationPermission()だけなのでその部分だけ。

        private async void ButtonAClickedAsync( )
        {
            TextMessage.Value = "XXXX";
            var ble = CrossBluetoothLE.Current;
            var adapter = CrossBluetoothLE.Current.Adapter;
            var deviceList = new List<IDevice>();
            
            //State
            Debug.WriteLine("★★★★BLE Stete:"+ble.State);

            //Permission
            PermissionStatus permission= await CheckAndRequestLocationPermission();
            if ( permission != PermissionStatus.Granted)
            {
                return;
            }

            //scan device 
            adapter.DeviceDiscovered += (s, a) =>
            {
                deviceList.Add(a.Device);
                Debug.WriteLine("★★★★Found:"+a.Device.Id.ToString());
            };

            adapter.ScanTimeout = 10000;
            
            await adapter.StartScanningForDevicesAsync();

            foreach(var device in deviceList)
            {
                Debug.WriteLine("★★★★:ID" + device.Id.ToString() +":Name:" + device.Name +":RSSI:" + device.Rssi.ToString() +":State:" + device.State.ToString());
            }
        }

        private async Task<PermissionStatus> CheckAndRequestLocationPermission()
        {
            var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();

            if (status == PermissionStatus.Granted)
                return status;

            if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
            {
                // Prompt the user to turn on in settings
                // On iOS once a permission has been denied it may not be requested again from the application
                return status;
            }

            if (Permissions.ShouldShowRationale<Permissions.LocationWhenInUse>())
            {
                // Prompt the user with additional information as to why the permission is needed
                await _dialogService.DisplayAlertAsync("Alert",
                    "For BLE communication, permission to location is required.", "OK");
            }

            status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();

            return status;
        }

正直なところどういう条件で勝手にアプリのパーミションを消すのかとかどういう条件でShouldShowRationaleがtrueになるのかとかがわかっていないので、ピントは来ないが、どうやらそれなりにちゃんと動いてそう。 デバイススキャンまでということではこんなものかな。 開始前にBLE.StateとかViewModelに書いてないでModelにもってけとか結果をデバッグじゃなくてUIに出せとかいろいろあるけど、今回はまずはこんなところで。