CellLayout.kt 35.5 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
14
15
import android.os.Handler
import android.os.Looper
Amit Kumar's avatar
Amit Kumar committed
16
import android.util.ArrayMap
Amit Kumar's avatar
Amit Kumar committed
17
18
import android.util.AttributeSet
import android.view.View
Amit Kumar's avatar
Amit Kumar committed
19
import android.view.ViewGroup
20
import android.view.animation.AnimationUtils
Amit Kumar's avatar
Amit Kumar committed
21
import android.widget.GridLayout
Amit Kumar's avatar
Amit Kumar committed
22
23
import androidx.annotation.IntDef
import foundation.e.blisslauncher.R
Amit Kumar's avatar
Amit Kumar committed
24
import foundation.e.blisslauncher.core.Utilities
25
import foundation.e.blisslauncher.core.database.model.ApplicationItem
26
import foundation.e.blisslauncher.core.database.model.LauncherItem
27
import foundation.e.blisslauncher.core.database.model.ShortcutItem
Amit Kumar's avatar
Amit Kumar committed
28
import foundation.e.blisslauncher.core.utils.Constants
29
import foundation.e.blisslauncher.features.launcher.Hotseat
Amit Kumar's avatar
Amit Kumar committed
30
31
32
import foundation.e.blisslauncher.features.test.anim.Interpolators
import foundation.e.blisslauncher.features.test.dragndrop.DropTarget
import foundation.e.blisslauncher.features.test.graphics.DragPreviewProvider
33
import foundation.e.blisslauncher.features.test.uninstall.UninstallHelper.isUninstallDisabled
34
import java.lang.Double.MAX_VALUE
Amit Kumar's avatar
Amit Kumar committed
35
36
import java.util.ArrayList
import java.util.Arrays
37
import java.util.Stack
Amit Kumar's avatar
Amit Kumar committed
38
import kotlin.math.hypot
Amit Kumar's avatar
Amit Kumar committed
39
40
41
42
43

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

46
    private var mItemPlacementDirty: Boolean = false
Amit Kumar's avatar
Amit Kumar committed
47
48
49
50
    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
51
    private val BACKGROUND_STATE_ACTIVE = intArrayOf(android.R.attr.state_active)
Amit Kumar's avatar
Amit Kumar committed
52
53
54
    private val BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET
    private var mDragging: Boolean = false

Amit Kumar's avatar
Amit Kumar committed
55
56
57
    private val DESTRUCTIVE_REORDER = false
    private val DEBUG_VISUALIZE_OCCUPIED = false

Amit Kumar's avatar
Amit Kumar committed
58
59
60
61
62
63
64
    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
65
66
67
68
    // 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)

69
    @Retention(AnnotationRetention.SOURCE)
Amit Kumar's avatar
Amit Kumar committed
70
71
72
73
74
75
    @IntDef(WORKSPACE, HOTSEAT)
    annotation class ContainerType

    @ContainerType
    private var mContainerType = 0

Amit Kumar's avatar
Amit Kumar committed
76
77
78
79
    private val TAG = "CellLayout"

    private val launcher: TestActivity = TestActivity.getLauncher(context)
    private val dp = launcher.deviceProfile
Amit Kumar's avatar
Amit Kumar committed
80
81
82
83
84
    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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

    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
100
101
102
103

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

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

Amit Kumar's avatar
Amit Kumar committed
106
107
108
109
110
111
    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
112
113
114

        const val WORKSPACE = 0
        const val HOTSEAT = 1
Amit Kumar's avatar
Amit Kumar committed
115
116
    }

Amit Kumar's avatar
Amit Kumar committed
117
    init {
Amit Kumar's avatar
Amit Kumar committed
118
        val a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyleAttr, 0)
119
        mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE)
Amit Kumar's avatar
Amit Kumar committed
120
121
        a.recycle()

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

        // 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
137
        val duration = 900
Amit Kumar's avatar
Amit Kumar committed
138
        val fromAlphaValue = 0f
Amit Kumar's avatar
Amit Kumar committed
139
        val toAlphaValue = 128f
Amit Kumar's avatar
Amit Kumar committed
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

        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
                    // 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
