diff --git a/compose/features/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapperImpl.kt b/compose/features/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapperImpl.kt index 22a47e96db..c408620089 100644 --- a/compose/features/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapperImpl.kt +++ b/compose/features/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapperImpl.kt @@ -18,8 +18,8 @@ package com.android.launcher3.widgetpicker import android.app.Activity.RESULT_OK import android.content.Context -import androidx.compose.foundation.isSystemInDarkTheme import android.content.Intent +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember @@ -33,7 +33,6 @@ import com.android.launcher3.compose.core.widgetpicker.WidgetPickerComposeWrappe 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 @@ -42,15 +41,16 @@ 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.shared.model.isAppWidget +import com.android.launcher3.widgetpicker.theme.darkWidgetPickerColors +import com.android.launcher3.widgetpicker.theme.lightWidgetPickerColors import com.android.launcher3.widgetpicker.ui.WidgetInteractionInfo import com.android.launcher3.widgetpicker.ui.WidgetPickerEventListeners import com.android.launcher3.widgetpicker.ui.theme.WidgetPickerTheme -import com.android.launcher3.widgetpicker.theme.darkWidgetPickerColors -import com.android.launcher3.widgetpicker.theme.lightWidgetPickerColors -import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Provider import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.launch /** * An helper that bootstraps widget picker UI (from [WidgetPickerComponent]) in to @@ -58,21 +58,21 @@ import kotlin.coroutines.CoroutineContext * * Sets up the bindings necessary for widget picker component. */ -class WidgetPickerComposeWrapperImpl @Inject constructor( +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, + @BackgroundContext private val backgroundContext: CoroutineContext, + @ApplicationContext private val appContext: Context, private val apiWrapper: ApiWrapper, ) : WidgetPickerComposeWrapper { override fun showAllWidgets( activity: WidgetPickerActivity, - widgetPickerConfig: WidgetPickerConfig + widgetPickerConfig: WidgetPickerConfig, ) { val widgetPickerComponent = newWidgetPickerComponent(widgetPickerConfig) val callbacks = activity.buildEventListeners(widgetPickerConfig, apiWrapper) @@ -85,11 +85,12 @@ class WidgetPickerComposeWrapperImpl @Inject constructor( val scope = rememberCoroutineScope() val view = LocalView.current - val widgetPickerColors = if (isSystemInDarkTheme()) { - darkWidgetPickerColors() - } else { - lightWidgetPickerColors() - } + val widgetPickerColors = + if (isSystemInDarkTheme()) { + darkWidgetPickerColors() + } else { + lightWidgetPickerColors() + } MaterialTheme { // TODO(b/408283627): Use launcher theme. WidgetPickerTheme(colors = widgetPickerColors) { @@ -99,13 +100,9 @@ class WidgetPickerComposeWrapperImpl @Inject constructor( } DisposableEffect(view) { - scope.launch { - initializeRepositories() - } + scope.launch { initializeRepositories() } - onDispose { - cleanUpRepositories() - } + onDispose { cleanUpRepositories() } } } } @@ -116,19 +113,22 @@ class WidgetPickerComposeWrapperImpl @Inject constructor( private fun newWidgetPickerComponent( widgetPickerConfig: WidgetPickerConfig ): WidgetPickerComponent { - return widgetPickerComponentProvider.get() + 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 + widgetHostInfo = + WidgetHostInfo( + title = + widgetPickerConfig.title + ?: appContext.resources.getString(R.string.widget_button_text), + description = widgetPickerConfig.description, + constraints = widgetPickerConfig.asHostConstraints(), + showDragShadow = !widgetPickerConfig.isForHomeScreen, + ), + backgroundContext = backgroundContext, ) } @@ -150,20 +150,21 @@ class WidgetPickerComposeWrapperImpl @Inject constructor( private fun WidgetPickerActivity.buildEventListeners( widgetPickerConfig: WidgetPickerConfig, - apiWrapper: ApiWrapper - ) = object : WidgetPickerEventListeners { - override fun onClose() { - finish() - } + apiWrapper: ApiWrapper, + ) = + object : WidgetPickerEventListeners { + override fun onClose() { + finish() + } - override fun onWidgetInteraction(widgetInteractionInfo: WidgetInteractionInfo) { - if (widgetPickerConfig.isForHomeScreen) { - handleWidgetInteractionForHomeScreen(widgetInteractionInfo, apiWrapper) - } else { - handleWidgetInteractionForExternalHost(widgetInteractionInfo) + 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 @@ -171,37 +172,38 @@ class WidgetPickerComposeWrapperImpl @Inject constructor( * * 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. + * handles the drag until completion. * - WidgetPickerAddItemListener: once launcher is shown, triggers the flow to add the - * widget to workspace. + * widget to workspace. */ private fun WidgetPickerActivity.handleWidgetInteractionForHomeScreen( interactionInfo: WidgetInteractionInfo, - apiWrapper: ApiWrapper + 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 - ) + val interactionListener = + when (interactionInfo) { + is WidgetInteractionInfo.WidgetDragInfo -> + WidgetPickerDragItemListener( + mimeType = interactionInfo.mimeType, + widgetInfo = interactionInfo.widgetInfo, + widgetPreview = interactionInfo.previewInfo, + previewRect = interactionInfo.bounds, + previewWidth = interactionInfo.widthPx, + ) - is WidgetInteractionInfo.WidgetAddInfo -> - WidgetPickerAddItemListener(interactionInfo.providerInfo) - } + is WidgetInteractionInfo.WidgetAddInfo -> + WidgetPickerAddItemListener(interactionInfo.widgetInfo) + } Launcher.ACTIVITY_TRACKER.registerCallback( interactionListener, - HOME_SCREEN_WIDGET_INTERACTION_REASON_STRING + 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() + /*options=*/ apiWrapper.createFadeOutAnimOptions().toBundle(), ) finish() } @@ -209,30 +211,35 @@ class WidgetPickerComposeWrapperImpl @Inject constructor( /** * 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. + * 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]. + * the widget being added (namely [Intent.EXTRA_COMPONENT_NAME] and [Intent.EXTRA_USER]. */ private fun WidgetPickerActivity.handleWidgetInteractionForExternalHost( - widgetInteractionInfo: WidgetInteractionInfo, + widgetInteractionInfo: WidgetInteractionInfo ) { when (widgetInteractionInfo) { is WidgetInteractionInfo.WidgetDragInfo -> - setResult( - RESULT_OK, - Intent().putExtra(EXTRA_IS_PENDING_WIDGET_DRAG, true) - ) + 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) - ) + val widgetInfo = widgetInteractionInfo.widgetInfo + if (widgetInfo.isAppWidget()) { + val providerInfo = widgetInfo.appWidgetProviderInfo + setResult( + RESULT_OK, + Intent().apply { + putExtra(Intent.EXTRA_COMPONENT_NAME, providerInfo.provider) + putExtra(Intent.EXTRA_USER, providerInfo.profile) + }, + ) + } else { + throw IllegalStateException( + "AppWidgetInfo not provided for external host drag" + ) + } } } @@ -240,21 +247,21 @@ class WidgetPickerComposeWrapperImpl @Inject constructor( } /** 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, - ) - ) - } + fun WidgetPickerConfig.asHostConstraints() = buildList { + if (filteredUsers.isNotEmpty()) { + add(HostConstraint.HostUserConstraint(filteredUsers)) } + if (!isForHomeScreen) { + add(HostConstraint.NoShortcutsConstraint) + } + if (categoryInclusionFilter != 0 || categoryExclusionFilter != 0) { + add( + HostConstraint.HostCategoryConstraint( + categoryInclusionMask = categoryInclusionFilter, + categoryExclusionMask = categoryExclusionFilter, + ) + ) + } + } } } diff --git a/compose/features/com/android/launcher3/widgetpicker/datasource/ConfigResourceFeaturedWidgetsDataSource.kt b/compose/features/com/android/launcher3/widgetpicker/datasource/ConfigResourceFeaturedWidgetsDataSource.kt index d361452286..5c11c7fe84 100644 --- a/compose/features/com/android/launcher3/widgetpicker/datasource/ConfigResourceFeaturedWidgetsDataSource.kt +++ b/compose/features/com/android/launcher3/widgetpicker/datasource/ConfigResourceFeaturedWidgetsDataSource.kt @@ -24,6 +24,7 @@ import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize import com.android.launcher3.widgetpicker.shared.model.PickableWidget import com.android.launcher3.widgetpicker.shared.model.WidgetApp +import com.android.launcher3.widgetpicker.shared.model.isAppWidget import java.util.Arrays import java.util.stream.Collectors import javax.inject.Inject @@ -31,47 +32,61 @@ import javax.inject.Inject /** * An implementation of [FeaturedWidgetsDataSource] that provides featured widgets based on a static * configuration from resources and pre-defined size templates. + * + * Only appwidgets; no shortcuts */ @LauncherAppSingleton -class ConfigResourceFeaturedWidgetsDataSource @Inject constructor( +class ConfigResourceFeaturedWidgetsDataSource +@Inject +constructor( @ApplicationContext private val appContext: Context, - private val idp: InvariantDeviceProfile + private val idp: InvariantDeviceProfile, ) : FeaturedWidgetsDataSource { // the package part in component name e.g. "com.example" in {com.example/widget.Provider} private var eligiblePackages: Set = emptySet() override suspend fun initialize() { if (eligiblePackages.isEmpty()) { - eligiblePackages = Arrays.stream( - appContext.resources.getStringArray(R.array.default_featured_widget_apps) - ).collect(Collectors.toSet()) + eligiblePackages = + Arrays.stream( + appContext.resources.getStringArray(R.array.default_featured_widget_apps) + ) + .collect(Collectors.toSet()) } } override suspend fun getFeaturedWidgets(widgetApps: List): List { - val widgetsByContainerSize = widgetApps - .shuffled() - // pick only one of user profiles - .distinctBy { Pair(it.id.packageName, it.id.category) } - .flatMap { it.widgets } - .filter { eligiblePackages.contains(it.id.componentName.packageName) } - .groupBy { - WidgetPreviewContainerSize( - it.sizeInfo.containerSpanX, - it.sizeInfo.containerSpanY - ) - } + val widgetsByContainerSize = + widgetApps + .shuffled() + // pick only one of user profiles + .distinctBy { Pair(it.id.packageName, it.id.category) } + .flatMap { it.widgets } + .filter { + eligiblePackages.isEmpty() || + eligiblePackages.contains(it.id.componentName.packageName) + } + .groupBy { + WidgetPreviewContainerSize( + it.sizeInfo.containerSpanX, + it.sizeInfo.containerSpanY, + ) + } val selected: MutableList = mutableListOf() val usedAppIds: MutableSet = mutableSetOf() - val sizesToPick = WidgetPreviewContainerSize.pickTemplateForFeaturedWidgets( - idp.getDeviceProfile(appContext) - ) + val sizesToPick = + WidgetPreviewContainerSize.pickTemplateForFeaturedWidgets( + idp.getDeviceProfile(appContext) + ) for (sizeToPick in sizesToPick) { widgetsByContainerSize[sizeToPick]?.shuffled()?.let { items -> for (item in items) { - if (!usedAppIds.contains(item.appId.packageName)) { + if ( + item.widgetInfo.isAppWidget() && + !usedAppIds.contains(item.appId.packageName) + ) { selected.add(item) usedAppIds.add(item.appId.packageName) break diff --git a/compose/features/com/android/launcher3/widgetpicker/listeners/WidgetPickerAddItemListener.kt b/compose/features/com/android/launcher3/widgetpicker/listeners/WidgetPickerAddItemListener.kt index 330b66a14f..988d016bbd 100644 --- a/compose/features/com/android/launcher3/widgetpicker/listeners/WidgetPickerAddItemListener.kt +++ b/compose/features/com/android/launcher3/widgetpicker/listeners/WidgetPickerAddItemListener.kt @@ -16,14 +16,17 @@ package com.android.launcher3.widgetpicker.listeners -import android.appwidget.AppWidgetProviderInfo import android.view.View import com.android.launcher3.Launcher import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY +import com.android.launcher3.PendingAddItemInfo import com.android.launcher3.logging.StatsLogManager.LauncherEvent +import com.android.launcher3.pm.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO import com.android.launcher3.util.ContextTracker.SchedulerCallback import com.android.launcher3.widget.LauncherAppWidgetProviderInfo +import com.android.launcher3.widget.PendingAddShortcutInfo import com.android.launcher3.widget.PendingAddWidgetInfo +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo /** * A callback listener (for tap-to-add flow) that handles adding a widget from a separate widget @@ -31,26 +34,38 @@ import com.android.launcher3.widget.PendingAddWidgetInfo * * Also logs to stats logger once widget is added. */ -class WidgetPickerAddItemListener(private val providerInfo: AppWidgetProviderInfo) : +class WidgetPickerAddItemListener(private val widgetInfo: WidgetInfo) : SchedulerCallback { override fun init(launcher: Launcher?, isHomeStarted: Boolean): Boolean { checkNotNull(launcher) - val launcherProviderInfo = - LauncherAppWidgetProviderInfo.fromProviderInfo(launcher, providerInfo) - val pendingAddWidgetInfo = - PendingAddWidgetInfo(launcherProviderInfo, CONTAINER_WIDGETS_TRAY) + val pendingAddItemInfo: PendingAddItemInfo = + when (widgetInfo) { + is WidgetInfo.AppWidgetInfo -> { + val launcherProviderInfo = + LauncherAppWidgetProviderInfo.fromProviderInfo( + launcher, + widgetInfo.appWidgetProviderInfo, + ) + PendingAddWidgetInfo(launcherProviderInfo, CONTAINER_WIDGETS_TRAY) + } + + is WidgetInfo.ShortcutInfo -> + PendingAddShortcutInfo( + ShortcutConfigActivityInfoVO(widgetInfo.launcherActivityInfo) + ) + } val view = View(launcher) - view.tag = pendingAddWidgetInfo + view.tag = pendingAddItemInfo launcher.accessibilityDelegate?.addToWorkspace( - /*item=*/ pendingAddWidgetInfo, - /*accessibility=*/ false + /*item=*/ pendingAddItemInfo, + /*accessibility=*/ false, ) { launcher.statsLogManager .logger() - .withItemInfo(pendingAddWidgetInfo) + .withItemInfo(pendingAddItemInfo) .log(LauncherEvent.LAUNCHER_WIDGET_ADD_BUTTON_TAP) } return false // don't receive any more callbacks as we got launcher and handled it diff --git a/compose/features/com/android/launcher3/widgetpicker/listeners/WidgetPickerDragItemListener.kt b/compose/features/com/android/launcher3/widgetpicker/listeners/WidgetPickerDragItemListener.kt index 3b3e8c3a4e..9d13b9bb49 100644 --- a/compose/features/com/android/launcher3/widgetpicker/listeners/WidgetPickerDragItemListener.kt +++ b/compose/features/com/android/launcher3/widgetpicker/listeners/WidgetPickerDragItemListener.kt @@ -16,72 +16,100 @@ package com.android.launcher3.widgetpicker.listeners -import android.appwidget.AppWidgetProviderInfo import android.graphics.Rect import android.view.View import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY +import com.android.launcher3.PendingAddItemInfo import com.android.launcher3.dragndrop.BaseItemDragListener +import com.android.launcher3.pm.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO import com.android.launcher3.widget.DatabaseWidgetPreviewLoader.WidgetPreviewInfo import com.android.launcher3.widget.LauncherAppWidgetProviderInfo +import com.android.launcher3.widget.PendingAddShortcutInfo import com.android.launcher3.widget.PendingAddWidgetInfo import com.android.launcher3.widget.PendingItemDragHelper +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo import com.android.launcher3.widgetpicker.shared.model.WidgetPreview +import com.android.launcher3.widgetpicker.shared.model.isAppWidget /** * A callback listener of type [BaseItemDragListener] that handles widget drag and drop from widget * picker hosted in a separate activity than home screen. * - * Responsible for initializing the [PendingItemDragHelper] that then handles the rest of the - * drag and drop (including showing a drag shadow for the widget). + * Responsible for initializing the [PendingItemDragHelper] that then handles the rest of the drag + * and drop (including showing a drag shadow for the widget). * * @param mimeType a mime type used by widget picker when attaching this listener for a specific - * widget's drag and drop session. - * @param appWidgetProviderInfo provider info of the widget being dragged + * widget's drag and drop session. + * @param widgetInfo metadata of the widget being dragged + * @param widgetPreview provides the preview information for widgets * @param previewRect the bounds of widget's preview offset by the point of long press * @param previewWidth width of the preview as it appears in the widget picker. */ class WidgetPickerDragItemListener( private val mimeType: String, - private val appWidgetProviderInfo: AppWidgetProviderInfo, + private val widgetInfo: WidgetInfo, private val widgetPreview: WidgetPreview, previewRect: Rect, - previewWidth: Int + previewWidth: Int, ) : BaseItemDragListener(previewRect, previewWidth, previewWidth) { override fun getMimeType(): String = mimeType override fun createDragHelper(): PendingItemDragHelper { - val launcherProviderInfo = - LauncherAppWidgetProviderInfo.fromProviderInfo(mLauncher, appWidgetProviderInfo) - val pendingAddWidgetInfo = - PendingAddWidgetInfo(launcherProviderInfo, CONTAINER_WIDGETS_TRAY) + val pendingAddItemInfo: PendingAddItemInfo = + when (widgetInfo) { + is WidgetInfo.AppWidgetInfo -> { + val launcherProviderInfo = + LauncherAppWidgetProviderInfo.fromProviderInfo( + mLauncher, + widgetInfo.appWidgetProviderInfo, + ) + PendingAddWidgetInfo(launcherProviderInfo, CONTAINER_WIDGETS_TRAY) + } + + is WidgetInfo.ShortcutInfo -> + PendingAddShortcutInfo( + ShortcutConfigActivityInfoVO(widgetInfo.launcherActivityInfo) + ) + } val view = View(mLauncher) - view.tag = pendingAddWidgetInfo + view.tag = pendingAddItemInfo val dragHelper = PendingItemDragHelper(view) - val info = WidgetPreviewInfo() - when (widgetPreview) { - is WidgetPreview.BitmapWidgetPreview -> { - info.previewBitmap = widgetPreview.bitmap - info.providerInfo = appWidgetProviderInfo - } - - is WidgetPreview.ProviderInfoWidgetPreview -> { - info.providerInfo = widgetPreview.providerInfo - } - - is WidgetPreview.RemoteViewsWidgetPreview -> { - info.remoteViews = widgetPreview.remoteViews - info.providerInfo = appWidgetProviderInfo - } - - else -> throw IllegalStateException( - "Unsupported preview type when dropping widget to launcher" - ) - } - dragHelper.setWidgetPreviewInfo(info) + if (widgetInfo.isAppWidget()) { + setAppWidgetPreviewInfo(widgetPreview, widgetInfo, dragHelper) + } // shortcut preview is fetched by home screen. return dragHelper } + + private fun setAppWidgetPreviewInfo( + appWidgetPreview: WidgetPreview, + widgetInfo: WidgetInfo.AppWidgetInfo, + dragHelper: PendingItemDragHelper, + ) { + val info = WidgetPreviewInfo() + when (appWidgetPreview) { + is WidgetPreview.BitmapWidgetPreview -> { + info.previewBitmap = appWidgetPreview.bitmap + info.providerInfo = widgetInfo.appWidgetProviderInfo + } + + is WidgetPreview.ProviderInfoWidgetPreview -> { + info.providerInfo = appWidgetPreview.providerInfo + } + + is WidgetPreview.RemoteViewsWidgetPreview -> { + info.remoteViews = appWidgetPreview.remoteViews + info.providerInfo = widgetInfo.appWidgetProviderInfo + } + + else -> + throw IllegalStateException( + "Unsupported preview type when dropping widget to launcher" + ) + } + dragHelper.setWidgetPreviewInfo(info) + } } diff --git a/compose/features/com/android/launcher3/widgetpicker/repository/WidgetsRepositoryImpl.kt b/compose/features/com/android/launcher3/widgetpicker/repository/WidgetsRepositoryImpl.kt index a49fbc44df..23ab58588a 100644 --- a/compose/features/com/android/launcher3/widgetpicker/repository/WidgetsRepositoryImpl.kt +++ b/compose/features/com/android/launcher3/widgetpicker/repository/WidgetsRepositoryImpl.kt @@ -24,6 +24,7 @@ import com.android.launcher3.dagger.ApplicationContext import com.android.launcher3.model.WidgetItem import com.android.launcher3.model.WidgetsModel import com.android.launcher3.model.data.PackageItemInfo +import com.android.launcher3.pm.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO import com.android.launcher3.util.ComponentKey import com.android.launcher3.util.Executors.MODEL_EXECUTOR import com.android.launcher3.widget.DatabaseWidgetPreviewLoader @@ -36,8 +37,11 @@ import com.android.launcher3.widgetpicker.shared.model.PickableWidget import com.android.launcher3.widgetpicker.shared.model.WidgetApp import com.android.launcher3.widgetpicker.shared.model.WidgetAppId import com.android.launcher3.widgetpicker.shared.model.WidgetId +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo import com.android.launcher3.widgetpicker.shared.model.WidgetPreview import com.android.launcher3.widgetpicker.shared.model.WidgetSizeInfo +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -51,76 +55,73 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import javax.inject.Inject -import kotlin.coroutines.CoroutineContext /** * An implementation of the [WidgetsRepository] that provides widgets for widget picker using the * [WidgetsModel], [FeaturedWidgetsDataSource] & enables search using the provided * [WidgetsSearchAlgorithm]. */ -class WidgetsRepositoryImpl @Inject constructor( +class WidgetsRepositoryImpl +@Inject +constructor( @ApplicationContext private val appContext: Context, idp: InvariantDeviceProfile, private val widgetsModel: WidgetsModel, private val featuredWidgetsDataSource: FeaturedWidgetsDataSource, private val searchAlgorithm: WidgetsSearchAlgorithm, - @BackgroundContext - private val backgroundContext: CoroutineContext, + @BackgroundContext private val backgroundContext: CoroutineContext, ) : WidgetsRepository { private val deviceProfile = idp.getDeviceProfile(appContext) - private val backgroundScope = CoroutineScope( - SupervisorJob() + backgroundContext + CoroutineName("WidgetsRepository") - ) + private val backgroundScope = + CoroutineScope(SupervisorJob() + backgroundContext + CoroutineName("WidgetsRepository")) - private val _widgetItemsByPackage = - MutableStateFlow>(emptyList()) + private val _widgetItemsByPackage = MutableStateFlow>(emptyList()) private val databaseWidgetPreviewLoader = DatabaseWidgetPreviewLoader(appContext, deviceProfile) override fun initialize() { // TODO(b/419495339): Remove the model executor requirement from widgets model and replace // with scope.launch MODEL_EXECUTOR.execute { - widgetsModel.update(/*packageUser=*/ null) + widgetsModel.update(/* packageUser= */ null) _widgetItemsByPackage.update { widgetsModel.widgetsByPackageItemForPicker.toPickableWidgets(deviceProfile) } } - backgroundScope.launch { - featuredWidgetsDataSource.initialize() - } + backgroundScope.launch { featuredWidgetsDataSource.initialize() } } override fun observeWidgets(): Flow> = _widgetItemsByPackage.asStateFlow() override suspend fun getWidgetPreview(id: WidgetId): WidgetPreview { val componentKey = ComponentKey(id.componentName, id.userHandle) - val widgetItem = widgetsModel.widgetsByComponentKey[componentKey] - ?: return WidgetPreview.PlaceholderWidgetPreview + val widgetItem = + widgetsModel.widgetsByComponentKey[componentKey] + ?: return WidgetPreview.PlaceholderWidgetPreview val previewSizePx = WidgetSizes.getWidgetSizePx(deviceProfile, widgetItem.spanX, widgetItem.spanY) - val preview = withContext(backgroundContext) { - val result = - databaseWidgetPreviewLoader.generatePreviewInfoBg( - widgetItem, - previewSizePx.width, - previewSizePx.height - ) - when { - result.remoteViews != null -> - WidgetPreview.RemoteViewsWidgetPreview(result.remoteViews) + val preview = + withContext(backgroundContext) { + val result = + databaseWidgetPreviewLoader.generatePreviewInfoBg( + widgetItem, + previewSizePx.width, + previewSizePx.height, + ) + when { + result.remoteViews != null -> + WidgetPreview.RemoteViewsWidgetPreview(result.remoteViews) - result.providerInfo != null -> - WidgetPreview.ProviderInfoWidgetPreview(result.providerInfo) + result.providerInfo != null -> + WidgetPreview.ProviderInfoWidgetPreview(result.providerInfo) - result.previewBitmap != null -> - WidgetPreview.BitmapWidgetPreview(result.previewBitmap) + result.previewBitmap != null -> + WidgetPreview.BitmapWidgetPreview(result.previewBitmap) - else -> WidgetPreview.PlaceholderWidgetPreview + else -> WidgetPreview.PlaceholderWidgetPreview + } } - } return preview } @@ -138,29 +139,32 @@ class WidgetsRepositoryImpl @Inject constructor( } override fun getFeaturedWidgets(): Flow> { - return _widgetItemsByPackage.map { widgets -> - featuredWidgetsDataSource.getFeaturedWidgets(widgets) - }.flowOn(backgroundContext) + return _widgetItemsByPackage + .map { widgets -> featuredWidgetsDataSource.getFeaturedWidgets(widgets) } + .flowOn(backgroundContext) } companion object { - private fun Map>.toPickableWidgets(deviceProfile: DeviceProfile) = - map { (packageItemInfo, widgetItems) -> - val widgetAppId = WidgetAppId( + private fun Map>.toPickableWidgets( + deviceProfile: DeviceProfile + ) = map { (packageItemInfo, widgetItems) -> + val widgetAppId = + WidgetAppId( packageName = packageItemInfo.packageName, userHandle = packageItemInfo.user, - category = packageItemInfo.widgetCategory + category = packageItemInfo.widgetCategory, ) - WidgetApp( - id = widgetAppId, - title = packageItemInfo.title, - widgets = widgetItems.filter { it.widgetInfo != null }.map { widgetItem -> + WidgetApp( + id = widgetAppId, + title = packageItemInfo.title, + widgets = + widgetItems.map { widgetItem -> val previewSize = WidgetSizes.getWidgetSizePx( deviceProfile, widgetItem.spanX, - widgetItem.spanY + widgetItem.spanY, ) val containerSpan = WidgetPreviewContainerSize.forItem(widgetItem, deviceProfile) @@ -168,31 +172,43 @@ class WidgetsRepositoryImpl @Inject constructor( WidgetSizes.getWidgetSizePx( deviceProfile, containerSpan.spanX, - containerSpan.spanY + containerSpan.spanY, ) PickableWidget( - id = WidgetId( - componentName = widgetItem.componentName, - userHandle = widgetItem.user - ), + id = + WidgetId( + componentName = widgetItem.componentName, + userHandle = widgetItem.user, + ), appId = widgetAppId, label = widgetItem.label, description = widgetItem.description, - appWidgetProviderInfo = widgetItem.widgetInfo.clone(), - sizeInfo = WidgetSizeInfo( - spanX = widgetItem.widgetInfo.spanX, - spanY = widgetItem.widgetInfo.spanY, - widthPx = previewSize.width, - heightPx = previewSize.height, - containerSpanX = containerSpan.spanX, - containerSpanY = containerSpan.spanY, - containerWidthPx = containerSize.width, - containerHeightPx = containerSize.height - ) + widgetInfo = + if (widgetItem.widgetInfo != null) { + WidgetInfo.AppWidgetInfo( + appWidgetProviderInfo = widgetItem.widgetInfo.clone() + ) + } else { + check(widgetItem.activityInfo is ShortcutConfigActivityInfoVO) + WidgetInfo.ShortcutInfo( + launcherActivityInfo = widgetItem.activityInfo.mInfo + ) + }, + sizeInfo = + WidgetSizeInfo( + spanX = widgetItem.spanX, + spanY = widgetItem.spanY, + widthPx = previewSize.width, + heightPx = previewSize.height, + containerSpanX = containerSpan.spanX, + containerSpanY = containerSpan.spanY, + containerWidthPx = containerSize.width, + containerHeightPx = containerSize.height, + ), ) - } - ) - } + }, + ) + } } } diff --git a/compose/tests/com/android/launcher3/widget/AddWidgetConfigTest.kt b/compose/tests/com/android/launcher3/widget/AddWidgetConfigTest.kt index 73daacd1af..288604f9eb 100644 --- a/compose/tests/com/android/launcher3/widget/AddWidgetConfigTest.kt +++ b/compose/tests/com/android/launcher3/widget/AddWidgetConfigTest.kt @@ -34,6 +34,7 @@ import com.android.launcher3.util.ui.PortraitLandscapeRunner.PortraitLandscape import com.android.launcher3.util.ui.TestViewHelpers import com.android.launcher3.util.workspace.FavoriteItemsTransaction import com.android.launcher3.widgetpicker.listeners.WidgetPickerAddItemListener +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -83,10 +84,10 @@ class AddWidgetConfigTest : BaseLauncherActivityTest() { // Add widget to home screen val monitor = WidgetConfigStartupMonitor() - launcherActivity.executeOnLauncher({ l: Launcher -> - val addItemListener = WidgetPickerAddItemListener(widgetInfo) + launcherActivity.executeOnLauncher { l: Launcher -> + val addItemListener = WidgetPickerAddItemListener(WidgetInfo.AppWidgetInfo(widgetInfo)) addItemListener.init(l, /* isHomeStarted= */ true) - }) + } uiDevice.waitForIdle() diff --git a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/domain/usecase/FilterWidgetsForHostUseCase.kt b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/domain/usecase/FilterWidgetsForHostUseCase.kt index 07de348c8f..45f7427139 100644 --- a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/domain/usecase/FilterWidgetsForHostUseCase.kt +++ b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/domain/usecase/FilterWidgetsForHostUseCase.kt @@ -21,8 +21,9 @@ import com.android.launcher3.widgetpicker.WidgetPickerSingleton import com.android.launcher3.widgetpicker.shared.model.HostConstraint import com.android.launcher3.widgetpicker.shared.model.PickableWidget import com.android.launcher3.widgetpicker.shared.model.WidgetHostInfo +import com.android.launcher3.widgetpicker.shared.model.isAppWidget +import com.android.launcher3.widgetpicker.shared.model.isShortcut import javax.inject.Inject -import javax.inject.Named /** * A usecase that hosts the business logic of filtering widgets based on host constraints and @@ -34,16 +35,28 @@ class FilterWidgetsForHostUseCase constructor(@WidgetPickerHostInfo private val hostInfo: WidgetHostInfo) { operator fun invoke(widgets: List) = widgets.filter { widget -> + val widgetInfo = widget.widgetInfo + val eligibleForHost = hostInfo.constraints.all { constraint -> when (constraint) { + is HostConstraint.NoShortcutsConstraint -> !widgetInfo.isShortcut() + is HostConstraint.HostUserConstraint -> !constraint.userFilters.contains(widget.id.userHandle) is HostConstraint.HostCategoryConstraint -> { - val widgetCategory = widget.appWidgetProviderInfo.widgetCategory - matchesCategory(constraint.categoryInclusionMask, widgetCategory) && - matchesCategory(constraint.categoryExclusionMask, widgetCategory) + // category applies only to widgets + if (widgetInfo.isAppWidget()) { + val widgetCategory = widgetInfo.appWidgetProviderInfo.widgetCategory + matchesCategory(constraint.categoryInclusionMask, widgetCategory) && + matchesCategory( + constraint.categoryExclusionMask, + widgetCategory, + ) + } else { + true + } } } } diff --git a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/shared/model/PickableWidget.kt b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/shared/model/PickableWidget.kt index 75cee6a722..146692efa4 100644 --- a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/shared/model/PickableWidget.kt +++ b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/shared/model/PickableWidget.kt @@ -17,6 +17,11 @@ package com.android.launcher3.widgetpicker.shared.model import android.appwidget.AppWidgetProviderInfo +import android.content.pm.LauncherActivityInfo +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo.AppWidgetInfo +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo.ShortcutInfo +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract /** * Raw information about a widget that can be considered for display in widget picker list. @@ -28,22 +33,22 @@ import android.appwidget.AppWidgetProviderInfo * @property appId a unique identifier for the app group that this widget could belong to * @property label a user friendly label for the widget. * @property description a user friendly description for the widget - * @property appWidgetProviderInfo widget info associated with the widget as configured by the - * developer; note: this should be a local clone and not the object that was received from - * appwidget manager. + * @property widgetInfo info associated with the widget as configured by the developer shared with + * host when adding a widget; note: this should be a local clone and not the object that was + * received from appwidget manager or package manager. */ data class PickableWidget( val id: WidgetId, val appId: WidgetAppId, val label: String, val description: CharSequence?, - val appWidgetProviderInfo: AppWidgetProviderInfo, + val widgetInfo: WidgetInfo, val sizeInfo: WidgetSizeInfo, ) { // Custom toString to account for the appWidgetProviderInfo. override fun toString(): String = "PickableWidget(id=$id,appId=$appId,label=$label,description=$description," + - "sizeInfo=$sizeInfo,provider=${appWidgetProviderInfo.provider})" + "sizeInfo=$sizeInfo,widgetInfo=${widgetInfo})" } /** @@ -73,3 +78,40 @@ data class WidgetSizeInfo( val containerWidthPx: Int, val containerHeightPx: Int, ) + +/** Information of the widget as configured by the developer. */ +sealed class WidgetInfo { + /** + * @param appWidgetProviderInfo metadata of an installed widgets as received from the appwidget + * manager. + */ + data class AppWidgetInfo(val appWidgetProviderInfo: AppWidgetProviderInfo) : WidgetInfo() + + /** + * @param launcherActivityInfo metadata of an installed deep shortcut as received from the + * package manager. + */ + data class ShortcutInfo(val launcherActivityInfo: LauncherActivityInfo) : WidgetInfo() + + override fun toString(): String { + when (this) { + is AppWidgetInfo -> "WidgetInfo(provider=${this.appWidgetProviderInfo.provider})" + is ShortcutInfo -> "WidgetInfo(activityInfo=${this.launcherActivityInfo.componentName})" + } + return super.toString() + } +} + +/** Returns true if the info is about an app widget. */ +@OptIn(ExperimentalContracts::class) +fun WidgetInfo.isAppWidget(): Boolean { + contract { returns(true) implies (this@isAppWidget is AppWidgetInfo) } + return this is AppWidgetInfo +} + +/** Returns true if the info is about a deep shortcut. */ +@OptIn(ExperimentalContracts::class) +fun WidgetInfo.isShortcut(): Boolean { + contract { returns(true) implies (this@isShortcut is ShortcutInfo) } + return this is ShortcutInfo +} diff --git a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/shared/model/WidgetHostInfo.kt b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/shared/model/WidgetHostInfo.kt index 3622f9f9a2..f6b124916e 100644 --- a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/shared/model/WidgetHostInfo.kt +++ b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/shared/model/WidgetHostInfo.kt @@ -23,17 +23,17 @@ import android.os.UserHandle * * @param title an optional title that should be shown in place of default "Widgets" title. * @param description an optional 1-2 line description to be shown below the title. If not set, no - * description is shown. + * description is shown. * @param constraints constraints around which widgets can be shown in the picker. * @param showDragShadow indicates whether to show drag shadow for the widgets when dragging them; - * can be set to false if host manages drag shadow on its own (e.g. home screen to animate the - * shadow with actual content) + * can be set to false if host manages drag shadow on its own (e.g. home screen to animate the + * shadow with actual content) */ data class WidgetHostInfo( val title: String? = null, val description: String? = null, val constraints: List = emptyList(), - val showDragShadow: Boolean = true + val showDragShadow: Boolean = true, ) /** Various constraints for the widget host. */ @@ -60,4 +60,7 @@ sealed class HostConstraint { * such case, the profile tab shows a generic no widgets available message. */ data class HostUserConstraint(val userFilters: List) : HostConstraint() + + /** Indicates that the host doesn't support shortcuts. */ + data object NoShortcutsConstraint : HostConstraint() } diff --git a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/WidgetPickerEventListeners.kt b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/WidgetPickerEventListeners.kt index 9973b79398..e515e221bd 100644 --- a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/WidgetPickerEventListeners.kt +++ b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/WidgetPickerEventListeners.kt @@ -16,13 +16,13 @@ package com.android.launcher3.widgetpicker.ui -import android.appwidget.AppWidgetProviderInfo import android.graphics.Rect +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo import com.android.launcher3.widgetpicker.shared.model.WidgetPreview /** - * General interface that clients can implement to listen to events from different types of - * widget picker. + * General interface that clients can implement to listen to events from different types of widget + * picker. */ interface WidgetPickerEventListeners { /** Called when the widget picker is dismissed. */ @@ -37,7 +37,7 @@ sealed class WidgetInteractionInfo { /** * Information passed in event listener when a widget is dragged. * - * @param providerInfo metadata for the provider of the widget being dragged. + * @param widgetInfo metadata for the provider of the widget being dragged. * @param bounds current bounds of the widget's preview considering the drag offset and scale. * @param widthPx measured width of the preview. * @param heightPx measured height of the preview. @@ -45,7 +45,7 @@ sealed class WidgetInteractionInfo { * @param mimeType a unique mime type set on clip data for the drag session */ data class WidgetDragInfo( - val providerInfo: AppWidgetProviderInfo, + val widgetInfo: WidgetInfo, val bounds: Rect, val widthPx: Int, val heightPx: Int, @@ -56,9 +56,7 @@ sealed class WidgetInteractionInfo { /** * Information passed in event listener when a widget is added using tap to add. * - * @param providerInfo metadata for the provider of the widget being added. + * @param widgetInfo metadata for the provider of the widget being added. */ - data class WidgetAddInfo( - val providerInfo: AppWidgetProviderInfo - ) : WidgetInteractionInfo() + data class WidgetAddInfo(val widgetInfo: WidgetInfo) : WidgetInteractionInfo() } diff --git a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/DragAndDrop.kt b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/DragAndDrop.kt index 309eb0f863..8dc79382e7 100644 --- a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/DragAndDrop.kt +++ b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/DragAndDrop.kt @@ -16,15 +16,16 @@ package com.android.launcher3.widgetpicker.ui.components -import android.appwidget.AppWidgetProviderInfo import android.content.ClipData import android.content.ClipDescription +import android.content.ComponentName import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Point import android.graphics.Rect +import android.os.UserHandle import android.view.View import android.view.View.DragShadowBuilder import androidx.compose.ui.unit.Dp @@ -32,33 +33,31 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntSize import androidx.core.graphics.drawable.RoundedBitmapDrawable import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo import java.util.UUID -/** - * Information about the image's dimensions post scaling. - */ +/** Information about the image's dimensions post scaling. */ data class ImageScaledDimensions( val scale: Float, val scaledSizeDp: DpSize, val scaledSizePx: IntSize, val scaledRadiusDp: Dp, - val scaledRadiusPx: Float + val scaledRadiusPx: Float, ) -/** - * A [DragShadowBuilder] that draws drag shadow using the provided bitmap and image dimensions. - */ +/** A [DragShadowBuilder] that draws drag shadow using the provided bitmap and image dimensions. */ class ImageBitmapDragShadowBuilder( context: Context, bitmap: Bitmap, - imageScaledDimensions: ImageScaledDimensions + imageScaledDimensions: ImageScaledDimensions, ) : DragShadowBuilder() { private val shadowWidth = imageScaledDimensions.scaledSizePx.width private val shadowHeight = imageScaledDimensions.scaledSizePx.height private val shadowDrawable: RoundedBitmapDrawable = - RoundedBitmapDrawableFactory.create(context.resources, bitmap) - .apply { cornerRadius = imageScaledDimensions.scaledRadiusPx } + RoundedBitmapDrawableFactory.create(context.resources, bitmap).apply { + cornerRadius = imageScaledDimensions.scaledRadiusPx + } override fun onProvideShadowMetrics(outShadowSize: Point?, outShadowTouchPoint: Point?) { outShadowSize?.set(shadowWidth, shadowHeight) @@ -84,48 +83,62 @@ object TransparentDragShadowBuilder : DragShadowBuilder() { private const val SHADOW_SIZE = 10 override fun onDrawShadow(canvas: Canvas) {} + override fun onProvideShadowMetrics(outShadowSize: Point, outShadowTouchPoint: Point) { - outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE); - outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2); + outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE) + outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2) } } -/** State containing information to start a drag for a widget. */ +/** State containing information to start a drag for a widget. */ class DragState( - private val widgetInfo: AppWidgetProviderInfo, - private val dragShadowBuilder: DragShadowBuilder + private val widgetInfo: WidgetInfo, + private val dragShadowBuilder: DragShadowBuilder, ) { private val uniqueId = UUID.randomUUID().toString() val pickerMimeType = "com.android.launcher3.widgetpicker.drag_and_drop/$uniqueId" fun startDrag(view: View) { - val clipData = ClipData( - ClipDescription( - // not displayed anywhere; so, set to empty. - /* label= */ "", - arrayOf( - // unique picker specific mime type. - pickerMimeType, - // indicates that the clip item contains an intent (with extras about widget - // info). - ClipDescription.MIMETYPE_TEXT_INTENT - ) - ), - ClipData.Item( - Intent() - .putExtra(Intent.EXTRA_USER, widgetInfo.profile) - .putExtra( - Intent.EXTRA_COMPONENT_NAME, - widgetInfo.provider - ) + val clipData = + ClipData( + ClipDescription( + // not displayed anywhere; so, set to empty. + /* label= */ "", + arrayOf( + // unique picker specific mime type. + pickerMimeType, + // indicates that the clip item contains an intent (with extras about widget + // info). + ClipDescription.MIMETYPE_TEXT_INTENT, + ), + ), + ClipData.Item( + when (widgetInfo) { + is WidgetInfo.AppWidgetInfo -> + buildIntentForClipData( + user = widgetInfo.appWidgetProviderInfo.profile, + componentName = widgetInfo.appWidgetProviderInfo.provider, + ) + + is WidgetInfo.ShortcutInfo -> + buildIntentForClipData( + user = widgetInfo.launcherActivityInfo.user, + componentName = widgetInfo.launcherActivityInfo.componentName, + ) + } + ), ) - ) view.startDragAndDrop( clipData, /*shadowBuilder=*/ dragShadowBuilder, /*myLocalState=*/ null, - View.DRAG_FLAG_GLOBAL + View.DRAG_FLAG_GLOBAL, ) } + + private fun buildIntentForClipData(user: UserHandle, componentName: ComponentName): Intent = + Intent() + .putExtra(Intent.EXTRA_USER, user) + .putExtra(Intent.EXTRA_COMPONENT_NAME, componentName) } diff --git a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetDetails.kt b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetDetails.kt index 4edcdfd40c..0ad64f804e 100644 --- a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetDetails.kt +++ b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetDetails.kt @@ -36,7 +36,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.Button -import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -90,61 +89,56 @@ fun WidgetDetails( ) { val haptic = LocalHapticFeedback.current val interactionSource = remember { MutableInteractionSource() } - val contentDescription = stringResource( - R.string.widget_details_accessibility_label, - widget.label, - widget.sizeInfo.spanX, - widget.sizeInfo.spanY - ) + val contentDescription = + stringResource( + R.string.widget_details_accessibility_label, + widget.label, + widget.sizeInfo.spanX, + widget.sizeInfo.spanY, + ) - val detailsAlpha: Float by animateFloatAsState( - targetValue = if (showAddButton) INVISIBLE_ALPHA else VISIBLE_ALPHA, - animationSpec = tween(durationMillis = TOGGLE_ANIMATION_DURATION), - label = "detailsAlphaAnimation" - ) + val detailsAlpha: Float by + animateFloatAsState( + targetValue = if (showAddButton) INVISIBLE_ALPHA else VISIBLE_ALPHA, + animationSpec = tween(durationMillis = TOGGLE_ANIMATION_DURATION), + label = "detailsAlphaAnimation", + ) Box( contentAlignment = Alignment.Center, - modifier = modifier - .fillMaxSize() - .clickable( - onClickLabel = if (showAddButton) { - stringResource(R.string.widget_tap_to_hide_add_button_label) - } else { - stringResource(R.string.widget_tap_to_show_add_button_label) - }, - interactionSource = interactionSource, - indication = null - ) { - haptic.performHapticFeedback(HapticFeedbackType.VirtualKey) - onAddButtonToggle( - widget.id - ) - } - .padding( - horizontal = WidgetDetailsDimensions.horizontalPadding, - vertical = WidgetDetailsDimensions.verticalPadding - ) + modifier = + modifier + .fillMaxSize() + .clickable( + onClickLabel = + if (showAddButton) { + stringResource(R.string.widget_tap_to_hide_add_button_label) + } else { + stringResource(R.string.widget_tap_to_show_add_button_label) + }, + interactionSource = interactionSource, + indication = null, + ) { + haptic.performHapticFeedback(HapticFeedbackType.VirtualKey) + onAddButtonToggle(widget.id) + } + .padding( + horizontal = WidgetDetailsDimensions.horizontalPadding, + vertical = WidgetDetailsDimensions.verticalPadding, + ), ) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top, - modifier = Modifier - .clearAndSetSemantics { this.contentDescription = contentDescription } - .minimumInteractiveComponentSize() - .graphicsLayer { alpha = detailsAlpha } - .fillMaxSize() + modifier = + Modifier.clearAndSetSemantics { this.contentDescription = contentDescription } + .minimumInteractiveComponentSize() + .graphicsLayer { alpha = detailsAlpha } + .fillMaxSize(), ) { - WidgetLabel( - label = widget.label, - appIcon = appIcon, - modifier = Modifier - ) + WidgetLabel(label = widget.label, appIcon = appIcon, modifier = Modifier) if (showAllDetails) { - WidgetSpanSizeLabel( - spanX = widget.sizeInfo.spanX, - spanY = widget.sizeInfo.spanY - ) + WidgetSpanSizeLabel(spanX = widget.sizeInfo.spanX, spanY = widget.sizeInfo.spanY) widget.description?.let { WidgetDescription(it) } } } @@ -152,16 +146,12 @@ fun WidgetDetails( visible = showAddButton, modifier = Modifier.fillMaxSize(), enter = AddButtonDefaults.enterTransition, - exit = AddButtonDefaults.exitTransition + exit = AddButtonDefaults.exitTransition, ) { AddButton( widget = widget, onClick = { - onWidgetAddClick( - WidgetInteractionInfo.WidgetAddInfo( - widget.appWidgetProviderInfo - ) - ) + onWidgetAddClick(WidgetInteractionInfo.WidgetAddInfo(widget.widgetInfo)) haptic.performHapticFeedback(HapticFeedbackType.Confirm) }, ) @@ -170,33 +160,28 @@ fun WidgetDetails( } @Composable -private fun AddButton( - widget: PickableWidget, - onClick: () -> Unit, -) { +private fun AddButton(widget: PickableWidget, onClick: () -> Unit) { val accessibleDescription = stringResource(R.string.widget_tap_to_add_button_content_description, widget.label) - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Button( modifier = Modifier.minimumInteractiveComponentSize(), contentPadding = AddButtonDimensions.paddingValues, - colors = ButtonDefaults.buttonColors( - containerColor = WidgetPickerTheme.colors.addButtonBackground, - contentColor = WidgetPickerTheme.colors.addButtonContent - ), + colors = + ButtonDefaults.buttonColors( + containerColor = WidgetPickerTheme.colors.addButtonBackground, + contentColor = WidgetPickerTheme.colors.addButtonContent, + ), onClick = onClick, ) { Icon( imageVector = Icons.Filled.Add, - contentDescription = null // decorative + contentDescription = null, // decorative ) Text( modifier = Modifier.semantics { this.contentDescription = accessibleDescription }, - text = stringResource(R.string.widget_tap_to_add_button_label) + text = stringResource(R.string.widget_tap_to_add_button_label), ) } } @@ -208,15 +193,13 @@ private fun WidgetLabel(label: String, appIcon: (@Composable () -> Unit)?, modif Row( modifier = modifier, horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { if (appIcon != null) { appIcon() Spacer( modifier = - Modifier - .width(WidgetDetailsDimensions.appIconLabelSpacing) - .fillMaxHeight() + Modifier.width(WidgetDetailsDimensions.appIconLabelSpacing).fillMaxHeight() ) } Text( @@ -272,12 +255,7 @@ private object WidgetDetailsDimensions { } private object AddButtonDimensions { - val paddingValues = PaddingValues( - start = 8.dp, - top = 11.dp, - end = 16.dp, - bottom = 11.dp - ) + val paddingValues = PaddingValues(start = 8.dp, top = 11.dp, end = 16.dp, bottom = 11.dp) } private object AddButtonDefaults { diff --git a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetPreview.kt b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetPreview.kt index 7977dd2ddc..2647ef77e8 100644 --- a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetPreview.kt +++ b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetPreview.kt @@ -63,8 +63,10 @@ import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.android.launcher3.widgetpicker.shared.model.WidgetId +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo import com.android.launcher3.widgetpicker.shared.model.WidgetPreview import com.android.launcher3.widgetpicker.shared.model.WidgetSizeInfo +import com.android.launcher3.widgetpicker.shared.model.isAppWidget import com.android.launcher3.widgetpicker.ui.WidgetInteractionInfo import com.android.launcher3.widgetpicker.ui.theme.WidgetPickerTheme import kotlin.math.roundToInt @@ -75,11 +77,11 @@ fun WidgetPreview( id: WidgetId, sizeInfo: WidgetSizeInfo, preview: WidgetPreview, - appwidgetInfo: AppWidgetProviderInfo, + widgetInfo: WidgetInfo, modifier: Modifier = Modifier, showDragShadow: Boolean, onWidgetInteraction: (WidgetInteractionInfo) -> Unit, - onAddButtonToggle: (WidgetId) -> Unit + onAddButtonToggle: (WidgetId) -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } val haptic = LocalHapticFeedback.current @@ -93,16 +95,16 @@ fun WidgetPreview( } Box( - modifier = modifier - .wrapContentSize() - .clickable( + modifier = + modifier.wrapContentSize().clickable( interactionSource = interactionSource, // no ripples for preview taps that toggle the add button. - indication = null + indication = null, ) { haptic.performHapticFeedback(HapticFeedbackType.VirtualKey) onAddButtonToggle(id) - }) { + } + ) { when (preview) { is WidgetPreview.PlaceholderWidgetPreview -> PlaceholderWidgetPreview(size = containerSize, widgetRadius = widgetRadius) @@ -112,30 +114,34 @@ fun WidgetPreview( bitmap = preview.bitmap, size = containerSize, widgetRadius = widgetRadius, - widgetInfo = appwidgetInfo, + widgetInfo = widgetInfo, showDragShadow = showDragShadow, onWidgetInteraction = onWidgetInteraction, ) - is WidgetPreview.RemoteViewsWidgetPreview -> + is WidgetPreview.RemoteViewsWidgetPreview -> { + check(widgetInfo.isAppWidget()) RemoteViewsWidgetPreview( remoteViews = preview.remoteViews, - widgetInfo = appwidgetInfo, + widgetInfo = widgetInfo, sizeInfo = sizeInfo, widgetRadius = widgetRadius, showDragShadow = showDragShadow, onWidgetInteraction = onWidgetInteraction, ) + } - is WidgetPreview.ProviderInfoWidgetPreview -> + is WidgetPreview.ProviderInfoWidgetPreview -> { + check(widgetInfo.isAppWidget()) RemoteViewsWidgetPreview( previewLayoutProviderInfo = preview.providerInfo, - widgetInfo = appwidgetInfo, + widgetInfo = widgetInfo, sizeInfo = sizeInfo, widgetRadius = widgetRadius, showDragShadow = showDragShadow, onWidgetInteraction = onWidgetInteraction, ) + } } } } @@ -145,8 +151,7 @@ private fun PlaceholderWidgetPreview(size: DpSize, widgetRadius: Dp) { Box( contentAlignment = Alignment.Center, modifier = - Modifier - .width(size.width) + Modifier.width(size.width) .height(size.height) .background( color = WidgetPickerTheme.colors.widgetPlaceholderBackground, @@ -161,7 +166,7 @@ private fun PlaceholderWidgetPreview(size: DpSize, widgetRadius: Dp) { private fun BitmapWidgetPreview( bitmap: Bitmap, size: DpSize, - widgetInfo: AppWidgetProviderInfo, + widgetInfo: WidgetInfo, widgetRadius: Dp, showDragShadow: Boolean, onWidgetInteraction: (WidgetInteractionInfo) -> Unit, @@ -170,22 +175,24 @@ private fun BitmapWidgetPreview( val density = LocalDensity.current val haptic = LocalHapticFeedback.current - val scaledBitmapDimensions by remember(bitmap, density, size) { - derivedStateOf { bitmap.calculateScaledDimensions(density, size, widgetRadius) } - } - - val dragState by remember(widgetInfo, showDragShadow) { - derivedStateOf { - DragState( - widgetInfo, - if (showDragShadow) { - ImageBitmapDragShadowBuilder(context, bitmap, scaledBitmapDimensions) - } else { - TransparentDragShadowBuilder - } - ) + val scaledBitmapDimensions by + remember(bitmap, density, size) { + derivedStateOf { bitmap.calculateScaledDimensions(density, size, widgetRadius) } + } + + val dragState by + remember(widgetInfo, showDragShadow) { + derivedStateOf { + DragState( + widgetInfo, + if (showDragShadow) { + ImageBitmapDragShadowBuilder(context, bitmap, scaledBitmapDimensions) + } else { + TransparentDragShadowBuilder + }, + ) + } } - } var imagePositionInParent by remember { mutableStateOf(Offset.Zero) } @@ -199,8 +206,7 @@ private fun BitmapWidgetPreview( contentDescription = null, // only visual (widget details provides the readable info) contentScale = ContentScale.FillBounds, modifier = - Modifier - .onGloballyPositioned { coordinates -> + Modifier.onGloballyPositioned { coordinates -> imagePositionInParent = coordinates.positionInParent() } .pointerInput(bitmap) { @@ -215,21 +221,19 @@ private fun BitmapWidgetPreview( calculateImageDragBounds( scaledBitmapDimensions = scaledBitmapDimensions, imagePositionInParent = imagePositionInParent, - offset = offset + offset = offset, ) onWidgetInteraction( WidgetInteractionInfo.WidgetDragInfo( mimeType = dragState.pickerMimeType, - providerInfo = widgetInfo, + widgetInfo = widgetInfo, bounds = bounds, widthPx = scaledBitmapDimensions.scaledSizePx.width, heightPx = scaledBitmapDimensions.scaledSizePx.height, - previewInfo = WidgetPreview.BitmapWidgetPreview( - bitmap = bitmap, - ), + previewInfo = WidgetPreview.BitmapWidgetPreview(bitmap = bitmap), ) ) - } + }, ) } .width(scaledBitmapDimensions.scaledSizeDp.width) @@ -242,26 +246,20 @@ private fun BitmapWidgetPreview( private fun calculateImageDragBounds( scaledBitmapDimensions: ImageScaledDimensions, imagePositionInParent: Offset, - offset: Offset + offset: Offset, ): Rect { val bounds = Rect() bounds.left = 0 bounds.top = 0 bounds.right = scaledBitmapDimensions.scaledSizePx.width bounds.bottom = scaledBitmapDimensions.scaledSizePx.height - val xOffset: Int = - (imagePositionInParent.x - offset.x).roundToInt() - val yOffset: Int = - (imagePositionInParent.y - offset.y).roundToInt() + val xOffset: Int = (imagePositionInParent.x - offset.x).roundToInt() + val yOffset: Int = (imagePositionInParent.y - offset.y).roundToInt() bounds.offset(xOffset, yOffset) return bounds } -private fun Bitmap.calculateScaledDimensions( - density: Density, - size: DpSize, - widgetRadius: Dp -) = +private fun Bitmap.calculateScaledDimensions(density: Density, size: DpSize, widgetRadius: Dp) = with(density) { val bitmapSize = DpSize(width = width.toDp(), height = height.toDp()) val bitmapAspectRatio = bitmapSize.width / bitmapSize.height @@ -269,20 +267,20 @@ private fun Bitmap.calculateScaledDimensions( // Scale by width if image has larger aspect ratio than the container else by // height; and avoid cropping the previews. - val scale = if (bitmapAspectRatio > containerAspectRatio) { - size.width / bitmapSize.width - } else { - size.height / bitmapSize.height - } + val scale = + if (bitmapAspectRatio > containerAspectRatio) { + size.width / bitmapSize.width + } else { + size.height / bitmapSize.height + } - val scaledDpSize = DpSize( - width = bitmapSize.width * scale, - height = bitmapSize.height * scale - ) - val scaledPxSize = IntSize( - width = scaledDpSize.width.roundToPx(), - height = scaledDpSize.height.roundToPx() - ) + val scaledDpSize = + DpSize(width = bitmapSize.width * scale, height = bitmapSize.height * scale) + val scaledPxSize = + IntSize( + width = scaledDpSize.width.roundToPx(), + height = scaledDpSize.height.roundToPx(), + ) val scaledRadius = (widgetRadius * scale).coerceAtMost(widgetRadius).value.roundToInt().dp ImageScaledDimensions( @@ -290,7 +288,7 @@ private fun Bitmap.calculateScaledDimensions( scaledSizePx = scaledPxSize, scaledSizeDp = scaledDpSize, scaledRadiusDp = scaledRadius, - scaledRadiusPx = scaledRadius.toPx() + scaledRadiusPx = scaledRadius.toPx(), ) } @@ -298,7 +296,7 @@ private fun Bitmap.calculateScaledDimensions( private fun RemoteViewsWidgetPreview( remoteViews: RemoteViews? = null, previewLayoutProviderInfo: AppWidgetProviderInfo? = null, - widgetInfo: AppWidgetProviderInfo, + widgetInfo: WidgetInfo.AppWidgetInfo, sizeInfo: WidgetSizeInfo, widgetRadius: Dp, onWidgetInteraction: (WidgetInteractionInfo) -> Unit, @@ -308,67 +306,71 @@ private fun RemoteViewsWidgetPreview( val haptic = LocalHapticFeedback.current val appWidgetHostView by - remember(sizeInfo, widgetInfo) { - derivedStateOf { - WidgetPreviewHostView(context).apply { - setContainerSizePx( - IntSize(sizeInfo.containerWidthPx, sizeInfo.containerHeightPx) - ) + remember(sizeInfo, widgetInfo) { + derivedStateOf { + WidgetPreviewHostView(context).apply { + setContainerSizePx( + IntSize(sizeInfo.containerWidthPx, sizeInfo.containerHeightPx) + ) + } } } - } val dragState by remember { derivedStateOf { DragState( widgetInfo = widgetInfo, - dragShadowBuilder = if (showDragShadow) { - DragShadowBuilder(appWidgetHostView) - } else { - TransparentDragShadowBuilder - } + dragShadowBuilder = + if (showDragShadow) { + DragShadowBuilder(appWidgetHostView) + } else { + TransparentDragShadowBuilder + }, ) } } key(appWidgetHostView) { AndroidView( - modifier = Modifier - .pointerInput(appWidgetHostView) { - detectDragGesturesAfterLongPress( - onDrag = { change, _ -> change.consume() }, - onDragStart = { offset -> - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - dragState.startDrag(appWidgetHostView) + modifier = + Modifier.pointerInput(appWidgetHostView) { + detectDragGesturesAfterLongPress( + onDrag = { change, _ -> change.consume() }, + onDragStart = { offset -> + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + dragState.startDrag(appWidgetHostView) - onWidgetInteraction( - WidgetInteractionInfo.WidgetDragInfo( - mimeType = dragState.pickerMimeType, - providerInfo = widgetInfo, - bounds = appWidgetHostView.getDragBoundsForOffset(offset), - widthPx = appWidgetHostView.measuredWidth, - heightPx = appWidgetHostView.measuredHeight, - previewInfo = when { - remoteViews != null -> - WidgetPreview.RemoteViewsWidgetPreview( - remoteViews = remoteViews, - ) + onWidgetInteraction( + WidgetInteractionInfo.WidgetDragInfo( + mimeType = dragState.pickerMimeType, + widgetInfo = widgetInfo, + bounds = appWidgetHostView.getDragBoundsForOffset(offset), + widthPx = appWidgetHostView.measuredWidth, + heightPx = appWidgetHostView.measuredHeight, + previewInfo = + when { + remoteViews != null -> + WidgetPreview.RemoteViewsWidgetPreview( + remoteViews = remoteViews + ) - previewLayoutProviderInfo != null -> - WidgetPreview.ProviderInfoWidgetPreview( - providerInfo = previewLayoutProviderInfo - ) + previewLayoutProviderInfo != null -> + WidgetPreview.ProviderInfoWidgetPreview( + providerInfo = previewLayoutProviderInfo + ) - else -> - throw IllegalStateException("No preview during drag") - } + else -> + throw IllegalStateException( + "No preview during drag" + ) + }, + ) ) - ) - }, - ) - } - .wrapContentSize() - .clip(RoundedCornerShape(widgetRadius)), + }, + ) + } + .wrapContentSize() + .clip(RoundedCornerShape(widgetRadius)), factory = { appWidgetHostView }, update = { view -> // if preview.remoteViews is null, initial layout will render. @@ -376,7 +378,7 @@ private fun RemoteViewsWidgetPreview( // to be the previewLayout. view.setAppWidget( /*appWidgetId=*/ NO_OP_APP_WIDGET_ID, - /*info=*/ previewLayoutProviderInfo ?: widgetInfo, + /*info=*/ previewLayoutProviderInfo ?: widgetInfo.appWidgetProviderInfo, ) view.updateAppWidget(remoteViews) }, diff --git a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetsGrid.kt b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetsGrid.kt index 5e0ecf8a94..4426ab7254 100644 --- a/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetsGrid.kt +++ b/modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetsGrid.kt @@ -61,10 +61,10 @@ import kotlin.math.max * @param appIcons optional map containing app icons to show in the widget details besides the label * (when showing the widgets outside of app context e.g. recommendations) * @param showDragShadow indicates if in a drag and drop session, widget picker should show drag - * shadow containing the preview; if not set, a transparent shadow is rendered and host should - * manage providing a shadow on its own. + * shadow containing the preview; if not set, a transparent shadow is rendered and host should + * manage providing a shadow on its own. * @param onWidgetInteraction callback invoked when a widget is being dragged and picker has started - * global drag and drop session. + * global drag and drop session. * @param modifier modifier with parent constraints and additional modifications */ @Composable @@ -93,12 +93,13 @@ fun WidgetsGrid( addButtonWidgetId = addButtonWidgetId, onWidgetInteraction = onWidgetInteraction, onAddButtonToggle = { id -> - addButtonWidgetId = if (id != addButtonWidgetId) { - id - } else { - null - } - } + addButtonWidgetId = + if (id != addButtonWidgetId) { + id + } else { + null + } + }, ) } } @@ -155,7 +156,7 @@ private fun WidgetsFlowRow( appIcons = appIcons, addButtonWidgetId = addButtonWidgetId, onWidgetInteraction = onWidgetInteraction, - onAddButtonToggle = onAddButtonToggle + onAddButtonToggle = onAddButtonToggle, ) }, previewContainerWidthPx = widgetSizeGroup.previewContainerWidthPx, @@ -184,21 +185,19 @@ private fun Previews( Box( contentAlignment = Alignment.BottomCenter, modifier = - Modifier - .fillMaxSize() - .clearAndSetSemantics { - traversalIndex = index.toFloat() - testTag = buildWidgetPickerTestTag(WIDGET_PREVIEW_TEST_TAG) - }, + Modifier.fillMaxSize().clearAndSetSemantics { + traversalIndex = index.toFloat() + testTag = buildWidgetPickerTestTag(WIDGET_PREVIEW_TEST_TAG) + }, ) { WidgetPreview( id = widgetItem.id, sizeInfo = widgetItem.sizeInfo, preview = widgetPreview, - appwidgetInfo = widgetItem.appWidgetProviderInfo, + widgetInfo = widgetItem.widgetInfo, showDragShadow = showDragShadow, onWidgetInteraction = onWidgetInteraction, - onAddButtonToggle = onAddButtonToggle + onAddButtonToggle = onAddButtonToggle, ) } } @@ -211,7 +210,7 @@ private fun Details( addButtonWidgetId: WidgetId?, appIcons: Map, onWidgetInteraction: (WidgetInteractionInfo) -> Unit, - onAddButtonToggle: (WidgetId) -> Unit + onAddButtonToggle: (WidgetId) -> Unit, ) { widgets.forEachIndexed { index, widgetItem -> val appId = widgetItem.appId @@ -376,8 +375,8 @@ private fun Placeable.PlacementScope.placeRows( // Move to next row yPosition += measuredRow.tallestPreviewHeight + - measuredRow.tallestDetailsHeight + - rowVerticalSpacingPx + measuredRow.tallestDetailsHeight + + rowVerticalSpacingPx } } diff --git a/modules/widgetpicker/tests/multivalentScreenshotTests/src/com/android/launcher3/widgetpicker/ui/components/WidgetsGridTestSamples.kt b/modules/widgetpicker/tests/multivalentScreenshotTests/src/com/android/launcher3/widgetpicker/ui/components/WidgetsGridTestSamples.kt index f86e0ab4f4..29a15cab93 100644 --- a/modules/widgetpicker/tests/multivalentScreenshotTests/src/com/android/launcher3/widgetpicker/ui/components/WidgetsGridTestSamples.kt +++ b/modules/widgetpicker/tests/multivalentScreenshotTests/src/com/android/launcher3/widgetpicker/ui/components/WidgetsGridTestSamples.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.unit.IntSize import com.android.launcher3.widgetpicker.shared.model.PickableWidget import com.android.launcher3.widgetpicker.shared.model.WidgetAppId import com.android.launcher3.widgetpicker.shared.model.WidgetId +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo import com.android.launcher3.widgetpicker.shared.model.WidgetPreview import com.android.launcher3.widgetpicker.shared.model.WidgetSizeInfo import com.android.launcher3.widgetpicker.tests.R @@ -253,7 +254,7 @@ object WidgetsGridTestSamples { containerWidthPx = cellWidth, containerHeightPx = cellHeight, ), - appWidgetProviderInfo = newAppWidgetInfo("OneByOneProvider"), + widgetInfo = WidgetInfo.AppWidgetInfo(newAppWidgetInfo("OneByOneProvider")), ) private fun twoByTwo(cellWidth: Int, cellHeight: Int) = @@ -262,7 +263,7 @@ object WidgetsGridTestSamples { appId = TEST_WIDGET_APP_ID, label = "Two by Two", description = null, - appWidgetProviderInfo = newAppWidgetInfo("TwoByTwoProvider"), + widgetInfo = WidgetInfo.AppWidgetInfo(newAppWidgetInfo("TwoByTwoProvider")), sizeInfo = WidgetSizeInfo( spanX = 2, @@ -283,7 +284,7 @@ object WidgetsGridTestSamples { appId = TEST_WIDGET_APP_ID, label = "Three by two", description = null, - appWidgetProviderInfo = newAppWidgetInfo("threeByTwoProvider"), + widgetInfo = WidgetInfo.AppWidgetInfo(newAppWidgetInfo("threeByTwoProvider")), sizeInfo = WidgetSizeInfo( spanX = 3, @@ -304,7 +305,7 @@ object WidgetsGridTestSamples { appId = TEST_WIDGET_APP_ID, label = "Four by two", description = null, - appWidgetProviderInfo = newAppWidgetInfo("FourByTwoProvider"), + widgetInfo = WidgetInfo.AppWidgetInfo(newAppWidgetInfo("FourByTwoProvider")), sizeInfo = WidgetSizeInfo( spanX = 4, diff --git a/modules/widgetpicker/tests/multivalentTests/src/com/android/launcher3/widgetpicker/TestUtils.kt b/modules/widgetpicker/tests/multivalentTests/src/com/android/launcher3/widgetpicker/TestUtils.kt index d65670177c..890191bea3 100644 --- a/modules/widgetpicker/tests/multivalentTests/src/com/android/launcher3/widgetpicker/TestUtils.kt +++ b/modules/widgetpicker/tests/multivalentTests/src/com/android/launcher3/widgetpicker/TestUtils.kt @@ -27,6 +27,7 @@ import com.android.launcher3.widgetpicker.shared.model.PickableWidget import com.android.launcher3.widgetpicker.shared.model.WidgetApp import com.android.launcher3.widgetpicker.shared.model.WidgetAppId import com.android.launcher3.widgetpicker.shared.model.WidgetId +import com.android.launcher3.widgetpicker.shared.model.WidgetInfo import com.android.launcher3.widgetpicker.shared.model.WidgetPreview import com.android.launcher3.widgetpicker.shared.model.WidgetSizeInfo import com.android.launcher3.widgetpicker.shared.model.WidgetUserProfile @@ -118,10 +119,13 @@ object TestUtils { appId = finalWidgetAppId, label = providerClassName, description = null, - appWidgetProviderInfo = AppWidgetProviderInfo().apply { - widgetCategory = category - provider = ComponentName.createRelative(PACKAGE_NAME, providerClassName) - }, + widgetInfo = + WidgetInfo.AppWidgetInfo( + AppWidgetProviderInfo().apply { + widgetCategory = category + provider = ComponentName.createRelative(PACKAGE_NAME, providerClassName) + } + ), sizeInfo = WidgetSizeInfo( spanX = 2, diff --git a/modules/widgetpicker/tests/multivalentTests/src/com/android/launcher3/widgetpicker/ui/components/WidgetInteractionsTest.kt b/modules/widgetpicker/tests/multivalentTests/src/com/android/launcher3/widgetpicker/ui/components/WidgetInteractionsTest.kt index 6e2fb01f08..83991fab0c 100644 --- a/modules/widgetpicker/tests/multivalentTests/src/com/android/launcher3/widgetpicker/ui/components/WidgetInteractionsTest.kt +++ b/modules/widgetpicker/tests/multivalentTests/src/com/android/launcher3/widgetpicker/ui/components/WidgetInteractionsTest.kt @@ -53,11 +53,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @AllowedDevices(allowed = [DeviceProduct.CF_PHONE]) class WidgetInteractionsTest { - @get:Rule - val limitDevicesRule = LimitDevicesRule() + @get:Rule val limitDevicesRule = LimitDevicesRule() - @get:Rule - val composeTestRule = createAndroidComposeRule() + @get:Rule val composeTestRule = createAndroidComposeRule() @Test fun tapPreview_andClickAdd() { @@ -66,7 +64,8 @@ class WidgetInteractionsTest { composeTestRule.waitForIdle() // tap on preview for widget 1 - composeTestRule.onAllNodesWithTag(PREVIEW_TEST_TAG) + composeTestRule + .onAllNodesWithTag(PREVIEW_TEST_TAG) .assertCountEquals(2) .onFirst() .performClick() @@ -74,18 +73,19 @@ class WidgetInteractionsTest { composeTestRule.waitForIdle() composeTestRule.onNodeWithText(WIDGET_ONE.label).isNotDisplayed() // label text not shown composeTestRule.onAllNodesWithText(ADD_BUTTON_TEXT).assertCountEquals(1) - composeTestRule.onNodeWithContentDescription(WIDGET_ONE_ADD_BUTTON_CONTENT_DESC) + composeTestRule + .onNodeWithContentDescription(WIDGET_ONE_ADD_BUTTON_CONTENT_DESC) .assertExists() .performClick() composeTestRule.waitForIdle() // widget interaction callback invoked and correct provider info returned. - composeTestRule.onNodeWithText(WIDGET_ONE.appWidgetProviderInfo.provider.toString()) - .assertExists() + composeTestRule.onNodeWithText(WIDGET_ONE.widgetInfo.toString()).assertExists() // tap again on preview for widget 1 - composeTestRule.onAllNodesWithTag(PREVIEW_TEST_TAG) + composeTestRule + .onAllNodesWithTag(PREVIEW_TEST_TAG) .assertCountEquals(2) .onFirst() .performClick() @@ -103,7 +103,8 @@ class WidgetInteractionsTest { composeTestRule.waitForIdle() // tap on preview for widget 1 - composeTestRule.onAllNodesWithTag(PREVIEW_TEST_TAG) + composeTestRule + .onAllNodesWithTag(PREVIEW_TEST_TAG) .assertCountEquals(2) .onFirst() .performClick() @@ -114,11 +115,13 @@ class WidgetInteractionsTest { composeTestRule.onNodeWithText(WIDGET_ONE.label).isNotDisplayed() composeTestRule.onNodeWithText(WIDGET_TWO.label).isNotDisplayed() composeTestRule.onAllNodesWithText(ADD_BUTTON_TEXT).assertCountEquals(1) - composeTestRule.onNodeWithContentDescription(WIDGET_ONE_ADD_BUTTON_CONTENT_DESC) + composeTestRule + .onNodeWithContentDescription(WIDGET_ONE_ADD_BUTTON_CONTENT_DESC) .assertExists() // tap on preview for widget 2 - composeTestRule.onAllNodesWithTag(PREVIEW_TEST_TAG) + composeTestRule + .onAllNodesWithTag(PREVIEW_TEST_TAG) .assertCountEquals(2) .onLast() .performClick() @@ -129,11 +132,11 @@ class WidgetInteractionsTest { composeTestRule.onNodeWithText(WIDGET_ONE.label).isDisplayed() composeTestRule.onNodeWithText(WIDGET_TWO.label).isNotDisplayed() composeTestRule.onAllNodesWithText(ADD_BUTTON_TEXT).assertCountEquals(1) - composeTestRule.onNodeWithContentDescription(WIDGET_TWO_ADD_BUTTON_CONTENT_DESC) + composeTestRule + .onNodeWithContentDescription(WIDGET_TWO_ADD_BUTTON_CONTENT_DESC) .assertExists() } - @Composable fun TapToAddTestComposable() { var provider by remember { mutableStateOf("invalid") } @@ -149,7 +152,7 @@ class WidgetInteractionsTest { showDragShadow = false, onWidgetInteraction = { widgetInteractionInfo -> if (widgetInteractionInfo is WidgetInteractionInfo.WidgetAddInfo) { - provider = widgetInteractionInfo.providerInfo.provider.toString() + provider = widgetInteractionInfo.widgetInfo.toString() } }, ) @@ -160,16 +163,18 @@ class WidgetInteractionsTest { private val WIDGET_ONE = PERSONAL_TEST_APPS[0].widgets[0] private val WIDGET_TWO = PERSONAL_TEST_APPS[1].widgets[0] - private val TEST_WIDGET_GROUP = WidgetSizeGroup( - previewContainerHeightPx = 200, - previewContainerWidthPx = 200, - widgets = listOf(WIDGET_ONE, WIDGET_TWO) - ) + private val TEST_WIDGET_GROUP = + WidgetSizeGroup( + previewContainerHeightPx = 200, + previewContainerWidthPx = 200, + widgets = listOf(WIDGET_ONE, WIDGET_TWO), + ) - private val PREVIEWS = mapOf( - WIDGET_ONE.id to TestUtils.createBitmapPreview(), - WIDGET_TWO.id to TestUtils.createBitmapPreview() - ) + private val PREVIEWS = + mapOf( + WIDGET_ONE.id to TestUtils.createBitmapPreview(), + WIDGET_TWO.id to TestUtils.createBitmapPreview(), + ) private val PREVIEW_TEST_TAG = buildWidgetPickerTestTag("widget_preview") private const val ADD_BUTTON_TEXT = "Add" diff --git a/res/values/config.xml b/res/values/config.xml index 05f489c65a..674b093d04 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -139,13 +139,6 @@ - com.google.android.calendar - com.google.android.deskclock - com.google.android.apps.maps - com.google.android.contacts - com.google.android.apps.chromecast.app - com.google.android.gm - com.google.android.videos diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 89c63511d5..8a76f8532e 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -21,6 +21,7 @@ import static android.view.View.MeasureSpec.makeMeasureSpec; import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -635,7 +636,8 @@ public abstract class DragView extends Fram // When widgets are dropped from another window, we don't want to remove the // dragView on resume of launcher. if (Flags.enableWidgetPickerRefactor() - && ((DragView) child).mItemType != ITEM_TYPE_APPWIDGET) { + && ((DragView) child).mItemType != ITEM_TYPE_APPWIDGET + && ((DragView) child).mItemType != ITEM_TYPE_DEEP_SHORTCUT) { dragLayer.removeView(child); } } diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java index 409174e068..235a46c800 100644 --- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java +++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java @@ -134,7 +134,7 @@ public abstract class ShortcutConfigActivityInfo implements CachedObject { @TargetApi(26) public static class ShortcutConfigActivityInfoVO extends ShortcutConfigActivityInfo { - private final LauncherActivityInfo mInfo; + public final LauncherActivityInfo mInfo; public ShortcutConfigActivityInfoVO(LauncherActivityInfo info) { super(info.getComponentName(), info.getUser(), diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java index 55404d68dc..48f75c73a5 100644 --- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java +++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java @@ -51,7 +51,6 @@ import com.android.launcher3.pm.ShortcutConfigActivityInfo; import com.android.launcher3.util.CancellableTask; import com.android.launcher3.util.Executors; import com.android.launcher3.util.LooperExecutor; -import com.android.launcher3.views.ActivityContext; import com.android.launcher3.widget.util.WidgetSizes; import java.util.concurrent.ExecutionException; @@ -272,8 +271,7 @@ public class DatabaseWidgetPreviewLoader { private Bitmap generateShortcutPreview( ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) { - int iconSize = ActivityContext.lookupContext( - mContext).getDeviceProfile().getAllAppsProfile().getIconSizePx(); + int iconSize = mDeviceProfile.getAllAppsProfile().getIconSizePx(); int padding = mContext.getResources() .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);