OPPOのランチャーでJetpack Glanceで実装したウィジェットの表示がおかしい問題

OPPOのランチャーにJetpack Glanceで実装したウィジェットを設置すると表示がおかしい問題にぶつかりました。Novaランチャーなど、3rdパーティのランチャーアプリをインストールすれば問題無く動作するので、端末に原因があるという訳でもなく、プリインのランチャーの挙動が原因のようです。

私が持っている端末はOPPO Pad Airでが、スマホも挙動が同様なのでこちらで検証してみます。
デフォルトのランチャーアプリは以下です。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/d6c29b00-cded-2977-15ad-a5e3b534120c.png)

Jetpack Glanceで以下のような表示をしてみます。通知されたサイズの縦横半分の矩形を中心に表示して分かりやすくしています。

```kotlin
class MyWidget : GlanceAppWidget() {
    override val sizeMode = SizeMode.Exact

    private val colors = listOf(Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Magenta, Color.Cyan)

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        val appWidgetId = GlanceAppWidgetManager(context).getAppWidgetId(id)
        val color = colors[appWidgetId % colors.size]

        provideContent {
            Box(
                modifier = GlanceModifier.background(color)
                    .fillMaxSize()
                    .appWidgetBackground(),
                contentAlignment = Alignment.Center,
            ) {
                val size = LocalSize.current
                Text(
                    text = "${size.width.value.toInt()} x ${size.height.value.toInt()}",
                    modifier = GlanceModifier.size(size.width / 2, size.height / 2)
                        .background(GlanceTheme.colors.widgetBackground)
                )
            }
        }
    }
}
```

期待値としてはこういう表示です。（Pixel Launcher @ API 34 AVD）

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/c3d2cf51-0b18-ab83-abbc-77ff0b37cc99.png)


では、OPPO端末に表示すると

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/f9127538-097f-867e-0721-4e866ae48e42.png)

**全くわけが分からんぞ**

# 調査

何が起こっているのか調べて見る。
サイズがおかしいので、AppWidgetOptionsの情報に問題がありそう。ということで調べて見ます。

Widgetのサイズ情報はちょっとややこしいですが
`OPTION_APPWIDGET_MIN_WIDTH`
`OPTION_APPWIDGET_MAX_WIDTH`
`OPTION_APPWIDGET_MIN_HEIGHT`
`OPTION_APPWIDGET_MAX_HEIGHT`
の4つの値が通知されて、
MIN_WIDTH/MAX_HEIGHTがPortraitのサイズ
MAX_WIDTH/MIN_HEIGHTがLandscapeのサイズ
ですね。（心の声：もっと分かりやすい仕様にしてほしい）
API 31からは`OPTION_APPWIDGET_SIZES`が追加され、`SizeF`のArrayListの形で受け取れる場合もあります。
この場合、最初がPortrait、Landscapeの順で返ってくるようです。

以下のコードでこれらの値がどうなっているか確認してみます。

```kotlin
val sizes = BundleCompat.getParcelableArrayList(options, AppWidgetManager.OPTION_APPWIDGET_SIZES, SizeF::class.java)
val minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 0)
val maxWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 0)
val minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 0)
val maxHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 0)
sizes?.forEach {
    Log.e("XXXX", "Sizes $appWidgetId Size: ${it.width} x ${it.height}")
}
Log.e("XXXX", "Sizes $appWidgetId P: $minWidth x $maxHeight L: $maxWidth x $minHeight")
```

### Pixel Launcher @ API 34 AVD

```
Sizes 5 Size: 176.0 x 224.0
Sizes 5 Size: 385.5238 x 126.47619
Sizes 5 P: 176 x 224 L: 385 x 126
```

想定通りの値で、実際の表示とも合っているようですね。

|portrait|landscape|
|--|--|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/4bc8b28b-d8bc-5b27-78d9-02f1dc8c88e2.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/61f60459-0a86-82c0-ffe4-48128c0c6c25.png)|

### OPPO Pad Air

```
Sizes 15 Size: 193.14285 x 274.2857
Sizes 15 Size: 193.14285 x 267.42856
Sizes 15 Size: 346.2857 x 91.42857
Sizes 15 P: 193 x 274 L: 346 x 91

Sizes 15 Size: 100.57143 x 369.14285
Sizes 15 Size: 100.57143 x 362.2857
Sizes 15 Size: 252.57143 x 186.28572
Sizes 15 P: 100 x 369 L: 252 x 186
```

