App Linksに対応してみた

## AndroidのApp Linksとは

公式情報
https://developer.android.com/training/app-links/

雑な理解としては、1つのサービスに対して、WebとAndroidアプリがある場合に、
Android端末上で別のアプリから `https://(自分のWebサイト)...` というURLに遷移した場合に、アプリがインストールされてればアプリに遷移する。
といったものです。

## 実装

今回は、
http://my-android-server.appspot.com/html/list.html?page_no=1&open=2
にアクセスすると、
https://play.google.com/store/apps/details?id=hm.orz.chaos114.android.tumekyouen
のアプリのステージ2の画面が表示されるようにします。

### まずは、intent-filterの実装

公式情報
https://developer.android.com/training/app-links/deep-linking

今回は、AndroidManifest.xmlにて下記のように設定しました。

```xml
        <activity
            android:name=".modules.initial.InitialActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>

                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>

                <data
                    android:host="my-android-server.appspot.com"
                    android:pathPrefix="/html/list.html"
                    android:scheme="http"/>
            </intent-filter>
        </activity>
```

2つ目のintent-filterが、
`http://my-android-server.appspot.com/html/list.html` から始まるURLが実行された場合、
という意味になります。

ここまでで、下記のコマンドを実行することで、アプリの起動ができました。（シェルによっては、`"`や`\`が無くてもいいかも。。？）

```
adb shell am start -W -a android.intent.action.VIEW -d "http://my-android-server.appspot.com/html/list.html?page_no=1\&open=2"
```

ただ、このままでは、

- アプリが起動するだけで、対象の画面に遷移しない
- 起動時にブラウザ or 任意のアプリという選択画面が表示される

という状態になっています。順に解決していきます。

### 起動時のURLから、任意の画面に遷移させる

公式情報
https://developer.android.com/training/app-links/deep-linking#handling-intents

intent-filterを追加したActivityにて、`getIntent`の戻り値を利用して遷移を行います。

ここはアプリによって大きく変わってくるかと思います。
基本的には、`getIntent().getData()`をとってみて、nullだったら通常の遷移、値が入っていたらURLを解析して対象の画面に遷移させる形になるかと。

今回は https://github.com/noboru-i/kyouen-android/commit/362ccff367b4bc24ac91b5c2abd9c130b34abee8#diff-ae9d6323f6b1c38043ae5931c6049b56 のように実装しました。

### アプリ選択画面をスキップさせる

公式情報
https://developer.android.com/training/app-links/verify-site-associations

ここが今回の本題です。

まずは、Android側のintent-filterに `android:autoVerify="true"` を追加します。

```xml
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW"/>

                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>

                <data
                    android:host="my-android-server.appspot.com"
                    android:pathPrefix="/html/list.html"
                    android:scheme="http"/>
            </intent-filter>
```

次に、サーバ側。
公式情報
https://developer.android.com/training/app-links/verify-site-associations#web-assoc
（https://www.google.co.jp/.well-known/assetlinks.json のように、他の有名ドメインのものは参考にできそうです。）

`.well-known/assetlinks.json` というファイルを作成し、内部を以下のようにしました。

```json
[
    {
        "relation": ["delegate_permission/common.handle_all_urls"],
        "target": {
            "namespace": "android_app",
            "package_name": "hm.orz.chaos114.android.tumekyouen.debug",
            "sha256_cert_fingerprints":
            ["C0:D7:F9:34:D8:F7:15:06:FA:21:FA:0D:CF:09:E5:C4:E2:66:D4:11:5D:72:E0:D7:B8:38:F9:49:AB:C7:A4:F0"]
        }
    },
    {
        "relation": ["delegate_permission/common.handle_all_urls"],
        "target": {
            "namespace": "android_app",
            "package_name": "hm.orz.chaos114.android.tumekyouen",
            "sha256_cert_fingerprints":
            ["6E:5A:DB:69:B4:C3:28:AC:DA:7F:00:A7:4D:5D:CD:F9:ED:87:0B:5D:D2:71:C5:89:64:DF:13:38:5A:5B:B8:A2"]
        }
    }
]
```

まず、公式情報にあるサンプルをコピーし、debug用のpackage名と本番用のpackage名を適用しました。
その後、各keystoreに対して、 `keytool -list -v -keystore app/cert/debug.keystore` などを実行し、 `SHA256` の結果を貼り付けました。

これを、 https://my-android-server.appspot.com/.well-known/assetlinks.json としてアクセスできるようにします。

今回はサーバサイドがGAE上で動いているので、 https://github.com/noboru-i/kyouen-python/pull/18/files のように設定しました。
（`.well-known/assetlinks.json` というファイルをそのまま転送しようとしたら `Directory matches ignore regex.` と言われてアップロードできなかったので、物理的には `well-known/assetlinks.json` に変更して、app.yamlの設定でアクセス先を変えました。）

https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://my-android-server.appspot.com&relation=delegate_permission/common.handle_all_urls にアクセスし、

```
"debugString": "********************* ERRORS *********************\nNone!...
```

と返却されることを確認しました。

これで、再度、

```
adb shell am start -W -a android.intent.action.VIEW -d "http://my-android-server.appspot.com/html/list.html?page_no=1\&open=2"
```

を実行すると、今まではダイアログでアプリ選択が出ていたのですが、今回は何も聞かれずに対象のアプリ画面が表示されました。

## 動作確認

上記の対応を行い、Play Storeにアプリを公開しました。

https://play.google.com/store/apps/details?id=hm.orz.chaos114.android.tumekyouen

https://twitter.com/noboru_i/status/1028173855754346496
このツイートをTwitterアプリで開き、URLをタップすると、ダイアログが表示されずにステージ2の画面が表示されました。

![capture.gif](https://qiita-image-store.s3.amazonaws.com/0/8708/02121f28-33b4-6c15-36c7-2debec65e022.gif)