Chrome Custom Tabsに対応しているChromeでないブラウザアプリたち

FirefoxやSamsung Internet BrowserがChrome Custom Tabsに対応するとかいう話を聞きました。

実際のところChrome Custom Tabsって結構シンプルなプロトコルっぽいですよね。他にも対応しているブラウザアプリがあってもおかしくない。

そんな訳で、どのブラウザが対応しているのかを調べる簡単なアプリを作ってみました。

ソースコードはこちらになります。
https://github.com/ohmae/custom-tabs-sample

※あくまでChrome Custom Tabsとして動かすためのIntent投げているだけです。bindしたりするのは別の記事等に譲ります。
※Chromeじゃない場合**Chrome** Custom Tabsとは呼ばないのではという気もしますが、おいておきます。

# 対応アプリの調べ方

Chrome Custom Tabsのサンプルコードは以下にあります。
https://github.com/GoogleChrome/custom-tabs-client

対応パッケージをサーチしているのは`CustomTabsHelper#getPackageNameToUse`です。
https://github.com/GoogleChrome/custom-tabs-client/blob/master/shared/src/main/java/org/chromium/customtabsclient/shared/CustomTabsHelper.java

やっていることをざっくり書くと
- `http://www.example.com`に反応するパッケージ（≒ブラウザアプリ）を調べる
- その中から、`android.support.customtabs.action.CustomTabsService`というActionに反応するサービスを持っているもののみを取り出す
- 一つだけならそれを採用
- 複数ある場合、`http://www.example.com`に紐付いたアプリ（≒デフォルトブラウザ）があればそれを、なければChromeのstable → beta → dev → localの優先度で採用

という感じです。Chrome Custom Tabsを使う場合、この処理を参考にして対応パッケージを取り出す処理を書くことになります。
~~（正直ブラウザーの中から調べるんじゃなくて、`android.support.customtabs.action.CustomTabsService`に反応するサービスをとりだすだけで良さそうですが……）~~
訂正：Chrome Custom TabsでもURLを開くIntentはACTION_VIEWにURL指定のものなので両方に対応している必要がありました。

## サンプルアプリ

今回は優先度をつけてどれか一つを取り出すのではなく、対応アプリのリストを全部取り出します。
元の処理をちょっとシンプルにして以下のようにします。

`http://www.example.com`に反応するパッケージ（≒ブラウザアプリ）のパッケージ名をSetとして取り出す。

```kotlin
private fun getBrowserPackages(pm: PackageManager): Set<String> {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com/"))
    intent.addCategory(Intent.CATEGORY_BROWSABLE)
    val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        PackageManager.MATCH_ALL
    } else {
        0
    }
    return pm.queryIntentActivities(intent, flags)
        .mapNotNull { it.activityInfo?.packageName }
        .toSet()
}
```

※上記でAndroid M以上の場合、`queryIntentActivities`の第二引数に`PackageManager.MATCH_ALL`を指定しないとデフォルトブラウザが設定されている場合、そのパッケージしか返ってこないので注意。

`android.support.customtabs.action.CustomTabsService`に反応するサービスを持つアプリかつブラウザアプリのものを取り出すだけ。
表示するためにアイコンやらアプリ名やらを取り出しておきます。

```kotlin
private fun createPackageList(): List<PackageInfo> {
    val pm = packageManager
    val browsers = getBrowserPackages(pm)
    return pm.queryIntentServices(Intent(ACTION_CUSTOM_TABS_CONNECTION), 0)
        .mapNotNull { it.serviceInfo }
        .filter { browsers.contains(it.packageName) }
        .map {
            PackageInfo(
                it.loadIcon(pm),
                it.loadLabel(pm).toString(),
                it.packageName
            )
        }
}
private data class PackageInfo(
    val drawable: Drawable?,
    val label: String,
    val packageName: String
)
```

そして、これをRecyclerViewに表示させます。
タップするとそのアプリを使ったCustomTabsでEditTextに書かれているURLを開くようにしました。

サンプルではMainActivity一つに全部収まっています。

https://github.com/ohmae/custom-tabs-sample/blob/master/app/src/main/java/net/mm2d/customtabssample/MainActivity.kt

## 結果

Google Playでブラウザとかで検索していろんなブラウザアプリをインストールしまくった後に実行するとこんな感じになりました。

| | |
|:-:|:-:|
|![device-2018-10-14-155108.png](https://qiita-image-store.s3.amazonaws.com/0/155171/ac229da2-1407-48e4-3cb4-477b7d237f2c.png)|![device-2018-10-14-155128.png](https://qiita-image-store.s3.amazonaws.com/0/155171/765382a4-32c5-d85c-87b8-8b6199001466.png)|

見た目

|Chrome|Edge|
|:-:|:-:|
|![device-2018-10-14-155632.png](https://qiita-image-store.s3.amazonaws.com/0/155171/40537697-b92c-02e5-4bf6-a004436f5109.png)|![device-2018-10-14-155702.png](https://qiita-image-store.s3.amazonaws.com/0/155171/198eb8a7-075d-5924-1f49-f70714cf2d8c.png)|

|Yandex|Firefox|
|:-:|:-:|
|![device-2018-10-14-155722.png](https://qiita-image-store.s3.amazonaws.com/0/155171/ee322838-d536-9073-34b7-7f9fcc758128.png)|![device-2018-10-14-155758.png](https://qiita-image-store.s3.amazonaws.com/0/155171/0718feed-4d37-a145-0916-ee0633f2a65f.png)|

# まとめ

対応していると分かったアプリ一覧です。

- Chrome (stable/dev/beta/canary)
- Firefox (stable/beta/nightly/focus)
- Yandex (stable/beta/alpha)
- Edge
- Samsungブラウザ
- Kiwi Browser
- Brave

ブラウザアプリはすごくたくさんあるのでぱっと検索して調べられたものだけです。
また、現在対応していないものもそのうち対応されるかもしれません。

Chrome Custom Tabsはかなり多くのブラウザアプリが対応していました。
せいぜい2～3個見つかればいいかなとおもって始めましたが、こんなにたくさんあるとは想定外。
ブラウザアプリを作るなら対応必須かもしれません。

一方、利用するアプリ側は、サンプルコードそのままで実装するとChrome以外が使われる可能性があるということを認識しておく必要があります。
どうしてもChromeでないと困る場合（どんな場合か思いつきませんが）は、利用パッケージの選択ロジックを変更して、Chrome以外が使われないようにしておかないといけないですね。
逆に特段の理由がなければ、ユーザーがデフォルトに設定しているブラウザが使われるというのは、利便性の面で最もよい選択となるアルゴリズムになっていると思います。

以上です。