【Unity】Meta Questで権限周りの設定画面を開く

## 背景
権限確認ダイアログから、アプリに必要な権限を許可するか問われた際、二度と権限に関して問わないように設定が可能です。

その設定をユーザーが行った場合、権限要求をScriptから行ったとしても権限確認ダイアログが出てくることはありません。つまり、アプリを体験するうえで必要な権限があるのに、その許可を促すことができなくなってしまいます。

その対策として、以下のようなフローを考えました。

:::note
1. アプリ開始時に権限要求を行う
2. 1の処理の後、権限の状態をチェックする
3. 権限が拒否されていた場合、権限周りの設定画面を開く 
:::

## デモ
最初の権限確認ダイアログが録画には映っていませんが、権限を拒否しています。その後、権限が許可されていない状態を検知し、アプリの権限設定を変更する画面に遷移しています。

![QuestSettingsOpen.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/610ffaae-ffb1-56b2-ea46-489cd48fe1e8.gif)

この機能を拡張すれば、モバイルアプリでよくある"権限設定を変更してください"的なモーダルと組み合わせて使えます。

## バージョン情報

| ソフトウェア/ツール | バージョン |
|:-:|:-:|
|  Quest3内のバージョン詳細 |  72.2.2.530.347.670816297 |
| Unity  |  2022.3.46f1 |

※Questのバージョン変更で頻繁に挙動がおかしくなりがちな機能です

## サンプルコード
※[UniTask](https://github.com/Cysharp/UniTask)使っています

```CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class CheckPermission : MonoBehaviour
{
    private readonly List<OVRPermissionsRequester.Permission> _permissionList = new()
    {
        OVRPermissionsRequester.Permission.RecordAudio, OVRPermissionsRequester.Permission.Scene
    };

    private bool _isFocus;

    private void Awake()
    {
        if (Application.isEditor) return;
        RequestPermissionsAsync(this.GetCancellationTokenOnDestroy()).Forget();
    }

    private async UniTask RequestPermissionsAsync(CancellationToken ct)
    {
        // パーミッションが許可されていないものを抽出。
        var notGrantedPermissionList = _permissionList
            .Where(permission => !OVRPermissionsRequester.IsPermissionGranted(permission))
            .ToList();

        // すべてのパーミッションが許可されている場合は何もしない。
        if (notGrantedPermissionList.Count == 0) return;

        // パーミッションが許可されていない場合はリクエストする。
        // もし一度拒否され、その設定が保存されていた場合、このリクエストはスルーされる。
        OVRPermissionsRequester.Request(notGrantedPermissionList);

        // パーミッションリクエストが表示されるまで安全のため0.5秒待ち、アプリにフォーカスが返ってくるまで待機する。
        await UniTask.Delay(TimeSpan.FromSeconds(0.5), cancellationToken: ct);
        await UniTask.WaitUntil(() => _isFocus, cancellationToken: ct);

        // 再度、パーミッションが許可されていないものを抽出。
        var notGrantedPermissionReCheckList = _permissionList
            .Where(permission => !OVRPermissionsRequester.IsPermissionGranted(permission))
            .ToList();

        // パーミッションに許可されていないものがある場合は設定画面を開く。
        if (notGrantedPermissionReCheckList.Count > 0)
        {
            RequestDetailSettings();
        }
    }
    
    private void RequestDetailSettings()
    {
        using var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        using var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        var packageName = activity.Call<string>("getPackageName");
        using var uriClass = new AndroidJavaClass("android.net.Uri");
        using var uriObject = uriClass.CallStatic<AndroidJavaObject>("fromParts", "package", packageName, null);
        using var intentObject = new AndroidJavaObject("android.content.Intent",
            "android.settings.APPLICATION_DETAILS_SETTINGS", uriObject);
        intentObject.Call<AndroidJavaObject>("addCategory", "android.intent.category.DEFAULT");
        intentObject.Call<AndroidJavaObject>("setFlags", 0x10000000);
        activity.Call("startActivity", intentObject);
    }

    private void OnApplicationPause(bool pauseStatus)
    {
        _isFocus = !pauseStatus;
    }
}
```

## 参考リンク
- [【Unity】C#のスクリプトのみで Android アプリの詳細設定画面を開く](https://kazupon.org/unity-android-app-detail-open/)