みんからきりまで

きりみんです。

Gboardに「ステッカー」という機能があることにようやく気がついたのでTwitterとかでLINEスタンプみたいなことが出来るアプリを試作してみた

こんにちは、VTuberきりみんちゃんのマネージャーをしているきりみんという者です。

LINEスタンプ、いいですよね。
好きなキャラクターのLINEスタンプが発売されるとつい無条件で買ってしまいますよね。
ちなみに使う機会はほぼありません。

LINEスタンプみたいな機能がTwitterにもあったら、ユーザーも合法的にキャラクターの画像を貼れるしTwitterも版権元も収益化ができてみんな幸せになりそうなのになぁ、と思っていたら、Gboardにステッカーという機能が存在することに気が付きました。

みなさんはGboardにこんなgifアニメステッカーを簡単に貼れる機能がついているのを知っていましたか?
ぼくは全く知りませんでした。

気になったので調べてみると、どうやら自分でもGboard用のステッカーアプリを作ることが出来るようです。
それなのにやはりあまり存在に気付いている人がいないのか、ストアで調べてもGboardステッカーアプリは数えるほどしかないっぽく、特に日本人向けのステッカーはほぼ存在していなさそうな感じでした。

もしかしてこれは一発当てるチャンスなのでは?と思い、実装方法を調べて実際にアプリをリリースしてみました。

実装

とりあえず公式の紹介記事をみる。が、情報が少なくてよくわからない。

android-developers.googleblog.com

Common builders for Indexable objects  |  Firebase

ググるといくつか解説しているエンジニアブログの記事があったのでそちらを参考にさせてもらいました。

spin.atomicobject.com

proandroiddev.com

基本的には上のエントリに実装方法が丁寧に書かれているので、そちらを読んでくださいという感じです。

ざっくりと実装コードを紹介します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="kirimin.me.emojires">

    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_title"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <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:scheme="mystickers"/>
                <data android:host="sticker"/>
            </intent-filter>
        </activity>
        <service android:name=".StickerIndexingService"
                 android:permission="android.permission.BIND_JOB_SERVICE"/>
    </application>

</manifest>
class StickerIndexingService : JobIntentService() {

    companion object {
        private const val UNIQUE_JOB_ID = 42
        private const val URI = "mystickers://sticker/"
        
        private const val IS_PART_OF = "isPartOf"
        private const val HAS_STICKER = "hasSticker"
        private const val STICKER = "Sticker"
        private const val STICKER_PACK = "StickerPack"
        private const val STICKER_PACK_NAME = "emojirespack"

        fun enqueueWork(context: Context) {
            enqueueWork(context, StickerIndexingService::class.java, UNIQUE_JOB_ID, Intent())
        }
    }

    override fun onHandleWork(intent: Intent) {
        val update = FirebaseAppIndex.getInstance().update(
            Indexable.Builder(STICKER_PACK)
                .setName(STICKER_PACK_NAME)
                .setImage(convertUrlFromDrawableResId(applicationContext, R.drawable.teetee))
                .setUrl(URI + "pack/0")
                .setDescription("A sticker pack of Nihongo")
                .put(
                    HAS_STICKER,
                    Indexable.Builder(STICKER)
                        .setName("Etsu")
                        .setImage(convertUrlFromDrawableResId(applicationContext, R.drawable.etsu))
                        .setUrl(URI + "etsu")
                        .setDescription("Etsu")
                        .put(IS_PART_OF, STICKER_PACK_NAME)
                        .build(),
                    Indexable.Builder(STICKER)
                        .setName("Gomenne")
                        .setImage(convertUrlFromDrawableResId(applicationContext, R.drawable.gomenne))
                        .setUrl(URI + "gomenne")
                        .setDescription("Gomenne")
                        .put(IS_PART_OF, STICKER_PACK_NAME)
                        .build(),
                   // 以下略
                )
                .build()
        )
    }

    private fun convertUrlFromDrawableResId(context: Context, drawableResId: Int): String {
        val sb = StringBuilder()
        sb.append(ContentResolver.SCHEME_ANDROID_RESOURCE)
        sb.append("://")
        sb.append(context.resources.getResourcePackageName(drawableResId))
        sb.append("/")
        sb.append(context.resources.getResourceTypeName(drawableResId))
        sb.append("/")
        sb.append(context.resources.getResourceEntryName(drawableResId))
        return Uri.parse(sb.toString()).toString()
    }
}

要約すると、FirebaseのAppIndexingの機能を使ってGboardに自作アプリのステッカーを紐付けるだけです。 そのためにJobIntentServiceを継承したServiceクラスを作り、FirebaseAppIndexにStickerとStickerPackを登録します。
実装中、ImageとUrlに何を入れればいいのかよく分からなくて迷ったのですが、Imageにはステッカー画像のURL(ローカルデータならResourceのURI)、UrlにはURL Scheme(AndroidManifestに設定したものと一致していれば何でもいい)を入れれば動きました。

とりあえずこれだけでこんな感じに動くアプリを作ることができました。

ストアにリリースしてます。 play.google.com

GitHubでもコードを公開しています。 github.com

今回はとりあえず試作なのでSlackみたいな文字スタンプを使いましたが、gifアニメにも対応しているのでLINEスタンプみたいな感じでいろいろなキャラクターのスタンプアプリを作れれば面白いんじゃないかなーと思いました。

というわけで(実装公開しちゃったけど)Gboardステッカーアプリ作りたいという案件お待ちしております。