CellLayout.kt 34.6 KB
Newer Older
Amit Kumar's avatar
Amit Kumar committed
1
2
package foundation.e.blisslauncher.features.test

Amit Kumar's avatar
Amit Kumar committed
3
4
5
6
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.TimeInterpolator
import android.animation.ValueAnimator
Amit Kumar's avatar
Amit Kumar committed
7
import android.content.Context
Amit Kumar's avatar
Amit Kumar committed
8
import android.graphics.Bitmap
Amit Kumar's avatar
Amit Kumar committed
9
import android.graphics.Canvas
Amit Kumar's avatar
Amit Kumar committed
10
11
12
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Point
13
import android.graphics.Rect
Amit Kumar's avatar
Amit Kumar committed
14
import android.util.ArrayMap
Amit Kumar's avatar
Amit Kumar committed
15
16
17
import android.util.AttributeSet
import android.util.Log
import android.view.View
Amit Kumar's avatar
Amit Kumar committed
18
import android.view.ViewGroup
19
import android.view.animation.AnimationUtils
Amit Kumar's avatar
Amit Kumar committed
20
import android.widget.GridLayout
Amit Kumar's avatar
Amit Kumar committed
21
22
import androidx.annotation.IntDef
import foundation.e.blisslauncher.R
Amit Kumar's avatar
Amit Kumar committed
23
import foundation.e.blisslauncher.core.Utilities
24
import foundation.e.blisslauncher.core.database.model.LauncherItem
Amit Kumar's avatar
Amit Kumar committed
25
import foundation.e.blisslauncher.core.utils.Constants
26
import foundation.e.blisslauncher.features.launcher.Hotseat
Amit Kumar's avatar
Amit Kumar committed
27
28
29
import foundation.e.blisslauncher.features.test.anim.Interpolators
import foundation.e.blisslauncher.features.test.dragndrop.DropTarget
import foundation.e.blisslauncher.features.test.graphics.DragPreviewProvider
30
import java.lang.Double.MAX_VALUE
Amit Kumar's avatar
Amit Kumar committed
31
32
import java.util.ArrayList
import java.util.Arrays
33
import java.util.Stack
Amit Kumar's avatar
Amit Kumar committed
34
import kotlin.math.hypot
Amit Kumar's avatar
Amit Kumar committed
35
36
37
38
39

open class CellLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
40
) : GridLayout(context, attrs, defStyleAttr) {
Amit Kumar's avatar
Amit Kumar committed
41

42
    private var mItemPlacementDirty: Boolean = false
Amit Kumar's avatar
Amit Kumar committed
43
44
45
46
    private var mIsDragOverlapping: Boolean = false

    // When a drag operation is in progress, holds the nearest cell to the touch point
    private val mDragCell = IntArray(2)
Amit Kumar's avatar
Amit Kumar committed
47
    private val BACKGROUND_STATE_ACTIVE = intArrayOf(android.R.attr.state_active)
Amit Kumar's avatar
Amit Kumar committed
48
49
50
    private val BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET
    private var mDragging: Boolean = false

Amit Kumar's avatar
Amit Kumar committed
51
52
53
    private val DESTRUCTIVE_REORDER = false
    private val DEBUG_VISUALIZE_OCCUPIED = false

Amit Kumar's avatar
Amit Kumar committed
54
55
56
57
58
59
60
    private var mDropPending = false

    // These are temporary variables to prevent having to allocate a new object just to
    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
    val mTmpPoint = IntArray(2)
    val mTempLocation = IntArray(2)

Amit Kumar's avatar
Amit Kumar committed
61
62
63
64
    // These are temporary variables to prevent having to allocate a new object just to
    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
    private val mTmpCellXY = IntArray(2)

65
    @Retention(AnnotationRetention.SOURCE)
Amit Kumar's avatar
Amit Kumar committed
66
67
68
69
70
71
    @IntDef(WORKSPACE, HOTSEAT)
    annotation class ContainerType

    @ContainerType
    private var mContainerType = 0

Amit Kumar's avatar
Amit Kumar committed
72
73
74
75
    private val TAG = "CellLayout"

    private val launcher: TestActivity = TestActivity.getLauncher(context)
    private val dp = launcher.deviceProfile
Amit Kumar's avatar
Amit Kumar committed
76
77
78
79
80
    var mCountX = dp.inv.numColumns
    var mCountY = dp.inv.numRows

    var mOccupied = GridOccupancy(mCountX, mCountY)
    var mTmpOccupied = GridOccupancy(mCountX, mCountY)
Amit Kumar's avatar
Amit Kumar committed
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

    private val mIntersectingViews = ArrayList<View>()

    // These arrays are used to implement the drag visualization on x-large screens.
    // They are used as circular arrays, indexed by mDragOutlineCurrent.
    val mDragOutlines = arrayOfNulls<Rect>(4)

    val mDragOutlineAlphas = FloatArray(mDragOutlines.size)
    private val mDragOutlineAnims: Array<InterruptibleInOutAnimator?> =
        arrayOfNulls<InterruptibleInOutAnimator>(mDragOutlines.size)

    // Used as an index into the above 3 arrays; indicates which is the most current value.
    private var mDragOutlineCurrent = 0
    private val mDragOutlinePaint = Paint()
    private var mEaseOutInterpolator: TimeInterpolator? = null
Amit Kumar's avatar
Amit Kumar committed
96
97
98
99

    private var cellWidth: Int = 0
    private var cellHeight: Int = 0

Amit Kumar's avatar
Amit Kumar committed
100
101
    val mReorderAnimators: ArrayMap<LayoutParams, Animator> = ArrayMap<LayoutParams, Animator>()

Amit Kumar's avatar
Amit Kumar committed
102
103
104
105
106
107
    companion object {
        const val MODE_SHOW_REORDER_HINT = 0
        const val MODE_DRAG_OVER = 1
        const val MODE_ON_DROP = 2
        const val MODE_ON_DROP_EXTERNAL = 3
        const val MODE_ACCEPT_DROP = 4
Amit Kumar's avatar
Amit Kumar committed
108
109
110

        const val WORKSPACE = 0
        const val HOTSEAT = 1
Amit Kumar's avatar
Amit Kumar committed
111
112
    }

Amit Kumar's avatar
Amit Kumar committed
113
    init {
Amit Kumar's avatar
Amit Kumar committed
114
        val a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyleAttr, 0)
115
116
        mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE)
        Log.i(TAG, "Container type: $mContainerType")
