/* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.popup import android.content.Context import android.graphics.Rect import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.annotation.CallSuper import com.android.launcher3.AbstractFloatingView import com.android.launcher3.DragSource import com.android.launcher3.DropTarget.DragObject import com.android.launcher3.R import com.android.launcher3.dragndrop.DragController import com.android.launcher3.dragndrop.DragOptions import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.util.ShortcutUtil import com.android.launcher3.views.ActivityContext /** * Base popup container for shortcuts associated with the item {@code originalView} * * @param The activity on which the popup shows * @param context The context in which the popup is created. * @param originalView The view from which this popup was opened. */ open class PopupContainer(context: Context?, val originalView: View, val itemInfo: ItemInfo) : ArrowPopup(context), DragSource, DragController.DragListener, Popup where T : Context, T : ActivityContext { /** Here we hold the system shortcuts that we show for the Popup. */ // TODO b/441320297 var systemShortcutContainer: ViewGroup? = null /** If the distance the user drags surpasses this number, then we should start drag. */ val startDragThreshold = originalView.context.resources.getDimensionPixelSize( R.dimen.deep_shortcuts_start_drag_threshold ) @CallSuper override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean { if (ev.action == MotionEvent.ACTION_DOWN) { val dl = popupContainer if (!dl.isEventOverView(this, ev)) { // TODO: add WW log if want to log if tap closed deep shortcut container. close(true) // We let touches on the original view go through so that users can launch // the item with one tap. return !dl.isEventOverView(originalView, ev) } } return false } @CallSuper override fun getTargetObjectLocation(outPos: Rect) { popupContainer.getDescendantRectRelativeToSelf(originalView, outPos) outPos.top += originalView.paddingTop outPos.left += originalView.paddingLeft outPos.right -= originalView.paddingRight outPos.bottom = outPos.top + originalView.height } override fun isOfType(type: Int): Boolean { return (type and AbstractFloatingView.TYPE_ACTION_POPUP) != 0 } @CallSuper override fun onDragStart(dragObject: DragObject, options: DragOptions) { // Either the original item or one of the shortcuts was dragged. // Hide the container, but don't remove it yet because that interferes with touch events. mDeferContainerRemoval = true animateClose() } override fun onDropCompleted(target: View, d: DragObject, success: Boolean) {} @CallSuper override fun onDragEnd() { if (!isOpen) { if (mOpenCloseAnimator != null) { // Close animation is running. mDeferContainerRemoval = false } else { // Close animation is not running. if (mDeferContainerRemoval) { closeComplete() } } } } /** * Determines when the deferred drag should be started. * * Current behavior: * - Start the drag if the touch passes a certain distance from the original touch down. */ override fun createPreDragCondition(): DragOptions.PreDragCondition { return object : DragOptions.PreDragCondition { override fun shouldStartDrag(distanceDragged: Double): Boolean { return distanceDragged > startDragThreshold } override fun onPreDragStart(dragObject: DragObject) {} override fun onPreDragEnd(dragObject: DragObject, dragStarted: Boolean) {} } } companion object { /** Returns a PopupContainer which is already open or null */ @JvmStatic fun getOpen(context: T): PopupContainer<*>? where T : Context?, T : ActivityContext? { return getOpenView(context, TYPE_ACTION_POPUP) } /** Dismisses the popup if it is no longer valid */ @JvmStatic fun dismissInvalidPopup(activity: T) where T : Context?, T : ActivityContext? { val popup = getOpen(activity) val originalView = popup?.originalView if ( originalView != null && (!originalView.isAttachedToWindow || !ShortcutUtil.supportsShortcuts(popup?.itemInfo)) ) { popup.animateClose() } } /** * Creates a new instance of [PopupContainer]. * * @param context The context in which the popup will be created. * @param originalView The view that the popup is associated with. * @return A new instance of [PopupContainer]. */ @JvmStatic fun create(context: Context, originalView: View, itemInfo: ItemInfo): PopupContainer where T : Context, T : ActivityContext { val container = PopupContainer(context, originalView, itemInfo) container.id = R.id.popup_container container.clipChildren = false container.clipToPadding = false container.orientation = VERTICAL container.layoutParams = LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, ) return container } } }