• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 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  *      https://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 package com.android.intentresolver.widget
18 
19 import android.content.Context
20 import android.graphics.Canvas
21 import android.graphics.Color
22 import android.graphics.Paint
23 import android.util.AttributeSet
24 import android.util.TypedValue
25 import android.view.InputDevice.SOURCE_MOUSE
26 import android.view.MotionEvent
27 import android.view.MotionEvent.ACTION_HOVER_ENTER
28 import android.view.MotionEvent.ACTION_HOVER_MOVE
29 import android.view.View
30 import android.widget.ImageView
31 import android.widget.LinearLayout
32 import com.android.intentresolver.R
33 
34 class ChooserTargetItemView(
35     context: Context,
36     attrs: AttributeSet?,
37     defStyleAttr: Int,
38     defStyleRes: Int,
39 ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
40     private val outlineRadius: Float
41     private val outlineWidth: Float
42     private val outlinePaint: Paint =
<lambda>null43         Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
44     private val outlineInnerPaint: Paint =
<lambda>null45         Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE }
46     private var iconView: ImageView? = null
47 
48     constructor(context: Context) : this(context, null)
49 
50     constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
51 
52     constructor(
53         context: Context,
54         attrs: AttributeSet?,
55         defStyleAttr: Int,
56     ) : this(context, attrs, defStyleAttr, 0)
57 
58     init {
59         val a = context.obtainStyledAttributes(attrs, R.styleable.ChooserTargetItemView)
60         val defaultWidth =
61             TypedValue.applyDimension(
62                 TypedValue.COMPLEX_UNIT_DIP,
63                 2f,
64                 context.resources.displayMetrics,
65             )
66         outlineRadius =
67             a.getDimension(R.styleable.ChooserTargetItemView_focusOutlineCornerRadius, 0f)
68         outlineWidth =
69             a.getDimension(R.styleable.ChooserTargetItemView_focusOutlineWidth, defaultWidth)
70 
71         outlinePaint.strokeWidth = outlineWidth
72         outlinePaint.color =
73             a.getColor(R.styleable.ChooserTargetItemView_focusOutlineColor, Color.TRANSPARENT)
74 
75         outlineInnerPaint.strokeWidth = outlineWidth
76         outlineInnerPaint.color =
77             a.getColor(R.styleable.ChooserTargetItemView_focusInnerOutlineColor, Color.TRANSPARENT)
78         a.recycle()
79     }
80 
onViewAddednull81     override fun onViewAdded(child: View) {
82         super.onViewAdded(child)
83         if (child is ImageView) {
84             iconView = child
85         }
86     }
87 
onViewRemovednull88     override fun onViewRemoved(child: View?) {
89         super.onViewRemoved(child)
90         if (child === iconView) {
91             iconView = null
92         }
93     }
94 
onHoverEventnull95     override fun onHoverEvent(event: MotionEvent): Boolean {
96         val iconView = iconView ?: return false
97         if (!isEnabled) return true
98         when (event.action) {
99             ACTION_HOVER_ENTER -> {
100                 iconView.isHovered = true
101             }
102             MotionEvent.ACTION_HOVER_EXIT -> {
103                 iconView.isHovered = false
104             }
105         }
106         return true
107     }
108 
onInterceptHoverEventnull109     override fun onInterceptHoverEvent(event: MotionEvent) =
110         if (event.isFromSource(SOURCE_MOUSE)) {
111             // This is the same logic as in super.onInterceptHoverEvent (ViewGroup) minus the check
112             // that the pointer fall on the scroll bar as we need to control the hover state of the
113             // icon.
114             // We also want to intercept only MOUSE hover events as the TalkBack's Explore by Touch
115             // (including single taps) reported as a hover event.
116             event.action == ACTION_HOVER_MOVE || event.action == ACTION_HOVER_ENTER
117         } else {
118             super.onInterceptHoverEvent(event)
119         }
120 
dispatchDrawnull121     override fun dispatchDraw(canvas: Canvas) {
122         super.dispatchDraw(canvas)
123         if (isFocused) {
124             drawFocusInnerOutline(canvas)
125             drawFocusOutline(canvas)
126         }
127     }
128 
drawFocusInnerOutlinenull129     private fun drawFocusInnerOutline(canvas: Canvas) {
130         val outlineOffset = outlineWidth + outlineWidth / 2
131         canvas.drawRoundRect(
132             outlineOffset,
133             outlineOffset,
134             maxOf(0f, width - outlineOffset),
135             maxOf(0f, height - outlineOffset),
136             outlineRadius - outlineWidth,
137             outlineRadius - outlineWidth,
138             outlineInnerPaint,
139         )
140     }
141 
drawFocusOutlinenull142     private fun drawFocusOutline(canvas: Canvas) {
143         val outlineOffset = outlineWidth / 2
144         canvas.drawRoundRect(
145             outlineOffset,
146             outlineOffset,
147             maxOf(0f, width - outlineOffset),
148             maxOf(0f, height - outlineOffset),
149             outlineRadius,
150             outlineRadius,
151             outlinePaint,
152         )
153     }
154 }
155