Amit Kumar's avatar
Amit Kumar committed
117
118
        a.recycle()

Amit Kumar's avatar
Amit Kumar committed
119
120
        setWillNotDraw(false)
        clipToPadding = false
Amit Kumar's avatar
Amit Kumar committed
121
122
123
124
125
126
127
128
129
130
131
132
133

        // Initialize the data structures used for the drag visualization.
        mEaseOutInterpolator = Interpolators.DEACCEL_2_5 // Quint ease out

        for (i in mDragOutlines.indices) {
            mDragOutlines[i] = Rect(-1, -1, -1, -1)
        }
        mDragOutlinePaint.color = Color.RED

        // When dragging things around the home screens, we show a green outline of
        // where the item will land. The outlines gradually fade out, leaving a trail
        // behind the drag path.
        // Set up all the animations that are used to implement this fading.
Amit Kumar's avatar
Amit Kumar committed
134
        val duration = 900
Amit Kumar's avatar
Amit Kumar committed
135
        val fromAlphaValue = 0f
Amit Kumar's avatar
Amit Kumar committed
136
        val toAlphaValue = 128f
Amit Kumar's avatar
Amit Kumar committed
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

        Arrays.fill(mDragOutlineAlphas, fromAlphaValue)

        for (i in mDragOutlineAnims.indices) {
            val anim = InterruptibleInOutAnimator(
                this,
                duration.toLong(), fromAlphaValue, toAlphaValue
            )
            anim.animator.interpolator = mEaseOutInterpolator
            anim.animator.addUpdateListener { animation ->
                val outline = anim.tag as Bitmap?

                // If an animation is started and then stopped very quickly, we can still
                // get spurious updates we've cleared the tag. Guard against this.
                if (outline == null) {

                    val `val` = animation.animatedValue
                    Log.d(
                        TAG, "anim " + i + " update: " + `val` +
                            ", isStopped " + anim.isStopped
                    )
                    // Try to prevent it from continuing to run
                    animation.cancel()
                } else {
                    mDragOutlineAlphas[i] = animation.animatedValue as Float
                    this@CellLayout.invalidate(mDragOutlines[i])
                }
            }
            // The animation holds a reference to the drag outline bitmap as long is it's
            // running. This way the bitmap can be GCed when the animations are complete.
            anim.animator.addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    if ((animation as ValueAnimator).animatedValue as Float == 0f) {
                        anim.tag = null
                    }
                }
            })
            mDragOutlineAnims[i] = anim
        }
Amit Kumar's avatar
Amit Kumar committed
176
177
178
179
180
181
182
183
184
185
    }

    override fun onMeasure(widthSpec: Int, heightSpec: Int) {
        super.onMeasure(widthSpec, heightSpec)
        val widthSpecMode = MeasureSpec.getMode(widthSpec)
        val heightSpecMode = MeasureSpec.getMode(heightSpec)
        val widthSize = MeasureSpec.getSize(widthSpec)
        val heightSize = MeasureSpec.getSize(heightSpec)
        val childWidthSize = widthSize - (paddingLeft + paddingRight)
        val childHeightSize = heightSize - (paddingTop + paddingBottom)
Amit Kumar's avatar
Amit Kumar committed
186
187
        cellWidth = VariantDeviceProfile.calculateCellWidth(childWidthSize, mCountX)
        cellHeight = VariantDeviceProfile.calculateCellHeight(childHeightSize, mCountY)
Amit Kumar's avatar
Amit Kumar committed
188
189
190
191
192
193
194
195
196
        setMeasuredDimension(widthSize, heightSize)
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (child.visibility != View.GONE) {
                measureChild(child)
            }
        }
    }

