e1d31e5406
Revert submission 33716851-revert-33672643-DataDeviceProfile1-XHYRAPLEBK Reason for revert: Error is fixed Reverted changes: /q/submissionid:33716851-revert-33672643-DataDeviceProfile1-XHYRAPLEBK Change-Id: Ib86824134955903c11e51e52dbfd6368aba1cd50
276 lines
11 KiB
Kotlin
276 lines
11 KiB
Kotlin
/*
|
|
* Copyright (C) 2024 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.launcher3.taskbar
|
|
|
|
import android.content.Context
|
|
import android.graphics.Bitmap
|
|
import android.view.MotionEvent
|
|
import android.view.View
|
|
import com.android.launcher3.AbstractFloatingView
|
|
import com.android.launcher3.R
|
|
import com.android.launcher3.Utilities
|
|
import com.android.launcher3.model.data.ItemInfo
|
|
import com.android.launcher3.popup.SystemShortcut
|
|
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN
|
|
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext
|
|
import com.android.launcher3.util.TouchController
|
|
import com.android.launcher3.views.ActivityContext
|
|
import com.android.quickstep.RecentsModel
|
|
import com.android.quickstep.SystemUiProxy
|
|
import com.android.quickstep.util.DesktopTask
|
|
import com.android.systemui.shared.recents.model.Task
|
|
import com.android.systemui.shared.recents.model.ThumbnailData
|
|
import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason
|
|
import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
|
|
|
|
/**
|
|
* A single menu item shortcut to execute displaying open instances of an app. Default interaction
|
|
* for [onClick] is to open the menu in a floating window. Touching one of the displayed tasks
|
|
* launches it.
|
|
*/
|
|
class ManageWindowsTaskbarShortcut<T>(
|
|
private val target: T,
|
|
private val itemInfo: ItemInfo?,
|
|
private val originalView: View,
|
|
private val controllers: TaskbarControllers,
|
|
) :
|
|
SystemShortcut<T>(
|
|
R.drawable.desktop_mode_ic_taskbar_menu_manage_windows,
|
|
R.string.manage_windows_option_taskbar,
|
|
target,
|
|
itemInfo,
|
|
originalView,
|
|
) where T : Context?, T : ActivityContext? {
|
|
private lateinit var taskbarShortcutAllWindowsView: TaskbarShortcutManageWindowsView
|
|
private val recentsModel = RecentsModel.INSTANCE[controllers.taskbarActivityContext]
|
|
|
|
override fun onClick(v: View?) {
|
|
val targetPackage = itemInfo?.getTargetPackage()
|
|
val targetUserId = itemInfo?.user?.identifier
|
|
val isTargetPackageTask: (Task) -> Boolean = { task ->
|
|
task.key?.packageName == targetPackage && task.key.userId == targetUserId
|
|
}
|
|
|
|
recentsModel.getTasks { tasks ->
|
|
val desktopTask = tasks.filterIsInstance<DesktopTask>().firstOrNull()
|
|
val packageDesktopTasks =
|
|
(desktopTask?.tasks ?: emptyList()).filter(isTargetPackageTask)
|
|
val nonDesktopPackageTasks =
|
|
tasks.flatMap { it.tasks }.filter { isTargetPackageTask(it) }
|
|
|
|
// Add tasks from the fetched tasks, deduplicating by task ID
|
|
val packageTasks =
|
|
(packageDesktopTasks + nonDesktopPackageTasks).distinctBy { it.key.id }
|
|
|
|
// Since fetching thumbnails is asynchronous, use `awaitedTaskIds` to gate until the
|
|
// tasks are ready to display
|
|
val awaitedTaskIds = packageTasks.map { it.key.id }.toMutableSet()
|
|
|
|
createAndShowTaskShortcutView(packageTasks, awaitedTaskIds)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes a list of tasks to generate thumbnails and create a taskbar shortcut view.
|
|
*
|
|
* Iterates through the tasks, retrieves thumbnails, and adds them to a list. When all
|
|
* thumbnails are processed, it creates a [TaskbarShortcutManageWindowsView] with the collected
|
|
* thumbnails and positions it appropriately.
|
|
*/
|
|
private fun createAndShowTaskShortcutView(tasks: List<Task>, pendingTaskIds: MutableSet<Int>) {
|
|
val taskList = arrayListOf<Pair<Int, Bitmap?>>()
|
|
|
|
tasks.forEach { task ->
|
|
recentsModel.thumbnailCache.getThumbnailInBackground(task) {
|
|
thumbnailData: ThumbnailData ->
|
|
pendingTaskIds.remove(task.key.id)
|
|
// Add the current pair of task id and ThumbnailData to the list of all tasks
|
|
if (thumbnailData.thumbnail != null) {
|
|
taskList.add(task.key.id to thumbnailData.thumbnail)
|
|
}
|
|
// If the set is empty, all thumbnails have been fetched
|
|
if (pendingTaskIds.isEmpty() && taskList.isNotEmpty()) {
|
|
createAndPositionTaskbarShortcut(taskList)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates and positions the [TaskbarShortcutManageWindowsView] with the provided thumbnails.
|
|
*/
|
|
private fun createAndPositionTaskbarShortcut(taskList: ArrayList<Pair<Int, Bitmap?>>) {
|
|
val onIconClickListener =
|
|
({ taskId: Int? ->
|
|
taskbarShortcutAllWindowsView.animateClose()
|
|
if (taskId != null) {
|
|
SystemUiProxy.INSTANCE.get(target)
|
|
.showDesktopApp(
|
|
taskId,
|
|
/* transition= */ null,
|
|
DesktopTaskToFrontReason.TASKBAR_MANAGE_WINDOW,
|
|
)
|
|
}
|
|
})
|
|
|
|
val onOutsideClickListener = { taskbarShortcutAllWindowsView.animateClose() }
|
|
|
|
taskbarShortcutAllWindowsView =
|
|
TaskbarShortcutManageWindowsView(
|
|
originalView,
|
|
controllers.taskbarOverlayController.requestWindow(),
|
|
taskList,
|
|
onIconClickListener,
|
|
onOutsideClickListener,
|
|
controllers,
|
|
)
|
|
|
|
// If the view is removed from elsewhere, reset the state to allow the taskbar to auto-stash
|
|
taskbarShortcutAllWindowsView.menuView.rootView.addOnAttachStateChangeListener(
|
|
object : View.OnAttachStateChangeListener {
|
|
override fun onViewAttachedToWindow(v: View) {
|
|
return
|
|
}
|
|
|
|
override fun onViewDetachedFromWindow(v: View) {
|
|
controllers.taskbarAutohideSuspendController.updateFlag(
|
|
FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
|
|
false,
|
|
)
|
|
controllers.taskbarPopupController.cleanUpMultiInstanceMenuReference()
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
/** Closes the multi-instance menu if it has been initialized. */
|
|
fun closeMultiInstanceMenu() {
|
|
if (::taskbarShortcutAllWindowsView.isInitialized) {
|
|
taskbarShortcutAllWindowsView.animateClose()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A view container for displaying the window of open instances of an app
|
|
*
|
|
* Handles showing the window snapshots, adding the carousel to the overlay, and closing it.
|
|
* Also acts as a touch controller to intercept touch events outside the carousel to close it.
|
|
*/
|
|
class TaskbarShortcutManageWindowsView(
|
|
private val originalView: View,
|
|
private val taskbarOverlayContext: TaskbarOverlayContext,
|
|
snapshotList: ArrayList<Pair<Int, Bitmap?>>,
|
|
onIconClickListener: (Int) -> Unit,
|
|
onOutsideClickListener: () -> Unit,
|
|
private val controllers: TaskbarControllers,
|
|
) :
|
|
ManageWindowsViewContainer(
|
|
originalView.context,
|
|
originalView.context.getColor(R.color.materialColorSurfaceBright),
|
|
),
|
|
TouchController {
|
|
private val taskbarActivityContext = controllers.taskbarActivityContext
|
|
|
|
init {
|
|
createAndShowMenuView(snapshotList, onIconClickListener, onOutsideClickListener)
|
|
taskbarOverlayContext.dragLayer.addTouchController(this)
|
|
animateOpen()
|
|
}
|
|
|
|
/** Adds the carousel menu to the taskbar overlay drag layer */
|
|
override fun addToContainer(menuView: ManageWindowsView) {
|
|
positionCarouselMenu()
|
|
|
|
controllers.taskbarAutohideSuspendController.updateFlag(
|
|
FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
|
|
true,
|
|
)
|
|
AbstractFloatingView.closeAllOpenViewsExcept(
|
|
taskbarActivityContext,
|
|
AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY,
|
|
)
|
|
menuView.rootView.minimumHeight = menuView.menuHeight
|
|
menuView.rootView.minimumWidth = menuView.menuWidth
|
|
|
|
taskbarOverlayContext.dragLayer?.addView(menuView.rootView)
|
|
menuView.rootView.requestFocus()
|
|
}
|
|
|
|
/**
|
|
* Positions the carousel menu relative to the taskbar and the calling app's icon.
|
|
*
|
|
* Calculates the Y position to place the carousel above the taskbar, and the X position to
|
|
* align with the calling app while ensuring it doesn't go beyond the screen edge.
|
|
*/
|
|
private fun positionCarouselMenu() {
|
|
val deviceProfile = taskbarActivityContext.deviceProfile
|
|
val margin =
|
|
context.resources.getDimension(
|
|
R.dimen.taskbar_multi_instance_menu_min_padding_from_screen_edge
|
|
)
|
|
|
|
// Calculate the Y position to place the carousel above the taskbar
|
|
menuView.rootView.y =
|
|
deviceProfile.deviceProperties.availableHeightPx -
|
|
menuView.menuHeight -
|
|
controllers.taskbarStashController.touchableHeight -
|
|
margin
|
|
|
|
// Calculate the X position to align with the calling app,
|
|
// but avoid clashing with the screen edge
|
|
menuView.rootView.translationX =
|
|
if (Utilities.isRtl(context.resources)) {
|
|
-(deviceProfile.deviceProperties.availableWidthPx - menuView.menuWidth) / 2f
|
|
} else {
|
|
val maxX =
|
|
deviceProfile.deviceProperties.availableWidthPx -
|
|
menuView.menuWidth -
|
|
margin
|
|
minOf(originalView.x, maxX)
|
|
}
|
|
}
|
|
|
|
/** Closes the carousel menu and removes it from the taskbar overlay drag layer */
|
|
override fun removeFromContainer() {
|
|
controllers.taskbarAutohideSuspendController.updateFlag(
|
|
FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
|
|
false,
|
|
)
|
|
taskbarOverlayContext.dragLayer?.removeView(menuView.rootView)
|
|
taskbarOverlayContext.dragLayer.removeTouchController(this)
|
|
controllers.taskbarPopupController.cleanUpMultiInstanceMenuReference()
|
|
}
|
|
|
|
/** TouchController implementations for closing the carousel when touched outside */
|
|
override fun onControllerTouchEvent(ev: MotionEvent?): Boolean {
|
|
return false
|
|
}
|
|
|
|
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
|
ev?.let {
|
|
if (
|
|
it.action == MotionEvent.ACTION_DOWN &&
|
|
!taskbarOverlayContext.dragLayer.isEventOverView(menuView.rootView, it)
|
|
) {
|
|
animateClose()
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
}
|