175
176
177
178
179
180
181
182
183
184
    }

    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
185
186
        cellWidth = VariantDeviceProfile.calculateCellWidth(childWidthSize, mCountX)
        cellHeight = VariantDeviceProfile.calculateCellHeight(childHeightSize, mCountY)
Amit Kumar's avatar
Amit Kumar committed
187
188
189
190
191
192
193
194
195
        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
196
197
    fun getContainerType() = mContainerType

198
199
200
201
202
203
204
205
    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
206
207
208
    open fun setGridSize(x: Int, y: Int) {
        mCountX = x
        mCountY = y
Amit Kumar's avatar
Amit Kumar committed
209
210
        columnCount = x
        rowCount = y
Amit Kumar's avatar
Amit Kumar committed
211
212
213
214
215
216
        mOccupied = GridOccupancy(mCountX, mCountY)
        mTmpOccupied = GridOccupancy(mCountX, mCountY)
        mTempRectStack.clear()
        requestLayout()
    }

217
218
219
220
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
    }

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

Amit Kumar's avatar
Amit Kumar committed
228
        // Center the icon/folder
Amit Kumar's avatar
Amit Kumar committed
229
        val cHeight: Int = getCellContentHeight()
Amit Kumar's avatar
Amit Kumar committed
230
231
        val cellPaddingY = 0f.coerceAtLeast((lp.height - cHeight) / 2f).toInt()
        var cellPaddingX: Int
Amit Kumar's avatar
Amit Kumar committed
232
        if (mContainerType == WORKSPACE) {
Amit Kumar's avatar
Amit Kumar committed
233
234
235
236
            cellPaddingX = dp.workspaceCellPaddingXPx
        } else {
            cellPaddingX = (dp.edgeMarginPx / 2f).toInt()
        }
237
        child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0)
Amit Kumar's avatar
Amit Kumar committed
238
239
240
241
242
        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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
    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
270
271
272
273
    override fun onViewAdded(child: View?) {
        super.onViewAdded(child)
    }

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

Amit Kumar's avatar
Amit Kumar committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
        val lp: LayoutParams = params

        // Hotseat icons - remove text
        if (child is IconTextView) {
            val bubbleChild: IconTextView = child
            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
296
            child.id = childId
Amit Kumar's avatar
Amit Kumar committed
297
298
            addView(child, index, lp)

Amit Kumar's avatar
Amit Kumar committed
299
            if (markCells) markCellsAsOccupiedForView(child)
Amit Kumar's avatar
Amit Kumar committed
300
301
302
            return true
        }
        return false
Amit Kumar's avatar
Amit Kumar committed
303
    }
304

Amit Kumar's avatar
Amit Kumar committed
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
    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