Amit Kumar's avatar
Amit Kumar committed
197
198
    fun getContainerType() = mContainerType

199
200
201
202
203
204
205
206
    open fun getCellContentHeight(): Int {
        return Math.min(
            measuredHeight,
            launcher.deviceProfile
                .getCellHeight(if (parent is Hotseat) Constants.CONTAINER_HOTSEAT else Constants.CONTAINER_DESKTOP)
        )
    }

Amit Kumar's avatar
Amit Kumar committed
207
208
209
    open fun setGridSize(x: Int, y: Int) {
        mCountX = x
        mCountY = y
Amit Kumar's avatar
Amit Kumar committed
210
211
        columnCount = x
        rowCount = y
Amit Kumar's avatar
Amit Kumar committed
212
213
214
215
216
217
        mOccupied = GridOccupancy(mCountX, mCountY)
        mTmpOccupied = GridOccupancy(mCountX, mCountY)
        mTempRectStack.clear()
        requestLayout()
    }

218
    fun measureChild(child: View) {
Amit Kumar's avatar
Amit Kumar committed
219
220
221
222
        val lp = child.layoutParams as LayoutParams
        lp.rowSpec = spec(UNDEFINED)
        lp.columnSpec = spec(UNDEFINED)
        lp.width = cellWidth
Amit Kumar's avatar
Amit Kumar committed
223
        lp.height = cellHeight
Amit Kumar's avatar
Amit Kumar committed
224

Amit Kumar's avatar
Amit Kumar committed
225
        // Center the icon/folder
Amit Kumar's avatar
Amit Kumar committed
226
        val cHeight: Int = getCellContentHeight()
Amit Kumar's avatar
Amit Kumar committed
227
228
        val cellPaddingY = 0f.coerceAtLeast((lp.height - cHeight) / 2f).toInt()
        var cellPaddingX: Int
Amit Kumar's avatar
Amit Kumar committed
229
        if (mContainerType == WORKSPACE) {
Amit Kumar's avatar
Amit Kumar committed
230
231
232
233
            cellPaddingX = dp.workspaceCellPaddingXPx
        } else {
            cellPaddingX = (dp.edgeMarginPx / 2f).toInt()
        }
234
        child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0)
Amit Kumar's avatar
Amit Kumar committed
235
236
237
238
239
        val childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY)
        val childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY)
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
    }

Amit Kumar's avatar
Amit Kumar committed
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
    override fun onDraw(canvas: Canvas?) {
        val paint = mDragOutlinePaint
        for (i in mDragOutlines.indices) {
            val alpha = mDragOutlineAlphas[i]
            if (alpha > 0) {
                val b = mDragOutlineAnims[i]!!.tag as Bitmap
                paint.alpha = (alpha + .5f).toInt()
                canvas!!.drawBitmap(b, null, mDragOutlines[i]!!, paint)
            }
        }
        super.onDraw(canvas)
    }

    override fun shouldDelayChildPressedState(): Boolean {
        return false
    }

    override fun cancelLongPress() {
        super.cancelLongPress()
        // Cancel long press for all children
        val count = childCount
        for (i in 0 until count) {
            val child = getChildAt(i)
            child.cancelLongPress()
        }
    }

Amit Kumar's avatar
Amit Kumar committed
267
268
269
270
271
    override fun onViewAdded(child: View?) {
        super.onViewAdded(child)
        Log.d(TAG, "onViewAdded() called with: child = $child")
    }

Amit Kumar's avatar
Amit Kumar committed
272
273
274
275
276
277
278
    fun addViewToCellLayout(
        child: View,
        index: Int,
        childId: Int,
        params: LayoutParams,
        markCells: Boolean
    ): Boolean {
Amit Kumar's avatar
Amit Kumar committed
279
280
281
282
283
        val lp: LayoutParams = params

        // Hotseat icons - remove text
        if (child is IconTextView) {
            val bubbleChild: IconTextView = child
284

Amit Kumar's avatar
Amit Kumar committed
285
286
287
288
289
290
291
292
293
            bubbleChild.setTextVisibility(mContainerType != HOTSEAT)
        }

        child.scaleX = 1f
        child.scaleY = 1f

        // Generate an id for each view, this assumes we have at most 256x256 cells
        // per workspace screen
        if (index >= 0 && index <= mCountX * mCountY - 1) {
Amit Kumar's avatar
Amit Kumar committed
294

Amit Kumar's avatar
Amit Kumar committed
295
            child.id = childId
Amit Kumar's avatar
Amit Kumar committed
296
297
298
299
300
301
/*            val rowSpec = spec(UNDEFINED)
            val colSpec = spec(UNDEFINED)
            val iconLayoutParams = LayoutParams(rowSpec, colSpec)
            iconLayoutParams.height = launcher.deviceProfile.cellHeightPx
            iconLayoutParams.width = launcher.deviceProfile.cellWidthPx
            iconLayoutParams.setGravity(Gravity.CENTER)*/
Amit Kumar's avatar
Amit Kumar committed
302
303
            addView(child, index, lp)

Amit Kumar's avatar
Amit Kumar committed
304
            if (markCells) markCellsAsOccupiedForView(child)
Amit Kumar's avatar
Amit Kumar committed
305
306
307
            return true
        }
        return false
Amit Kumar's avatar
Amit Kumar committed
308
    }
