Partial Custom Tabを使ってブラウザを良い感じに使う

みなさん、こんにちは。
AndroidでもiPhoneのように良い感じでブラウザが開けるようになったようです。
公式の記事は以下になります。
https://android-developers.googleblog.com/2023/02/bringing-best-of-chrome-to-android-developers-and-users.html

どのようなものかと言うと、今までもCustomTab自体はありアプリ内で別ページで開きながら、Chromeとしての機能を使えるようになっていました。
アプリでのWebViewだと使えなかったりするパスワードマネージャ等も良い感じに使えてとても便利でした。
しかし、これだと画面が別で開いてしまうため、元のアプリと同時に使うことができませんでした。
しかし、それができるようになった!!と言うことで実際に少し試してみました。

これらの機能は`androidx.browser:browser:1.5.0`以上で提供されています。
また、ChromeのVersionが45以上でないといけないらしいです。
実装に必要なものに関しては、以下の公式サイトに書いてあります。
https://developer.chrome.com/docs/android/custom-tabs/

では実際にやってみます。

# 実装

調べてみると、`CustomTabIntent`で`setInitialActivityHeightPx`を設定すればいけるらしい。
そこで、適当な箇所のsetOnClickListnerで以下で実装してみたが、動かない!!
いや、CustomTab自体は開くが、PartialCustomTabになっていない。
動かない理由は正直よくわかってないです。
詳しい方教えてください。

```Kotlin
val customTabsIntent = CustomTabsIntent.Builder()
   .setInitialActivityHeightPx(600)
   .build()
customTabsIntent.launchUrl(this, Uri.parse("https://developer.android.com/"))
```

公式を見ると、二種類の実装を推奨しているらしい。
[`customTabsSession`を用いた実装](https://developer.chrome.com/docs/android/custom-tabs/integration-guide/#launch-a-partial-custom-tab-with-an-existing-session)、もしくは[`startActivityForResult`を使った実装](https://developer.chrome.com/docs/android/custom-tabs/integration-guide/#launch-a-partial-custom-tab-via-startactivityforresult)があるらしい。
とりあえず両方実装してみました。

## CustomTabsSessionを用いた実装

CustomTab周りを扱うためのクラスを作成
```Kotlin
class CustomTabActivityHelper (private val context: Context) {

    private var customTabsClient: CustomTabsClient? = null
    private var customTabsSession: CustomTabsSession? = null

    fun openCustomTabWithSession(url: String) {
        val customTabsIntent = CustomTabsIntent.Builder(customTabsSession)
            .setInitialActivityHeightPx(2000)
            .build()
        val packageName = CustomTabsClient.getPackageName(context, null)
        packageName?.let {
            customTabsIntent.intent.setPackage(packageName)
            customTabsIntent.launchUrl(context, Uri.parse(url))
        } ?: run {
            context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
        }
    }

    fun bindCustomTabsService() {
        val packageName = CustomTabsClient.getPackageName(context, null) ?: return
        val connection = object : CustomTabsServiceConnection() {
            override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
                customTabsClient = client
                // Warm up the browser process
                client.warmup(0L)
                // Create a new session
                createNewSession()
            }

            override fun onServiceDisconnected(name: ComponentName) {
                customTabsClient = null
            }
        }
        CustomTabsClient.bindCustomTabsService(context, packageName, connection)
    }

    private fun createNewSession() {
        customTabsClient?.let { client ->
            val callback = object : CustomTabsCallback() {
                // 終了時に呼び出されるコールバック
                // 必要であれば、実装してください。
            }
            customTabsSession = client.newSession(callback)
        }
    }

    fun unbindCustomTabsService() {
        customTabsClient = null
    }
}
```

そして、ActivityのOnCreateで以下を実行

```Kotlin
var customTabActivityHelper = CustomTabActivityHelper(this)
// Custom Tabs サービスへのバインドを開始
customTabActivityHelper.bindCustomTabsService()
```

あとは適当な箇所で`customTabActivityHelper.openCustomTabWithSession("https://google.com")`を実行すれば、動きました。
ちょっと面倒ですが、良い感じに取り回せるので良さそうです。



## StartActivityForResultを使った実装

こちらはActivityの`OnCreate`内でActivityResultLauncherを用いて実装します。
```Kotlin
        val activityResultLauncher: ActivityResultLauncher<String> =
            registerForActivityResult(object: ActivityResultContract<String, Int>() {
                override fun createIntent(
                    context: Context,
                    url: String
                ): Intent {
                    val builder = CustomTabsIntent.Builder()
                        .setInitialActivityHeightPx(500)
                    val customTabsIntent = builder.build().intent
                    customTabsIntent.data = Uri.parse(url)
                    return customTabsIntent
                }

                override fun parseResult(
                    resultCode: Int,
                    intent: Intent?
                ): Int {
                    return resultCode
                }
            }) { resultCode ->
                // 終了時に呼び出されるコールバック
                // 必要であれば実装してください。
            }

```
あとは適当な場所で `activityResultLauncher.launch("https://google.com")`
を実行すれば動きます。


# まとめ

とても便利で、なんで今までなかったんだ！！と言いたくなる機能でした。
実装として使い勝手が良さそうなのは、CustomTabsSessionを用いた実装かなと感じました。
ただ、ちょっと複雑になるので、特に実装を複雑にしないのであれば、ActivityForResultも楽で良いです。

### 実装の参考にしたサイト
https://github.com/GoogleChrome/android-browser-helper

https://speakerdeck.com/horie23/custom-tabsnogao-sawobian-geng-siyou

https://tech.yappli.io/entry/android-customtabs-bottomsheet