327
328
329
330
331
332
333
334
335
336
337
    open fun setDropPending(pending: Boolean) {
        mDropPending = pending
    }

    open fun isDropPending(): Boolean {
        return mDropPending
    }

    open fun setIsDragOverlapping(isDragOverlapping: Boolean) {
        if (mIsDragOverlapping != isDragOverlapping) {
            mIsDragOverlapping = isDragOverlapping
338
            // mBackground.setState(if (mIsDragOverlapping) BACKGROUND_STATE_ACTIVE else BACKGROUND_STATE_DEFAULT)
Amit Kumar's avatar
Amit Kumar committed
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
            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
366
367
368
        mDragOutlineAnims[mDragOutlineCurrent]?.animateOut()
        mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.size
        revertTempState()
Amit Kumar's avatar
Amit Kumar committed
369
370
371
372
        setIsDragOverlapping(false)
    }

    fun revertTempState() {
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
/*        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
391
392
393
394
395
396
397
398
399
    }

    /**
     * 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
400
401
402
    fun onDropChild(child: View?) {
        child?.also {
            val lp: LayoutParams = it.layoutParams as LayoutParams
403
            // lp.dropped = true
Amit Kumar's avatar
Amit Kumar committed
404
            child.requestLayout()
405
            markCellsAsOccupiedForView(child)
Amit Kumar's avatar
Amit Kumar committed
406
407
408
        }
    }

Amit Kumar's avatar
Amit Kumar committed
409
410
411
412
413
414
415
416
417
418
419
420
    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
421
422
423
424
425
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
    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()
    }

545
546
547
548
549
550
551
552
553
554
555
556
557
558
    /**
     * 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?
559
    ): IntArray {
Amit Kumar's avatar
Amit Kumar committed
560
        return findNearestArea(pixelX, pixelY, false, result, null)
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
    }

    /**
     * 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
576
577
578
579
580
        pixelX: Int,
        pixelY: Int,
        ignoreOccupied: Boolean,
        result: IntArray?,
        resultSpan: IntArray?
581
    ): IntArray {
582
583
584
585
586
587
588
589
590
591
592
        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
593
594
595
596
597
598

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

599
600
601
602
603
604
605
606
        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
607
                            if (mOccupied.cells[(x + i) * countY + (y + j)]) {
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
                                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
624
                                if (x + xSize > countX - 1 || mOccupied.cells[(x + xSize) * countY + (y + j)]
625
626
627
628
629
630
631
632
633
634
                                ) {
                                    // 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
635
                                if (y + ySize > countY - 1 || mOccupied.cells[(x + i) * countY + (y + ySize)]
636
637
638
639
640
641
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
                                ) {
                                    // 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
670
                    hypot((cellXY[0] - pixelX).toDouble(), (cellXY[1] - pixelY).toDouble())
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
                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)
        return bestXY
    }

Amit Kumar's avatar
Amit Kumar committed
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
    /**
     * 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
        result[0] = hStartPadding + cellX * cellWidth + spanX * cellWidth / 2
        result[1] = vStartPadding + cellY * cellHeight + spanY * cellHeight / 2
    }

Amit Kumar's avatar
Amit Kumar committed
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
    /**
     * 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
738
739
740
741
742
743
744
745
        pixelX: Int,
        pixelY: Int,
        minSpanX: Int,
        minSpanY: Int,
        spanX: Int,
        spanY: Int,
        result: IntArray?,
        resultSpan: IntArray?
Amit Kumar's avatar
Amit Kumar committed
746
747
748
749
750
751
752
    ): IntArray? {
        return findNearestArea(
            pixelX, pixelY, true,
            result, resultSpan
        )
    }

Amit Kumar's avatar
Amit Kumar committed
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
    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())
        }
    }

768
    fun getChildAt(x: Int, y: Int): View? {
Amit Kumar's avatar
Amit Kumar committed
769
770
        return getChildAt(y * mCountX + x)
    }
771

Amit Kumar's avatar
Amit Kumar committed
772
773
774
775
776
777
778
    open fun performReorder(
        pixelX: Int,
        pixelY: Int,
        minSpanX: Int,
        minSpanY: Int,
        spanX: Int,
        spanY: Int,
779
        dragView: View,
780
        result: IntArray,
Amit Kumar's avatar
Amit Kumar committed
781
782
783
        resultSpan: IntArray?,
        mode: Int
    ): IntArray? {
784

Amit Kumar's avatar
Amit Kumar committed
785
        // First we determine if things have moved enough to cause a different layout
Amit Kumar's avatar
Amit Kumar committed
786
787
        var result = result
        var resultSpan = resultSpan
Amit Kumar's avatar
Amit Kumar committed
788
        result = findNearestArea(pixelX, pixelY, result)
Amit Kumar's avatar
Amit Kumar committed
789
790
791
792
        if (resultSpan == null) {
            resultSpan = IntArray(2)
        }

793
794
795
        resultSpan[0] = 1
        resultSpan[1] = 1

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

798
799
800
801
        // 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) {
802
            val parent = (dragView.parent as ViewGroup?)
803
            parent?.removeView(dragView)
804

Amit Kumar's avatar
Amit Kumar committed
805
806
807
            if (childCount == mCountX * mCountY) {
                return intArrayOf(-1, -1)
            }
808
809
            var index = result[1] * mCountX + result[0]

810
            // Handles the case when the icon is being dragged after the last item on the grid.
Amit Kumar's avatar
Amit Kumar committed
811
            if (index > childCount) {
812
                index = childCount
Amit Kumar's avatar
Amit Kumar committed
813
814
                result[0] = index % mCountX
                result[1] = index / mCountX
815
            }
816

817
            // appView.findViewById(R.id.app_label).setVisibility(GONE);
818
819
820
            val rowSpec = spec(UNDEFINED)
            val colSpec = spec(UNDEFINED)
            val iconLayoutParams = LayoutParams(rowSpec, colSpec)
821
            // iconLayoutParams.setGravity(Gravity.CENTER)
822
823
824
            iconLayoutParams.height = if (mContainerType == HOTSEAT)
                dp.hotseatCellHeightPx else dp.cellHeightPx
            iconLayoutParams.width = dp.cellWidthPx
825
            dragView.also {
Amit Kumar's avatar
Amit Kumar committed
826
                if (it is IconTextView) it.setTextVisibility(mContainerType != HOTSEAT)
827
            }
828
            dragView.layoutParams = iconLayoutParams
Amit Kumar's avatar
Amit Kumar committed
829
830
            addView(dragView, index)

Amit Kumar's avatar
Amit Kumar committed
831
832
833
            // 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
834
835
            item.container =
                if (mContainerType == HOTSEAT) Constants.CONTAINER_HOTSEAT else Constants.CONTAINER_DESKTOP
Amit Kumar's avatar
Amit Kumar committed
836
837
838
            item.cell = index
            (dragView as IconTextView).applyFromShortcutItem(item)

839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
            /*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
854

855
856
857
858
859
860
861
862
863
            val handler = Handler(Looper.getMainLooper())
            handler.post {
                if ((mode == MODE_ON_DROP)) {
                    if (index % 2 == 0) {
                        dragView.startAnimation(
                            AnimationUtils.loadAnimation(
                                context,
                                R.anim.wobble
                            )
864
                        )
865
866
867
868
869
870
871
872
                    } else {
                        dragView.startAnimation(
                            AnimationUtils.loadAnimation(
                                context,
                                R.anim.wobble_reverse
                            )
                        )
                    }
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892

                    val info: LauncherItem = dragView.getTag() as LauncherItem
                    if ((info is ApplicationItem || info is ShortcutItem) && !isUninstallDisabled(
                            info.user.realHandle,
                            context
                        )
                    ) {
                        // Return early if this app is system app
                        if (info is ApplicationItem) {
                            if (info.isSystemApp != ApplicationItem.FLAG_SYSTEM_UNKNOWN) {
                                if (info.isSystemApp and ApplicationItem.FLAG_SYSTEM_NO != 0) {
                                    dragView.applyUninstallIconState(true)
                                }
                            } else {
                                dragView.applyUninstallIconState(true)
                            }
                        } else if (info is ShortcutItem) {
                            dragView.applyUninstallIconState(true)
                        }
                    }
893
894
895
896
                }
            }
        }
        requestLayout()
Amit Kumar's avatar
Amit Kumar committed
897
898
899
        return result
    }

900
901
902
903
904
905
906
907
    open fun setItemPlacementDirty(dirty: Boolean) {
        mItemPlacementDirty = dirty
    }

    open fun isItemPlacementDirty(): Boolean {
        return mItemPlacementDirty
    }

Amit Kumar's avatar
Amit Kumar committed
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
    /**
     * 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
927
928
    }

929
930
931
932
933
934
935
    // 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) {
936
937
938
939
940
        val cell: View = v
        val screenId: Int = info.screenId
        val container: Long = info.container
        val rank: Int = info.cell

941
942
943
944
        override fun toString(): String {
            return ("Cell[view=${cell.javaClass}, rank=$rank")
        }
    }
945

Amit Kumar's avatar
Amit Kumar committed
946
947
    fun getMaxChildCount(): Int = mCountX * mCountY

948
949
950
    fun isOccupied(cellIdx: Int): Boolean {
        return if (cellIdx < mOccupied.cells.size) {
            mOccupied.cells[cellIdx]
Amit Kumar's avatar
Amit Kumar committed
951
952
        } else {
            throw RuntimeException("Position exceeds the bound of this CellLayout")
953
954
        }
    }
Amit Kumar's avatar
Amit Kumar committed
955
}