309

Amit Kumar's avatar
Amit Kumar committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
    override fun removeAllViews() {
        mOccupied.clear()
        super.removeAllViews()
    }

    override fun removeView(view: View) {
        markCellsAsUnoccupiedForView(view)
        super.removeView(view)
    }

    override fun removeViewAt(index: Int) {
        markCellsAsUnoccupiedForView(getChildAt(index))
        super.removeViewAt(index)
    }

    override fun removeViews(start: Int, count: Int) {
        for (i in start until start + count) {
            markCellsAsUnoccupiedForView(getChildAt(i))
        }
        super.removeViews(start, count)
    }

Amit Kumar's avatar
Amit Kumar committed
332
333
334
335
336
337
338
339
340
341
342
    open fun setDropPending(pending: Boolean) {
        mDropPending = pending
    }

    open fun isDropPending(): Boolean {
        return mDropPending
    }

    open fun setIsDragOverlapping(isDragOverlapping: Boolean) {
        if (mIsDragOverlapping != isDragOverlapping) {
            mIsDragOverlapping = isDragOverlapping
343
            // mBackground.setState(if (mIsDragOverlapping) BACKGROUND_STATE_ACTIVE else BACKGROUND_STATE_DEFAULT)
Amit Kumar's avatar
Amit Kumar committed
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
            invalidate()
        }
    }

    /**
     * A drag event has begun over this layout.
     * It may have begun over this layout (in which case onDragChild is called first),
     * or it may have begun on another layout.
     */
    open fun onDragEnter() {
        mDragging = true
    }

    /**
     * Called when drag has left this CellLayout or has been completed (successfully or not)
     */
    open fun onDragExit() {
        // This can actually be called when we aren't in a drag, e.g. when adding a new
        // item to this layout via the customize drawer.
        // Guard against that case.
        if (mDragging) {
            mDragging = false
        }

        // Invalidate the drag data
        mDragCell[1] = -1
        mDragCell[0] = -1
Amit Kumar's avatar
Amit Kumar committed
371
372
373
        mDragOutlineAnims[mDragOutlineCurrent]?.animateOut()
        mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.size
        revertTempState()
Amit Kumar's avatar
Amit Kumar committed
374
375
376
377
        setIsDragOverlapping(false)
    }

    fun revertTempState() {
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/*        completeAndClearReorderPreviewAnimations()
        if (isItemPlacementDirty()) {
            val count: Int = getChildCount()
            for (i in 0 until count) {
                val child: View = getChildAt(i)
                val lp: com.android.launcher3.CellLayout.LayoutParams =
                    child.layoutParams as com.android.launcher3.CellLayout.LayoutParams
                if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
                    lp.tmpCellX = lp.cellX
                    lp.tmpCellY = lp.cellY
                    animateChildToPosition(
                        child, lp.cellX, lp.cellY, CellLayout.REORDER_ANIMATION_DURATION,
                        0, false, false
                    )
                }
            }
            setItemPlacementDirty(false)
        }*/
Amit Kumar's avatar
Amit Kumar committed
396
397
398
399
400
401
402
403
404
    }

    /**
     * Mark a child as having been dropped.
     * At the beginning of the drag operation, the child may have been on another
     * screen, but it is re-parented before this method is called.
     *
     * @param child The child that is being dropped
     */
Amit Kumar's avatar
Amit Kumar committed
405
406
407
    fun onDropChild(child: View?) {
        child?.also {
            val lp: LayoutParams = it.layoutParams as LayoutParams
408
            // lp.dropped = true
Amit Kumar's avatar
Amit Kumar committed
409
            child.requestLayout()
410
            markCellsAsOccupiedForView(child)
Amit Kumar's avatar
Amit Kumar committed
411
412
413
        }
    }

Amit Kumar's avatar
Amit Kumar committed
414
415
416
417
418
419
420
421
422
423
424
425
    open fun markCellsAsOccupiedForView(view: View?) {
        if (view == null || view.parent == null) return
        val parent = view.parent as ViewGroup
        mOccupied.markCells(parent.indexOfChild(view), true)
    }

    open fun markCellsAsUnoccupiedForView(view: View?) {
        if (view == null || view.parent == null) return
        val parent = view.parent as ViewGroup
        mOccupied.markCells(parent.indexOfChild(view), false)
    }

