1 /*
2  * Copyright (C) 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  *      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 package com.android.launcher3
18 
19 import android.view.View
20 import android.view.ViewGroup
21 import android.view.ViewParent
22 
23 object UtilitiesKt {
24 
25     /**
26      * Modify [ViewGroup]'s attribute with type [T]. The overridden attribute is saved by calling
27      * [View.setTag] and can be later restored by [View.getTag].
28      *
29      * @param <T> type of [ViewGroup] attribute. For example, [T] is [Boolean] if modifying
30      *   [ViewGroup.setClipChildren]
31      */
32     abstract class ViewGroupAttrModifier<T>(
33         private val targetAttrValue: T,
34         private val tagKey: Int
35     ) {
36         /**
37          * If [targetAttrValue] is different from existing view attribute returned from
38          * [getAttribute], this method will save existing attribute by calling [ViewGroup.setTag].
39          * Then call [setAttribute] to set attribute with [targetAttrValue].
40          */
saveAndChangeAttributenull41         fun saveAndChangeAttribute(viewGroup: ViewGroup) {
42             val oldAttrValue = getAttribute(viewGroup)
43             if (oldAttrValue !== targetAttrValue) {
44                 viewGroup.setTag(tagKey, oldAttrValue)
45                 setAttribute(viewGroup, targetAttrValue)
46             }
47         }
48 
49         /** Restore saved attribute in [saveAndChangeAttribute] by calling [ViewGroup.getTag]. */
50         @Suppress("UNCHECKED_CAST")
restoreAttributenull51         fun restoreAttribute(viewGroup: ViewGroup) {
52             val oldAttrValue: T = viewGroup.getTag(tagKey) as T ?: return
53             setAttribute(viewGroup, oldAttrValue)
54             viewGroup.setTag(tagKey, null)
55         }
56 
57         /** Subclass will override this method to decide how to get [ViewGroup] attribute. */
getAttributenull58         abstract fun getAttribute(viewGroup: ViewGroup): T
59 
60         /** Subclass will override this method to decide how to set [ViewGroup] attribute. */
61         abstract fun setAttribute(viewGroup: ViewGroup, attr: T)
62     }
63 
64     /** [ViewGroupAttrModifier] to call [ViewGroup.setClipChildren] to false. */
65     @JvmField
66     val CLIP_CHILDREN_FALSE_MODIFIER: ViewGroupAttrModifier<Boolean> =
67         object : ViewGroupAttrModifier<Boolean>(false, R.id.saved_clip_children_tag_id) {
68             override fun getAttribute(viewGroup: ViewGroup): Boolean {
69                 return viewGroup.clipChildren
70             }
71 
72             override fun setAttribute(viewGroup: ViewGroup, clipChildren: Boolean) {
73                 viewGroup.clipChildren = clipChildren
74             }
75         }
76 
77     /** [ViewGroupAttrModifier] to call [ViewGroup.setClipToPadding] to false. */
78     @JvmField
79     val CLIP_TO_PADDING_FALSE_MODIFIER: ViewGroupAttrModifier<Boolean> =
80         object : ViewGroupAttrModifier<Boolean>(false, R.id.saved_clip_to_padding_tag_id) {
getAttributenull81             override fun getAttribute(viewGroup: ViewGroup): Boolean {
82                 return viewGroup.clipToPadding
83             }
84 
setAttributenull85             override fun setAttribute(viewGroup: ViewGroup, clipToPadding: Boolean) {
86                 viewGroup.clipToPadding = clipToPadding
87             }
88         }
89 
90     /**
91      * Recursively call [ViewGroupAttrModifier.saveAndChangeAttribute] from [View] to its parent
92      * (direct or indirect) inclusive.
93      *
94      * [ViewGroupAttrModifier.saveAndChangeAttribute] will save the existing attribute value on each
95      * view with [View.setTag], which can be restored in [restoreAttributesOnViewTree].
96      *
97      * Note that if parent is null or not a parent of the view, this method will be applied all the
98      * way to root view.
99      *
100      * @param v child view
101      * @param parent direct or indirect parent of child view
102      * @param modifiers list of [ViewGroupAttrModifier] to modify view attribute
103      */
104     @JvmStatic
modifyAttributesOnViewTreenull105     fun modifyAttributesOnViewTree(
106         v: View?,
107         parent: ViewParent?,
108         vararg modifiers: ViewGroupAttrModifier<*>
109     ) {
110         if (v == null) {
111             return
112         }
113         if (v is ViewGroup) {
114             for (modifier in modifiers) {
115                 modifier.saveAndChangeAttribute(v)
116             }
117         }
118         if (v === parent) {
119             return
120         }
121         if (v.parent is View) {
122             modifyAttributesOnViewTree(v.parent as View, parent, *modifiers)
123         }
124     }
125 
126     /**
127      * Recursively call [ViewGroupAttrModifier.restoreAttribute]} to restore view attributes
128      * previously saved in [ViewGroupAttrModifier.saveAndChangeAttribute] on view to its parent
129      * (direct or indirect) inclusive.
130      *
131      * Note that if parent is null or not a parent of the view, this method will be applied all the
132      * way to root view.
133      *
134      * @param v child view
135      * @param parent direct or indirect parent of child view
136      * @param modifiers list of [ViewGroupAttrModifier] to restore view attributes
137      */
138     @JvmStatic
restoreAttributesOnViewTreenull139     fun restoreAttributesOnViewTree(
140         v: View?,
141         parent: ViewParent?,
142         vararg modifiers: ViewGroupAttrModifier<*>
143     ) {
144         if (v == null) {
145             return
146         }
147         if (v is ViewGroup) {
148             for (modifier in modifiers) {
149                 modifier.restoreAttribute(v)
150             }
151         }
152         if (v === parent) {
153             return
154         }
155         if (v.parent is View) {
156             restoreAttributesOnViewTree(v.parent as View, parent, *modifiers)
157         }
158     }
159 }
160