Files
lawnchair/compose/features/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapperImpl.kt
Shamali Patwa a1c4ff596d Move host constraint builder logic to the concrete wrapper implementation.
Fix build.


Bug: 408283627
Flag: EXEMPT independent module
Test: See demo
Change-Id: Ic15eb61eaa76ee9eeb1680dfe90cb89442f7d159
2025-06-02 22:34:00 -07:00

249 lines
10 KiB
Kotlin

/*
* Copyright (C) 2025 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.launcher3.widgetpicker
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalView
import com.android.launcher3.Launcher
import com.android.launcher3.R
import com.android.launcher3.compose.ComposeFacade
import com.android.launcher3.compose.core.widgetpicker.WidgetPickerComposeWrapper
import com.android.launcher3.concurrent.annotations.BackgroundContext
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.util.ApiWrapper
import com.android.launcher3.widgetpicker.WidgetPickerConfig
import com.android.launcher3.widgetpicker.WidgetPickerConfig.Companion.EXTRA_IS_PENDING_WIDGET_DRAG
import com.android.launcher3.widgetpicker.data.repository.WidgetAppIconsRepository
import com.android.launcher3.widgetpicker.data.repository.WidgetUsersRepository
import com.android.launcher3.widgetpicker.data.repository.WidgetsRepository
import com.android.launcher3.widgetpicker.listeners.WidgetPickerAddItemListener
import com.android.launcher3.widgetpicker.listeners.WidgetPickerDragItemListener
import com.android.launcher3.widgetpicker.shared.model.HostConstraint
import com.android.launcher3.widgetpicker.shared.model.WidgetHostInfo
import com.android.launcher3.widgetpicker.ui.WidgetInteractionInfo
import com.android.launcher3.widgetpicker.ui.WidgetPickerEventListeners
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Provider
import kotlin.coroutines.CoroutineContext
/**
* An helper that bootstraps widget picker UI (from [WidgetPickerComponent]) in to
* [WidgetPickerActivity] when compose is available and widget picker refactor flags are on.
*
* Sets up the bindings necessary for widget picker component.
*/
class WidgetPickerComposeWrapperImpl @Inject constructor(
private val widgetPickerComponentProvider: Provider<WidgetPickerComponent.Factory>,
private val widgetsRepository: WidgetsRepository,
private val widgetUsersRepository: WidgetUsersRepository,
private val widgetAppIconsRepository: WidgetAppIconsRepository,
@BackgroundContext
private val backgroundContext: CoroutineContext,
@ApplicationContext
private val appContext: Context,
private val apiWrapper: ApiWrapper,
) : WidgetPickerComposeWrapper {
override fun showAllWidgets(
activity: WidgetPickerActivity,
widgetPickerConfig: WidgetPickerConfig
) {
val widgetPickerComponent = newWidgetPickerComponent(widgetPickerConfig)
val callbacks = activity.buildEventListeners(widgetPickerConfig, apiWrapper)
val fullWidgetsCatalog = widgetPickerComponent.getFullWidgetsCatalog()
val composeView = ComposeFacade.initComposeView(activity.asContext()) as ComposeView
composeView.apply {
setContent {
val scope = rememberCoroutineScope()
val view = LocalView.current
MaterialTheme { // TODO(b/408283627): Use launcher theme.
val eventListeners = remember { callbacks }
fullWidgetsCatalog.Content(eventListeners)
}
DisposableEffect(view) {
scope.launch {
initializeRepositories()
}
onDispose {
cleanUpRepositories()
}
}
}
}
checkNotNull(activity.dragLayer).addView(composeView)
}
private fun newWidgetPickerComponent(
widgetPickerConfig: WidgetPickerConfig
): WidgetPickerComponent {
return widgetPickerComponentProvider.get()
.build(
widgetsRepository = widgetsRepository,
widgetUsersRepository = widgetUsersRepository,
widgetAppIconsRepository = widgetAppIconsRepository,
widgetHostInfo = WidgetHostInfo(
title = widgetPickerConfig.title
?: appContext.resources.getString(R.string.widget_button_text),
description = widgetPickerConfig.description,
constraints = widgetPickerConfig.asHostConstraints(),
showDragShadow = !widgetPickerConfig.isForHomeScreen
),
backgroundContext = backgroundContext
)
}
private fun initializeRepositories() {
widgetsRepository.initialize()
widgetUsersRepository.initialize()
widgetAppIconsRepository.initialize()
}
private fun cleanUpRepositories() {
widgetsRepository.cleanUp()
widgetUsersRepository.cleanUp()
widgetAppIconsRepository.cleanUp()
}
companion object {
private const val HOME_SCREEN_WIDGET_INTERACTION_REASON_STRING =
"WidgetPickerActivity.OnWidgetInteraction"
private fun WidgetPickerActivity.buildEventListeners(
widgetPickerConfig: WidgetPickerConfig,
apiWrapper: ApiWrapper
) = object : WidgetPickerEventListeners {
override fun onClose() {
finish()
}
override fun onWidgetInteraction(widgetInteractionInfo: WidgetInteractionInfo) {
if (widgetPickerConfig.isForHomeScreen) {
handleWidgetInteractionForHomeScreen(widgetInteractionInfo, apiWrapper)
} else {
handleWidgetInteractionForExternalHost(widgetInteractionInfo)
}
}
}
/**
* Handles communication with the home screen about the "add" and "drag" interactions on
* widgets within widget picker.
*
* For home screen, we register a listener that is called back when home screen is shown;
* - WidgetPickerDragItemListener: bootstraps the drag helper that displays the shadow and
* handles the drag until completion.
* - WidgetPickerAddItemListener: once launcher is shown, triggers the flow to add the
* widget to workspace.
*/
private fun WidgetPickerActivity.handleWidgetInteractionForHomeScreen(
interactionInfo: WidgetInteractionInfo,
apiWrapper: ApiWrapper
) {
val interactionListener = when (interactionInfo) {
is WidgetInteractionInfo.WidgetDragInfo ->
WidgetPickerDragItemListener(
mimeType = interactionInfo.mimeType,
appWidgetProviderInfo = interactionInfo.providerInfo,
widgetPreview = interactionInfo.previewInfo,
previewRect = interactionInfo.bounds,
previewWidth = interactionInfo.widthPx
)
is WidgetInteractionInfo.WidgetAddInfo ->
WidgetPickerAddItemListener(interactionInfo.providerInfo)
}
Launcher.ACTIVITY_TRACKER.registerCallback(
interactionListener,
HOME_SCREEN_WIDGET_INTERACTION_REASON_STRING
)
startActivity(
/*intent=*/ Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(packageName)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
/*options=*/ apiWrapper.createFadeOutAnimOptions().toBundle()
)
finish()
}
/**
* Handles communication with the external host about the "add" and "drag" interactions on
* widgets within widget picker.
*
* - In case of drag and drop, finishes the activity with result indicating that there is a
* pending drag [EXTRA_IS_PENDING_WIDGET_DRAG] (that would contain the widget info as part
* of clip data) that the host should be handling.
* - In case of add, finishes the activity with result containing extra information about
* the widget being added (namely [Intent.EXTRA_COMPONENT_NAME] and [Intent.EXTRA_USER].
*/
private fun WidgetPickerActivity.handleWidgetInteractionForExternalHost(
widgetInteractionInfo: WidgetInteractionInfo,
) {
when (widgetInteractionInfo) {
is WidgetInteractionInfo.WidgetDragInfo ->
setResult(
RESULT_OK,
Intent().putExtra(EXTRA_IS_PENDING_WIDGET_DRAG, true)
)
is WidgetInteractionInfo.WidgetAddInfo -> {
val providerInfo = widgetInteractionInfo.providerInfo
setResult(
RESULT_OK, Intent()
.putExtra(Intent.EXTRA_COMPONENT_NAME, providerInfo.provider)
.putExtra(Intent.EXTRA_USER, providerInfo.profile)
)
}
}
finish()
}
/** Builds the host constraints to provide to the widget picker module. */
fun WidgetPickerConfig.asHostConstraints() =
buildList {
if (filteredUsers.isNotEmpty()) {
add(HostConstraint.HostUserConstraint(filteredUsers))
}
if (categoryInclusionFilter != 0
|| categoryExclusionFilter != 0
) {
add(
HostConstraint.HostCategoryConstraint(
categoryInclusionMask = categoryInclusionFilter,
categoryExclusionMask = categoryExclusionFilter,
)
)
}
}
}
}