Amit Kumar's avatar
Amit Kumar committed
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
    open fun visualizeDropLocation(
        v: View?,
        outlineProvider: DragPreviewProvider?,
        cellX: Int,
        cellY: Int,
        resize: Boolean,
        dragObject: DropTarget.DragObject
    ) {
        val oldDragCellX = mDragCell[0]
        val oldDragCellY = mDragCell[1]
        if (outlineProvider?.generatedDragOutline == null) {
            return
        }
        val dragOutline: Bitmap = outlineProvider.generatedDragOutline
        if (cellX != oldDragCellX || cellY != oldDragCellY) {
            val dragOffset: Point = dragObject.dragView.dragVisualizeOffset
            val dragRegion: Rect = dragObject.dragView.dragRegion
            mDragCell[0] = cellX
            mDragCell[1] = cellY
            val oldIndex: Int = mDragOutlineCurrent
            mDragOutlineAnims[oldIndex]?.animateOut()
            mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.size
            val r: Rect = mDragOutlines[mDragOutlineCurrent]!!

            // Find the top left corner of the rect the object will occupy
            val topLeft = mTmpPoint
            cellToPoint(cellX, cellY, topLeft)
            var left = topLeft[0]
            var top = topLeft[1]
            if (v != null && dragOffset == null) {
                // When drawing the drag outline, it did not account for margin offsets
                // added by the view's parent.
                val lp = v.layoutParams as MarginLayoutParams
                left += lp.leftMargin
                top += lp.topMargin

                // Offsets due to the size difference between the View and the dragOutline.
                // There is a size difference to account for the outer blur, which may lie
                // outside the bounds of the view.
                top += (cellHeight * 1 - dragOutline.height) / 2
                // We center about the x axis
                left += (cellWidth * 1 - dragOutline.width) / 2
            } else {
                if (dragOffset != null && dragRegion != null) {
                    // Center the drag region *horizontally* in the cell and apply a drag
                    // outline offset
                    left += dragOffset.x + (cellWidth * 1 - dragRegion.width()) / 2
                    val cHeight: Int = getCellContentHeight()
                    val cellPaddingY =
                        Math.max(0f, (cellHeight - cHeight) / 2f).toInt()
                    top += dragOffset.y + cellPaddingY
                } else {
                    // Center the drag outline in the cell
                    left += (cellWidth * 1 - dragOutline.width) / 2
                    top += (cellHeight * 1 - dragOutline.height) / 2
                }
            }
            r[left, top, left + dragOutline.width] = top + dragOutline.height
            Utilities.scaleRectAboutCenter(r, 1f)
            mDragOutlineAnims[mDragOutlineCurrent]?.setTag(dragOutline)
            mDragOutlineAnims[mDragOutlineCurrent]?.animateIn()
        }
    }

    /**
     * Given a cell coordinate, return the point that represents the upper left corner of that cell
     *
     * @param cellX X coordinate of the cell
     * @param cellY Y coordinate of the cell
     *
     * @param result Array of 2 ints to hold the x and y coordinate of the point
     */
    open fun cellToPoint(cellX: Int, cellY: Int, result: IntArray) {
        val hStartPadding = paddingLeft
        val vStartPadding = paddingTop
        result[0] = hStartPadding + cellX * cellWidth
        result[1] = vStartPadding + cellY * cellHeight
    }

    fun getDistanceFromCell(x: Float, y: Float, cell: IntArray): Float {
        cellToCenterPoint(cell[0], cell[1], mTmpPoint)
        return Math.hypot((x - mTmpPoint[0]).toDouble(), (y - mTmpPoint[1]).toDouble())
            .toFloat()
    }

    // For a given cell and span, fetch the set of views intersecting the region.
    private fun getViewsIntersectingRegion(
        cellX: Int,
        cellY: Int,
        dragView: View,
        boundingRect: Rect?,
        intersectingViews: ArrayList<View>
    ) {
        boundingRect?.set(cellX, cellY, cellX + 1, cellY + 1)
        intersectingViews.clear()
        val r0 = Rect(cellX, cellY, cellX + 1, cellY + 1)
        val r1 = Rect()
        val count: Int = childCount
        for (i in 0 until count) {
            val child: View = getChildAt(i)
            if (child === dragView) continue
            r1[i % mCountX, i % mCountY, i % mCountX + 1] = i % mCountY + 1
            if (Rect.intersects(r0, r1)) {
                mIntersectingViews.add(child)
                boundingRect?.union(r1)
            }
        }
    }

    open fun isNearestDropLocationOccupied(
        pixelX: Int,
        pixelY: Int,
        dragView: View,
        result: IntArray?
    ): Boolean {
        var result = result
        result = findNearestArea(pixelX, pixelY, result)
        getViewsIntersectingRegion(
            result!![0], result!![1], dragView, null,
            mIntersectingViews
        )
        return !mIntersectingViews.isEmpty()
    }

