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