1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 package com.android.wallpaper.picker.customization.ui.section
19 
20 import android.content.Context
21 import android.util.AttributeSet
22 import android.view.MotionEvent
23 import android.view.ViewConfiguration
24 import android.widget.FrameLayout
25 import com.android.wallpaper.R
26 import com.android.wallpaper.util.RtlUtils
27 import kotlin.math.pow
28 import kotlin.math.sqrt
29 
30 class ScreenPreviewClickView(
31     context: Context,
32     attrs: AttributeSet?,
33 ) :
34     FrameLayout(
35         context,
36         attrs,
37     ) {
38 
39     private var downX = 0f
40     private var downY = 0f
41     private var onPreviewClicked: (() -> Unit)? = null
42     // isStart true means the start side; otherwise the end side
43     private var onSideClicked: ((isStart: Boolean) -> Unit)? = null
44 
onInterceptTouchEventnull45     override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
46         if (event.actionMasked == MotionEvent.ACTION_DOWN) {
47             downX = event.x
48             downY = event.y
49         }
50 
51         // We want to intercept clicks so the Carousel MotionLayout child doesn't prevent users from
52         // clicking on the screen preview.
53         if (isClick(event, downX, downY)) {
54             val viewCenterX = width / 2F
55             val halfPreviewWidth =
56                 context.resources.getDimensionPixelSize(R.dimen.screen_preview_width) / 2F
57             val leftXBound = viewCenterX - halfPreviewWidth
58             val rightXBound = viewCenterX + halfPreviewWidth
59             val isRtl = RtlUtils.isRtl(context)
60             when {
61                 downX in (leftXBound..rightXBound) -> onPreviewClicked?.invoke()
62                 downX < leftXBound -> onSideClicked?.invoke(!isRtl)
63                 downX > rightXBound -> onSideClicked?.invoke(isRtl)
64             }
65             return true
66         }
67         return super.onInterceptTouchEvent(event)
68     }
69 
setOnPreviewClickedListenernull70     fun setOnPreviewClickedListener(onPreviewClicked: (() -> Unit)) {
71         this.onPreviewClicked = onPreviewClicked
72     }
73 
setOnSideClickedListenernull74     fun setOnSideClickedListener(onSideClicked: ((isStart: Boolean) -> Unit)) {
75         this.onSideClicked = onSideClicked
76     }
77 
78     companion object {
isClicknull79         private fun isClick(event: MotionEvent, downX: Float, downY: Float): Boolean {
80             return when {
81                 // It's not a click if the event is not an UP action (though it may become one
82                 // later, when/if an UP is received).
83                 event.actionMasked != MotionEvent.ACTION_UP -> false
84                 // It's not a click if too much time has passed between the down and the current
85                 // event.
86                 gestureElapsedTime(event) > ViewConfiguration.getTapTimeout() -> false
87                 // It's not a click if the touch traveled too far.
88                 distanceMoved(event, downX, downY) > ViewConfiguration.getTouchSlop() -> false
89                 // Otherwise, this is a click!
90                 else -> true
91             }
92         }
93 
94         /**
95          * Returns the distance that the pointer traveled in the touch gesture the given event is
96          * part of.
97          */
distanceMovednull98         private fun distanceMoved(event: MotionEvent, downX: Float, downY: Float): Float {
99             val deltaX = event.x - downX
100             val deltaY = event.y - downY
101             return sqrt(deltaX.pow(2) + deltaY.pow(2))
102         }
103 
104         /**
105          * Returns the elapsed time since the touch gesture the given event is part of has begun.
106          */
gestureElapsedTimenull107         private fun gestureElapsedTime(event: MotionEvent): Long {
108             return event.eventTime - event.downTime
109         }
110     }
111 }
112