550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
    /**
     * Find a starting cell position that will fit the given bounds nearest the requested
     * cell location. Uses Euclidean distance to score multiple vacant areas.
     *
     * @param pixelX The X location at which you want to search for a vacant area.
     * @param pixelY The Y location at which you want to search for a vacant area.
     * @param result Previously returned value to possibly recycle.
     * @return The X, Y cell of a vacant area that can contain this object,
     * nearest the requested location.
     */
    open fun findNearestArea(
        pixelX: Int,
        pixelY: Int,
        result: IntArray?
    ): IntArray? {
Amit Kumar's avatar
Amit Kumar committed
565
        return findNearestArea(pixelX, pixelY, false, result, null)
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
    }

    /**
     * Find a vacant area that will fit the given bounds nearest the requested
     * cell location. Uses Euclidean distance to score multiple vacant areas.
     *
     * @param pixelX The X location at which you want to search for a vacant area.
     * @param pixelY The Y location at which you want to search for a vacant area.
     * @param ignoreOccupied If true, the result can be an occupied cell
     * @param result Array in which to place the result, or null (in which case a new array will
     * be allocated)
     * @return The X, Y cell of a vacant area that can contain this object,
     * nearest the requested location.
     */
    private fun findNearestArea(
Amit Kumar's avatar
Amit Kumar committed
581
582
583
584
585
        pixelX: Int,
        pixelY: Int,
        ignoreOccupied: Boolean,
        result: IntArray?,
        resultSpan: IntArray?
586
587
588
589
590
591
592
593
594
595
596
597
    ): IntArray? {
        var pixelX = pixelX
        var pixelY = pixelY
        lazyInitTempRectStack()

        // Keep track of best-scoring drop area
        val bestXY = result ?: IntArray(2)
        var bestDistance: Double = MAX_VALUE
        val bestRect = Rect(-1, -1, -1, -1)
        val validRegions = Stack<Rect>()
        val countX: Int = mCountX
        val countY: Int = mCountY
Amit Kumar's avatar
Amit Kumar committed
598
599
600
601
602
603

        val minSpanX = 1
        val minSpanY = 1
        val spanX = 1
        val spanY = 1

604
        Log.d(TAG, "FINDING START")
605
606
607
608
609
610
611
612
        for (y in 0 until countY - (minSpanY - 1)) {
            inner@ for (x in 0 until countX - (minSpanX - 1)) {
                var ySize = -1
                var xSize = -1
                if (ignoreOccupied) {
                    // First, let's see if this thing fits anywhere
                    for (i in 0 until minSpanX) {
                        for (j in 0 until minSpanY) {
Amit Kumar's avatar
Amit Kumar committed
613
                            if (mOccupied.cells[(x + i) * countY + (y + j)]) {
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
                                continue@inner
                            }
                        }
                    }
                    xSize = minSpanX
                    ySize = minSpanY

                    // We know that the item will fit at _some_ acceptable size, now let's see
                    // how big we can make it. We'll alternate between incrementing x and y spans
                    // until we hit a limit.
                    var incX = true
                    var hitMaxX = xSize >= spanX
                    var hitMaxY = ySize >= spanY
                    while (!(hitMaxX && hitMaxY)) {
                        if (incX && !hitMaxX) {
                            for (j in 0 until ySize) {
Amit Kumar's avatar
Amit Kumar committed
630
                                if (x + xSize > countX - 1 || mOccupied.cells[(x + xSize) * countY + (y + j)]
631
632
633
634
635
636
637
638
639
640
                                ) {
                                    // We can't move out horizontally
                                    hitMaxX = true
                                }
                            }
                            if (!hitMaxX) {
                                xSize++
                            }
                        } else if (!hitMaxY) {
                            for (i in 0 until xSize) {
Amit Kumar's avatar
Amit Kumar committed
641
                                if (y + ySize > countY - 1 || mOccupied.cells[(x + i) * countY + (y + ySize)]
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
                                ) {
                                    // We can't move out vertically
                                    hitMaxY = true
                                }
                            }
                            if (!hitMaxY) {
                                ySize++
                            }
                        }
                        hitMaxX = hitMaxX or (xSize >= spanX)
                        hitMaxY = hitMaxY or (ySize >= spanY)
                        incX = !incX
                    }
                    incX = true
                    hitMaxX = xSize >= spanX
                    hitMaxY = ySize >= spanY
                }
                val cellXY = mTmpPoint
                cellToCenterPoint(x, y, cellXY)

                // We verify that the current rect is not a sub-rect of any of our previous
                // candidates. In this case, the current rect is disqualified in favour of the
                // containing rect.
                val currentRect: Rect = mTempRectStack.pop()
                currentRect[x, y, x + xSize] = y + ySize
                var contained = false
                for (r in validRegions) {
                    if (r.contains(currentRect)) {
                        contained = true
                        break
                    }
                }
                validRegions.push(currentRect)
                val distance =
Amit Kumar's avatar
Amit Kumar committed
676
                    hypot((cellXY[0] - pixelX).toDouble(), (cellXY[1] - pixelY).toDouble())
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
                if (distance <= bestDistance && !contained ||
                    currentRect.contains(bestRect)
                ) {
                    bestDistance = distance
                    bestXY[0] = x
                    bestXY[1] = y
                    if (resultSpan != null) {
                        resultSpan[0] = xSize
                        resultSpan[1] = ySize
                    }
                    bestRect.set(currentRect)
                }
            }
        }

        // Return -1, -1 if no suitable location found
        if (bestDistance == MAX_VALUE) {
            bestXY[0] = -1
            bestXY[1] = -1
        }
        recycleTempRects(validRegions)
698
        Log.d(TAG, "FINDING END")
699
700
701
        return bestXY
    }

