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