1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-08-24

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

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

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

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)

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

全くわけが分からんぞ

調査

何が起こっているのか調べて見る。
サイズがおかしいので、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の順で返ってくるようです。

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

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

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

全然違うじゃん!!

何が起こっているのか

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

Glanceが扱うサイズ情報は

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

改善策を考える

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

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

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

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

ランチャーを識別する

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

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

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

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

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"

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

以上です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?