Amit Kumar's avatar
Amit Kumar committed
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
    /**
     * Given a cell coordinate, return the point that represents the center of the cell
     *
     * @param cellX X coordinate of the cell
     * @param cellY Y coordinate of the cell
     *
     * @param result Array of 2 ints to hold the x and y coordinate of the point
     */
    open fun cellToCenterPoint(cellX: Int, cellY: Int, result: IntArray) {
        regionToCenterPoint(cellX, cellY, 1, 1, result)
    }

    /**
     * Given a cell coordinate and span return the point that represents the center of the regio
     *
     * @param cellX X coordinate of the cell
     * @param cellY Y coordinate of the cell
     *
     * @param result Array of 2 ints to hold the x and y coordinate of the point
     */
    open fun regionToCenterPoint(cellX: Int, cellY: Int, spanX: Int, spanY: Int, result: IntArray) {
        val hStartPadding = paddingLeft
        val vStartPadding = paddingTop
725
        Log.i(TAG, "regionToCenterPoint: $hStartPadding $vStartPadding")
Amit Kumar's avatar
Amit Kumar committed
726
727
728
729
        result[0] = hStartPadding + cellX * cellWidth + spanX * cellWidth / 2
        result[1] = vStartPadding + cellY * cellHeight + spanY * cellHeight / 2
    }

Amit Kumar's avatar
Amit Kumar committed
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
    /**
     * Find a vacant area that will fit the given bounds nearest the requested
     * cell location. Uses Euclidean distance to score multiple vacant areas.
     *
     * @param pixelX The X location at which you want to search for a vacant area.
     * @param pixelY The Y location at which you want to search for a vacant area.
     * @param minSpanX The minimum horizontal span required
     * @param minSpanY The minimum vertical span required
     * @param spanX Horizontal span of the object.
     * @param spanY Vertical span of the object.
     * @param result Array in which to place the result, or null (in which case a new array will
     * be allocated)
     * @return The X, Y cell of a vacant area that can contain this object,
     * nearest the requested location.
     */
    open fun findNearestVacantArea(
Amit Kumar's avatar
Amit Kumar committed
746
747
748
749
750
751
752
753
        pixelX: Int,
        pixelY: Int,
        minSpanX: Int,
        minSpanY: Int,
        spanX: Int,
        spanY: Int,
        result: IntArray?,
        resultSpan: IntArray?
Amit Kumar's avatar
Amit Kumar committed
754
755
756
757
758
759
760
    ): IntArray? {
        return findNearestArea(
            pixelX, pixelY, true,
            result, resultSpan
        )
    }

Amit Kumar's avatar
Amit Kumar committed
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
    private val mTempRectStack = Stack<Rect>()
    private fun lazyInitTempRectStack() {
        if (mTempRectStack.isEmpty()) {
            for (i in 0 until mCountX * mCountY) {
                mTempRectStack.push(Rect())
            }
        }
    }

    private fun recycleTempRects(used: Stack<Rect>) {
        while (!used.isEmpty()) {
            mTempRectStack.push(used.pop())
        }
    }

Amit Kumar's avatar
Amit Kumar committed
776
777
778
    fun getChildAt(x: Int, y: Int): View {
        return getChildAt(y * mCountX + x)
    }
779