`OPTION_APPWIDGET_SIZES`に3つの値が入っている。その上画面が回転すると異なる値が通知されました。
（さらに使っているうちに4つになったり、挙動が謎です）

|portrait|landscape|
|--|--|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/1d6719c4-cf8f-fb28-3ce1-62e18fccdd61.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/8b3c303b-e2ed-f37a-7d89-3f6db353a717.png)|

**全然違うじゃん！！**

# 何が起こっているのか

全く意味が分からないので、可能な範囲で推測してみましょう。

Glanceが扱うサイズ情報は

https://android.googlesource.com/platform/frameworks/support/+/androidx-main/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetUtils.kt

にあるように、`OPTION_APPWIDGET_SIZES`があれば、それを、無ければ4つの`OPTION_APPWIDGET_XXXX`からサイズを取り出すようです。
この端末ではサイズ変更時に3回、`OPTION_APPWIDGET_SIZES`に格納された順のサイズでrecomposeが発生していました。
表示されている数値からすると、Portraitのときに、3つ目のサイズ、landscapeの時に2つ目のサイズで作ったレイアウトが表示されていますが、これも決まっているわけでもなく、操作する度に変わったり安定しません。
API 31以降であれば、sizeMapでRemoteViewsが返却されていると思いますが、そのあとどうなってこのような結果になっているのかまでは追い切れませんでした。

https://developer.android.com/reference/android/widget/RemoteViews#RemoteViews(java.util.Map%3Candroid.util.SizeF,android.widget.RemoteViews%3E)

## 改善策を考える

**※このこの方法では別の端末だとうまくいかないことが分かりました。よい方法が見つかったら更新します**

`OPTION_APPWIDGET_XXXX`から推測すると、1つ目と3つ目の値がそれっぽいですね。
Portraitの場合に1つ目のサイズ、Landscapeの場合に、3つ目のサイズを信用すると、そこそこ一致しそうな気がします。
かなりやっつけ仕事でrunBlockingしちゃってますが、以下のようにしてみるとどうでしょうか？

```kotlin
provideContent {
    val sizes = runBlocking { manager.getAppWidgetSizes(id) }
    val size = if (sizes.size >= 3) {
        if (context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
            sizes[0]
        } else {
            sizes[2]
        }
    } else {
        LocalSize.current
    }
    Box(
        modifier = GlanceModifier.background(color)
            .fillMaxSize()
            .appWidgetBackground(),
        contentAlignment = Alignment.Center,
    ) {
        Text(
            text = "${size.width.value.toInt()} x ${size.height.value.toInt()}",
            modifier = GlanceModifier.size(size.width / 2, size.height / 2)
                .background(GlanceTheme.colors.widgetBackground)
        )
    }
}
```

それっぽい表示になりましたね

|portrait|landscape|
|--|--|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/d80c8e19-1457-e5d1-8c37-ae8ee228b7bb.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/d85eef3c-1ac0-7a9c-bcd8-56c5ffdc7749.png)|

いや、だからといって、うーん

# ランチャーを識別する

改善策がなくはない、ですが、汎用的な処理にはならないので、やるとしても発動条件を限定したいですね。
OPPOのランチャーがインストールされていることを検出してみましょうか。
ところで、このランチャーのパッケージ名は何かなーと……

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/96fcad56-e663-e026-293d-b80a568897ea.png)

おおーっと、いけません。「com.android.launcher」です。
↓デスヤン。

https://android.googlesource.com/platform/packages/apps/Launcher/

AOSPベースにカスタマイズしたのでしょうが、パッケージ名が……
今は使われるとしてもLauncher3だろうから、このパッケージ名で識別できないことは無いんだろうけど、やりたくない感じがしますね

それでもやる場合、ウィジェットの貼り付け先を調べる方法はおそらくないでしょうから、
以下のようにデフォルトほホームアプリが何かを調べて判定でしょうか

```kotlin
val intent = Intent(Intent.ACTION_MAIN)
    .addCategory(Intent.CATEGORY_HOME)
val resolveInfo = context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
val isOppoLauncher = resolveInfo?.activityInfo?.packageName == "com.android.launcher"
```

----

正直、よい解決策が見つからず苦悩してますってだけの記事になってしまっていますが、エレガントな解決策をお持ちの方いらっしゃいましたら教えていただけると幸いです。

以上です。