Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd10d9b5 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Move widget view inflation to the main thread" into main

parents 1ce14b17 a4b09144
Loading
Loading
Loading
Loading
+0 −19
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
@@ -61,28 +60,10 @@ class CommunalAppWidgetHostTest : SysuiTestCase() {
                context = context,
                backgroundScope = kosmos.applicationCoroutineScope,
                hostId = 116,
                interactionHandler = mock(),
                looper = testableLooper.looper,
                logBuffer = logcatLogBuffer("CommunalAppWidgetHostTest"),
            )
    }

    @Test
    fun createViewForCommunal_returnCommunalAppWidgetView() {
        val appWidgetId = 789
        val view =
            underTest.createViewForCommunal(
                context = context,
                appWidgetId = appWidgetId,
                appWidget = null
            )
        testableLooper.processAllMessages()

        assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
        assertThat(view).isNotNull()
        assertThat(view.appWidgetId).isEqualTo(appWidgetId)
    }

    @Test
    fun appWidgetIdToRemove_emit() =
        testScope.runTest {
+18 −12
Original line number Diff line number Diff line
@@ -21,8 +21,10 @@ import android.os.Bundle
import android.util.SizeF
import com.android.app.tracing.coroutines.withContext
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.qualifiers.UiBackground
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
@@ -33,6 +35,8 @@ class WidgetViewFactory
constructor(
    @UiBackground private val uiBgContext: CoroutineContext,
    private val appWidgetHost: CommunalAppWidgetHost,
    private val interactionHandler: WidgetInteractionHandler,
    private val listenerFactory: AppWidgetHostListenerDelegate.Factory,
) {
    suspend fun createWidget(
        context: Context,
@@ -40,10 +44,12 @@ constructor(
        size: SizeF,
    ): CommunalAppWidgetHostView =
        withContext("$TAG#createWidget", uiBgContext) {
            appWidgetHost
                .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
                .apply {
                    updateAppWidgetSize(
            val view = CommunalAppWidgetHostView(context, interactionHandler)
            view.setAppWidget(model.appWidgetId, model.providerInfo)
            // Instead of setting the view as the listener directly, we wrap the view in a delegate
            // which ensures the callbacks always get called on the main thread.
            appWidgetHost.setListener(model.appWidgetId, listenerFactory.create(view))
            view.updateAppWidgetSize(
                /* newOptions = */ Bundle(),
                /* minWidth = */ size.width.toInt(),
                /* minHeight = */ size.height.toInt(),
@@ -51,7 +57,7 @@ constructor(
                /* maxHeight = */ size.height.toInt(),
                /* ignorePadding = */ true,
            )
                }
            view
        }

    private companion object {
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.communal.widgets

import android.appwidget.AppWidgetHost.AppWidgetHostListener
import android.appwidget.AppWidgetProviderInfo
import android.widget.RemoteViews
import com.android.systemui.dagger.qualifiers.Main
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.concurrent.Executor

/**
 * Wrapper for an [AppWidgetHostListener] to ensure the callbacks are executed on the main thread.
 */
class AppWidgetHostListenerDelegate
@AssistedInject
constructor(
    @Main private val mainExecutor: Executor,
    @Assisted private val listener: AppWidgetHostListener,
) : AppWidgetHostListener {

    @AssistedFactory
    interface Factory {
        fun create(listener: AppWidgetHostListener): AppWidgetHostListenerDelegate
    }

    override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
        mainExecutor.execute { listener.onUpdateProviderInfo(appWidget) }
    }

    override fun updateAppWidget(views: RemoteViews?) {
        mainExecutor.execute { listener.updateAppWidget(views) }
    }

    override fun onViewDataChanged(viewId: Int) {
        mainExecutor.execute { listener.onViewDataChanged(viewId) }
    }
}
+1 −31
Original line number Diff line number Diff line
@@ -17,11 +17,7 @@
package com.android.systemui.communal.widgets

import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetHostView
import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.os.Looper
import android.widget.RemoteViews
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import javax.annotation.concurrent.GuardedBy
@@ -36,11 +32,8 @@ class CommunalAppWidgetHost(
    context: Context,
    private val backgroundScope: CoroutineScope,
    hostId: Int,
    private val interactionHandler: RemoteViews.InteractionHandler,
    looper: Looper,
    logBuffer: LogBuffer,
) : AppWidgetHost(context, hostId, interactionHandler, looper) {

) : AppWidgetHost(context, hostId) {
    private val logger = Logger(logBuffer, TAG)

    private val _appWidgetIdToRemove = MutableSharedFlow<Int>()
@@ -50,29 +43,6 @@ class CommunalAppWidgetHost(

    @GuardedBy("observers") private val observers = mutableSetOf<Observer>()

    override fun onCreateView(
        context: Context,
        appWidgetId: Int,
        appWidget: AppWidgetProviderInfo?
    ): AppWidgetHostView {
        return CommunalAppWidgetHostView(context, interactionHandler)
    }

    /**
     * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as
     * `createView`. The only difference is that the returned value will be casted to
     * [CommunalAppWidgetHostView].
     */
    fun createViewForCommunal(
        context: Context?,
        appWidgetId: Int,
        appWidget: AppWidgetProviderInfo?
    ): CommunalAppWidgetHostView {
        // `createView` internally calls `onCreateView` to create the view. We cannot override
        // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
        return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
    }

    override fun onAppWidgetRemoved(appWidgetId: Int) {
        backgroundScope.launch {
            logger.i({ "App widget removed from system: $int1" }) { int1 = appWidgetId }
+1 −11
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ package com.android.systemui.communal.widgets
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.res.Resources
import android.os.Looper
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -46,18 +45,9 @@ interface CommunalWidgetModule {
        fun provideCommunalAppWidgetHost(
            @Application context: Context,
            @Background backgroundScope: CoroutineScope,
            interactionHandler: WidgetInteractionHandler,
            @Main looper: Looper,
            @CommunalLog logBuffer: LogBuffer,
        ): CommunalAppWidgetHost {
            return CommunalAppWidgetHost(
                context,
                backgroundScope,
                APP_WIDGET_HOST_ID,
                interactionHandler,
                looper,
                logBuffer,
            )
            return CommunalAppWidgetHost(context, backgroundScope, APP_WIDGET_HOST_ID, logBuffer)
        }

        @SysUISingleton