Amit Kumar's avatar
Amit Kumar committed
780
781
782
783
784
785
786
    open fun performReorder(
        pixelX: Int,
        pixelY: Int,
        minSpanX: Int,
        minSpanY: Int,
        spanX: Int,
        spanY: Int,
787
        dragView: View,
Amit Kumar's avatar
Amit Kumar committed
788
789
790
791
        result: IntArray?,
        resultSpan: IntArray?,
        mode: Int
    ): IntArray? {
792

Amit Kumar's avatar
Amit Kumar committed
793
        // First we determine if things have moved enough to cause a different layout
Amit Kumar's avatar
Amit Kumar committed
794
795
        var result = result
        var resultSpan = resultSpan
Amit Kumar's avatar
Amit Kumar committed
796
        result = findNearestArea(pixelX, pixelY, result)
797
798
799
800
        Log.d(
            TAG,
            "performReorder() called with: resultX = ${result!![0]} ${result!![1]} mode = $mode"
        )
Amit Kumar's avatar
Amit Kumar committed
801
802
803
804
        if (resultSpan == null) {
            resultSpan = IntArray(2)
        }

805
806
807
        resultSpan[0] = 1
        resultSpan[1] = 1

Amit Kumar's avatar
Amit Kumar committed
808
809
        // We don't need to find for nearest area when the grid is already totally occupied.

810
811
812
813
        // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
        // committing anything or animating anything as we just want to determine if a solution
        // exists
        if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
814
            val parent = (dragView.parent as ViewGroup?)
815
            parent?.removeView(dragView)
816

Amit Kumar's avatar
Amit Kumar committed
817
818
819
            if (childCount == mCountX * mCountY) {
                return intArrayOf(-1, -1)
            }
820
821
            var index = result[1] * mCountX + result[0]

822
            // Handles the case when the icon is being dragged after the last item on the grid.
Amit Kumar's avatar
Amit Kumar committed
823
            if (index > childCount) {
824
                index = childCount
Amit Kumar's avatar
Amit Kumar committed
825
826
                result[0] = index % mCountX
                result[1] = index / mCountX
827
            }
828

829
            // appView.findViewById(R.id.app_label).setVisibility(GONE);
830
831
832
            val rowSpec = spec(UNDEFINED)
            val colSpec = spec(UNDEFINED)
            val iconLayoutParams = LayoutParams(rowSpec, colSpec)
833
            // iconLayoutParams.setGravity(Gravity.CENTER)
834
835
836
            iconLayoutParams.height = if (mContainerType == HOTSEAT)
                dp.hotseatCellHeightPx else dp.cellHeightPx
            iconLayoutParams.width = dp.cellWidthPx
837
            dragView.also {
Amit Kumar's avatar
Amit Kumar committed
838
                if (it is IconTextView) it.setTextVisibility(mContainerType != HOTSEAT)
839
            }
840
            dragView.layoutParams = iconLayoutParams
Amit Kumar's avatar
Amit Kumar committed
841
842
            addView(dragView, index)

Amit Kumar's avatar
Amit Kumar committed
843
844
845
846
847
848
849
            // Update item info after reordering so that we always save correct state in database.
            // TODO: May optimize this
            val item: LauncherItem = dragView.tag as LauncherItem
            item.container = if (mContainerType == HOTSEAT) Constants.CONTAINER_HOTSEAT else Constants.CONTAINER_DESKTOP
            item.cell = index
            (dragView as IconTextView).applyFromShortcutItem(item)

850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
            /*copySolutionToTempState(finalSolution, dragView)
            setItemPlacementDirty(true)
            animateItemsToSolution(finalSolution, dragView!!, mode == MODE_ON_DROP)
            if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
                commitTempPlacement()
                completeAndClearReorderPreviewAnimations()
                setItemPlacementDirty(false)
            } else {
                beginOrAdjustReorderPreviewAnimations(
                    finalSolution,
                    dragView!!,
                    CellLayout.REORDER_ANIMATION_DURATION,
                    ReorderPreviewAnimation.MODE_PREVIEW
                )
            }*/
Amit Kumar's avatar
Amit Kumar committed
865

866
867
868
869
870
871
872
873
874
            if ((mode == MODE_ON_DROP)) {
                if (index % 2 == 0) {
                    dragView.startAnimation(AnimationUtils.loadAnimation(context, R.anim.wobble))
                } else {
                    dragView.startAnimation(AnimationUtils.loadAnimation(context, R.anim.wobble_reverse))
                }
            }
        }
        requestLayout()
Amit Kumar's avatar
Amit Kumar committed
875
876
877
        return result
    }

878
879
880
881
882
883
884
885
    open fun setItemPlacementDirty(dirty: Boolean) {
        mItemPlacementDirty = dirty
    }

    open fun isItemPlacementDirty(): Boolean {
        return mItemPlacementDirty
    }

Amit Kumar's avatar
Amit Kumar committed
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
    /**
     * Computes a bounding rectangle for a range of cells
     *
     * @param cellX X coordinate of upper left corner expressed as a cell position
     * @param cellY Y coordinate of upper left corner expressed as a cell position
     * @param cellHSpan Width in cells
     * @param cellVSpan Height in cells
     * @param resultRect Rect into which to put the results
     */
    fun cellToRect(cellX: Int, cellY: Int, cellHSpan: Int, cellVSpan: Int, resultRect: Rect) {
        val cellWidth: Int = cellWidth
        val cellHeight: Int = cellHeight
        val hStartPadding = paddingLeft
        val vStartPadding = paddingTop
        val width = cellHSpan * cellWidth
        val height = cellVSpan * cellHeight
        val x = hStartPadding + cellX * cellWidth
        val y = vStartPadding + cellY * cellHeight
        resultRect[x, y, x + width] = y + height
Amit Kumar's avatar
Amit Kumar committed
905
906
    }

907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
    // This class stores info for two purposes:
    // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
    //    its spanX, spanY, and the screen it is on
    // 2. When long clicking on an empty cell in a CellLayout, we save information about the
    //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
    //    the CellLayout that was long clicked
    class CellInfo(v: View, info: LauncherItem) {
        val cell: View
        val screenId: Long
        val container: Long
        val rank: Int
        override fun toString(): String {
            return ("Cell[view=${cell.javaClass}, rank=$rank")
        }

        init {
            cell = v
            rank = info.cell
            screenId = info.screenId
            container = info.container
        }
    }
929

Amit Kumar's avatar
Amit Kumar committed
930
931
932
933
934
    fun isOccupied(x: Int, y: Int): Boolean {
        return if (x < mCountX && y < mCountY) {
            mOccupied.cells[x * mCountY + y]
        } else {
            throw RuntimeException("Position exceeds the bound of this CellLayout")
935
936
        }
    }
Amit Kumar's avatar
Amit Kumar committed
937
}