Unit testing ButtonDropTarget

Originally there was a bug in a method (isTextClippedVertically) in ButtonDropTarget. While attempting to write a unit test it became necessary to refactor ButtonDropTarget and DeleteDropTarget to decouple them from their dependency on launcher in order to allow for a unit test to be written

The pattern we are introducing here is to decouple Launcher from a controller, in order to facilitate easier testing.
Instead of calling Launcher.getLauncher() we call the method through ActivityContext, which has a testing wrapper already defined. Here is a diagram that explains the old and new pattern

Design Pattern: https://screenshot.googleplex.com/7apiE2DaGDrFzy9.png

Test: isTextClippedVerticallyTest
Bug: b/274402490

Change-Id: I1f3003d0b62dddbceb6e492b15aa5d7352d3a293
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
new file mode 100644
index 0000000..277f8b3
--- /dev/null
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -0,0 +1,119 @@
+package com.android.launcher3
+
+import android.content.ComponentName
+import android.view.View
+import com.android.launcher3.DropTarget.DragObject
+import com.android.launcher3.SecondaryDropTarget.DeferredOnComplete
+import com.android.launcher3.dragndrop.DragLayer
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.model.ModelWriter
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.util.IntSet
+import com.android.launcher3.util.PendingRequestArgs
+import com.android.launcher3.views.Snackbar
+
+/**
+ * Handler class for drop target actions that require modifying or interacting with launcher.
+ *
+ * This class is created by Launcher and provided the instance of launcher when created, which
+ * allows us to decouple drop target controllers from Launcher to enable easier testing.
+ */
+class DropTargetHandler(launcher: Launcher) {
+    val mLauncher: Launcher = launcher
+
+    val modelWriter: ModelWriter = mLauncher.modelWriter
+
+    fun onDropAnimationComplete() {
+        mLauncher.stateManager.goToState(LauncherState.NORMAL)
+    }
+
+    fun onSecondaryTargetCompleteDrop(target: ComponentName?, d: DragObject) {
+        when (val dragSource = d.dragSource) {
+            is DeferredOnComplete -> {
+                val deferred: DeferredOnComplete = dragSource
+                if (d.dragSource is SecondaryDropTarget.DeferredOnComplete) {
+                    target?.let {
+                        deferred.mPackageName = it.packageName
+                        mLauncher.addOnResumeCallback { deferred.onLauncherResume() }
+                    }
+                        ?: deferred.sendFailure()
+                }
+            }
+        }
+    }
+
+    fun reconfigureWidget(widgetId: Int, info: ItemInfo) {
+        mLauncher.setWaitingForResult(PendingRequestArgs.forWidgetInfo(widgetId, null, info))
+        mLauncher.appWidgetHolder.startConfigActivity(
+            mLauncher,
+            widgetId,
+            Launcher.REQUEST_RECONFIGURE_APPWIDGET
+        )
+    }
+
+    fun dismissPrediction(
+        announcement: CharSequence,
+        onActionClicked: Runnable,
+        onDismiss: Runnable?
+    ) {
+        mLauncher.dragLayer.announceForAccessibility(announcement)
+        Snackbar.show(mLauncher, R.string.item_removed, R.string.undo, onDismiss, onActionClicked)
+    }
+
+    fun getViewUnderDrag(info: ItemInfo): View? {
+        return if (
+            info is LauncherAppWidgetInfo &&
+                info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                mLauncher.workspace.dragInfo != null
+        ) {
+            mLauncher.workspace.dragInfo.cell
+        } else null
+    }
+
+    fun prepareToUndoDelete() {
+        mLauncher.modelWriter.prepareToUndoDelete()
+    }
+
+    fun onDeleteComplete(item: ItemInfo) {
+        var pageItem: ItemInfo = item
+        if (item.container <= 0) {
+            val v = mLauncher.workspace.getHomescreenIconByItemId(item.container)
+            v?.let { pageItem = v.tag as ItemInfo }
+        }
+        val pageIds =
+            if (pageItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP)
+                IntSet.wrap(pageItem.screenId)
+            else mLauncher.workspace.currentPageScreenIds
+        val onUndoClicked = Runnable {
+            mLauncher.setPagesToBindSynchronously(pageIds)
+            modelWriter.abortDelete()
+            mLauncher.statsLogManager.logger().log(LauncherEvent.LAUNCHER_UNDO)
+        }
+
+        Snackbar.show(
+            mLauncher,
+            R.string.item_removed,
+            R.string.undo,
+            modelWriter::commitDelete,
+            onUndoClicked
+        )
+    }
+
+    fun onAccessibilityDelete(view: View?, item: ItemInfo, announcement: CharSequence) {
+        // Remove the item from launcher and the db, we can ignore the containerInfo in this call
+        // because we already remove the drag view from the folder (if the drag originated from
+        // a folder) in Folder.beginDrag()
+        mLauncher.removeItem(view, item, true /* deleteFromDb */, "removed by accessibility drop")
+        mLauncher.workspace.stripEmptyScreens()
+        mLauncher.dragLayer.announceForAccessibility(announcement)
+    }
+
+    fun getDragLayer(): DragLayer {
+        return mLauncher.dragLayer
+    }
+
+    fun onClick(buttonDropTarget: ButtonDropTarget) {
+        mLauncher.accessibilityDelegate.handleAccessibleDrop(buttonDropTarget, null, null)
+    }
+}