diff --git a/Android.bp b/Android.bp index daf87eb914..e8a4606e29 100644 --- a/Android.bp +++ b/Android.bp @@ -47,8 +47,8 @@ filegroup { filegroup { name: "launcher-compose-enabled-src", srcs: [ - "compose/facade/enabled/*.kt", - "compose/facade/core/*.kt", + "compose/facade/enabled/**/*.kt", + "compose/facade/core/**/*.kt", "compose/features/**/*.kt", ], } @@ -56,8 +56,8 @@ filegroup { filegroup { name: "launcher-compose-disabled-src", srcs: [ - "compose/facade/core/*.kt", - "compose/facade/disabled/*.kt", + "compose/facade/core/**/*.kt", + "compose/facade/disabled/**/*.kt", ], } diff --git a/compose/facade/core/widgetpicker/NoOpWidgetPickerModule.kt b/compose/facade/core/widgetpicker/NoOpWidgetPickerModule.kt new file mode 100644 index 0000000000..3cba82d54e --- /dev/null +++ b/compose/facade/core/widgetpicker/NoOpWidgetPickerModule.kt @@ -0,0 +1,30 @@ +/* + * 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.compose.core.widgetpicker + +import dagger.Binds +import dagger.Module + +/** + * A module that provides a no-op [WidgetPickerComposeWrapper] for dagger graph that doesn't + * involve widget picker e.g. launcher preview OR when compose is disabled via build flag. + */ +@Module +interface NoOpWidgetPickerModule { + @Binds + fun bindWidgetPickerWrapper(noOp: NoOpWidgetPickerComposeWrapper): WidgetPickerComposeWrapper +} \ No newline at end of file diff --git a/compose/facade/core/widgetpicker/WidgetPickerComposeWrapper.kt b/compose/facade/core/widgetpicker/WidgetPickerComposeWrapper.kt new file mode 100644 index 0000000000..452d8881e5 --- /dev/null +++ b/compose/facade/core/widgetpicker/WidgetPickerComposeWrapper.kt @@ -0,0 +1,38 @@ +/* + * 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.compose.core.widgetpicker + +import com.android.launcher3.widgetpicker.WidgetPickerActivity +import javax.inject.Inject + +/** + * A wrapper for widget picker activity that is responsible for displaying the compose based + * widget picker in [WidgetPickerActivity] when compose is enabled via build flag. + */ +interface WidgetPickerComposeWrapper { + fun showAllWidgets(activity: WidgetPickerActivity) +} + +/** + * A No-op [WidgetPickerComposeWrapper] that doesn't include widget picker in dagger graph that + * don't involve widget picker e.g. launcher preview OR when compose is disabled via build flag. + */ +class NoOpWidgetPickerComposeWrapper @Inject constructor() : WidgetPickerComposeWrapper { + override fun showAllWidgets(activity: WidgetPickerActivity) { + error("Widget picker with compose is not supported") + } +} diff --git a/compose/facade/disabled/widgetpicker/LauncherWidgetPickerModule.kt b/compose/facade/disabled/widgetpicker/LauncherWidgetPickerModule.kt new file mode 100644 index 0000000000..65635817e0 --- /dev/null +++ b/compose/facade/disabled/widgetpicker/LauncherWidgetPickerModule.kt @@ -0,0 +1,26 @@ +/* + * 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.compose.widgetpicker + +import com.android.launcher3.compose.core.widgetpicker.NoOpWidgetPickerModule +import dagger.Module + +/** + * A no-op module that is used when compose is disabled in launcher. + */ +@Module(includes = [NoOpWidgetPickerModule::class]) +class LauncherWidgetPickerModule diff --git a/compose/facade/enabled/widgetpicker/LauncherWidgetPickerModule.kt b/compose/facade/enabled/widgetpicker/LauncherWidgetPickerModule.kt new file mode 100644 index 0000000000..7694077760 --- /dev/null +++ b/compose/facade/enabled/widgetpicker/LauncherWidgetPickerModule.kt @@ -0,0 +1,63 @@ +/* + * 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.compose.widgetpicker + +import com.android.launcher3.compose.core.widgetpicker.WidgetPickerComposeWrapper +import com.android.launcher3.widgetpicker.WidgetPickerComponent +import com.android.launcher3.widgetpicker.WidgetPickerComposeWrapperImpl +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.datasource.ConfigResourceFeaturedWidgetsDataSource +import com.android.launcher3.widgetpicker.datasource.FeaturedWidgetsDataSource +import com.android.launcher3.widgetpicker.datasource.InMemoryWidgetSearchAlgorithm +import com.android.launcher3.widgetpicker.datasource.WidgetsSearchAlgorithm +import com.android.launcher3.widgetpicker.repository.WidgetsRepositoryImpl +import com.android.launcher3.widgetpicker.repository.WidgetAppIconsRepositoryImpl +import com.android.launcher3.widgetpicker.repository.WidgetUsersRepositoryImpl +import dagger.Binds +import dagger.Module + +/** + * A module that installs widget picker for launcher. + */ +@Module(subcomponents = [WidgetPickerComponent::class]) +interface LauncherWidgetPickerModule { + @Binds + fun bindWidgetPickerComposeWrapper( + impl: WidgetPickerComposeWrapperImpl + ): WidgetPickerComposeWrapper + + @Binds + fun bindWidgetUsersRepository(impl: WidgetUsersRepositoryImpl): WidgetUsersRepository + + @Binds + fun bindWidgetsRepository(impl: WidgetsRepositoryImpl): WidgetsRepository + + @Binds + fun bindWidgetAppIconsRepository(impl: WidgetAppIconsRepositoryImpl): WidgetAppIconsRepository + + @Binds + fun bindFeaturedWidgetsDataSource( + impl: ConfigResourceFeaturedWidgetsDataSource + ): FeaturedWidgetsDataSource + + @Binds + fun bindWidgetsSearchAlgorithm( + impl: InMemoryWidgetSearchAlgorithm + ): WidgetsSearchAlgorithm +} diff --git a/compose/features/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapperImpl.kt b/compose/features/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapperImpl.kt new file mode 100644 index 0000000000..d4d67edb55 --- /dev/null +++ b/compose/features/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapperImpl.kt @@ -0,0 +1,119 @@ +/* + * 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.content.Context +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.R +import com.android.launcher3.widgetpicker.WidgetPickerActivity +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.widgetpicker.WidgetPickerComponent +import com.android.launcher3.widgetpicker.WidgetPickerEventListeners +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.shared.model.WidgetHostInfo +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]. + * + * Sets up the bindings necessary for widget picker component. + */ +class WidgetPickerComposeWrapperImpl @Inject constructor( + private val widgetPickerComponentProvider: Provider, + private val widgetsRepository: WidgetsRepository, + private val widgetUsersRepository: WidgetUsersRepository, + private val widgetAppIconsRepository: WidgetAppIconsRepository, + @BackgroundContext + private val backgroundContext: CoroutineContext, + @ApplicationContext + private val appContext: Context, +) : WidgetPickerComposeWrapper { + override fun showAllWidgets( + activity: WidgetPickerActivity, + ) { + val widgetPickerComponent = newWidgetPickerComponent() + val callbacks = object : WidgetPickerEventListeners { + override fun onClose() { + activity.finish() + } + } + + 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() + } + } + } + } + + activity.dragLayer?.addView(composeView) + } + + private fun newWidgetPickerComponent(): WidgetPickerComponent = + widgetPickerComponentProvider.get() + .build( + widgetsRepository = widgetsRepository, + widgetUsersRepository = widgetUsersRepository, + widgetAppIconsRepository = widgetAppIconsRepository, + widgetHostInfo = WidgetHostInfo( + appContext.resources.getString(R.string.widget_button_text) + ), + backgroundContext = backgroundContext + ) + + private fun initializeRepositories() { + widgetsRepository.initialize() + widgetUsersRepository.initialize() + widgetAppIconsRepository.initialize() + } + + private fun cleanUpRepositories() { + widgetsRepository.cleanUp() + widgetUsersRepository.cleanUp() + widgetAppIconsRepository.cleanUp() + } +} diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java index de798fd9f7..54e532758b 100644 --- a/src/com/android/launcher3/dagger/LauncherAppModule.java +++ b/src/com/android/launcher3/dagger/LauncherAppModule.java @@ -16,6 +16,7 @@ package com.android.launcher3.dagger; +import com.android.launcher3.compose.widgetpicker.LauncherWidgetPickerModule; import com.android.launcher3.concurrent.ExecutorsModule; import com.android.launcher3.util.dagger.LauncherExecutorsModule; @@ -33,6 +34,7 @@ import dagger.Module; LauncherConcurrencyModule.class, ExecutorsModule.class, LauncherExecutorsModule.class, + LauncherWidgetPickerModule.class }, subcomponents = ActivityContextComponent.class) public class LauncherAppModule { } diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java index 10806f6083..ce3082c4ac 100644 --- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java +++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java @@ -25,6 +25,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.RemoveAnimationSettingsTracker; import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; +import com.android.launcher3.compose.core.widgetpicker.WidgetPickerComposeWrapper; import com.android.launcher3.folder.FolderNameSuggestionLoader; import com.android.launcher3.graphics.GridCustomizationsProxy; import com.android.launcher3.graphics.ThemeManager; @@ -100,7 +101,9 @@ public interface LauncherBaseAppComponent { InstantAppResolver getInstantAppResolver(); DumpManager getDumpManager(); StatsLogManager.StatsLogManagerFactory getStatsLogManagerFactory(); - ActivityContextComponent.Builder getActivityContextComponentBuilder(); + ActivityContextComponent.Builder getActivityContextComponentBuilder(); + WidgetPickerComposeWrapper getWidgetPickerComposeWrapper(); + /** Builder for LauncherBaseAppComponent. */ interface Builder { diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index ca404ea451..60861b0c26 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -71,6 +71,7 @@ import com.android.launcher3.R; import com.android.launcher3.WorkspaceLayoutManager; import com.android.launcher3.celllayout.CellLayoutLayoutParams; import com.android.launcher3.celllayout.CellPosMapper; +import com.android.launcher3.compose.core.widgetpicker.NoOpWidgetPickerModule; import com.android.launcher3.concurrent.ExecutorsModule; import com.android.launcher3.dagger.ApiWrapperModule; import com.android.launcher3.dagger.AppModule; @@ -546,6 +547,7 @@ public class LauncherPreviewRenderer extends BaseContext LauncherConcurrencyModule.class, ExecutorsModule.class, LauncherExecutorsModule.class, + NoOpWidgetPickerModule.class }) public interface PreviewAppComponent extends LauncherAppComponent { diff --git a/src/com/android/launcher3/widgetpicker/WidgetPickerActivity.kt b/src/com/android/launcher3/widgetpicker/WidgetPickerActivity.kt index 36c6d10b86..cc67fcd257 100644 --- a/src/com/android/launcher3/widgetpicker/WidgetPickerActivity.kt +++ b/src/com/android/launcher3/widgetpicker/WidgetPickerActivity.kt @@ -50,7 +50,7 @@ open class WidgetPickerActivity : BaseActivity() { checkNotNull(_dragLayer).recreateControllers() if (Flags.enableWidgetPickerRefactor() && isComposeAvailable()) { - // TODO(b/408283627): add compose picker here. + component.widgetPickerComposeWrapper.showAllWidgets(this) } }