From ea7cb497f5a5644477aafe7e76cb389524a4476f Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Mon, 18 Mar 2024 11:36:26 -0700 Subject: [PATCH] [Predictive Back] Refactor setClipChildrenOnViewTree/restoreClipChildrenOnViewTree to support clipToPadding Bug: 325930715 Test: Added unit test Flag: aconfig com.android.launcher3.enable_predictive_back_gesture TEAMFOOD Change-Id: I509546ac4ee1fa851cf0648d365a5348362267cc --- src/com/android/launcher3/Utilities.java | 132 --------------- src/com/android/launcher3/UtilitiesKt.kt | 159 ++++++++++++++++++ .../allapps/AllAppsTransitionController.java | 11 +- .../widget/picker/WidgetsTwoPaneSheet.java | 22 +-- .../com/android/launcher3/UtilitiesKtTest.kt | 118 +++++++++++++ 5 files changed, 296 insertions(+), 146 deletions(-) create mode 100644 src/com/android/launcher3/UtilitiesKt.kt create mode 100644 tests/src/com/android/launcher3/UtilitiesKtTest.kt diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index d3019c58d9..d44438f5bc 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -68,8 +68,6 @@ import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewParent; import android.view.animation.Interpolator; import androidx.annotation.ChecksSdkIntAtLeast; @@ -774,136 +772,6 @@ public final class Utilities { )); } - /** - * Recursively call {@link ViewGroup#setClipChildren(boolean)} from {@link View} to ts parent - * (direct or indirect) inclusive. This method will also save the old clipChildren value on each - * view with {@link View#setTag(int, Object)}, which can be restored in - * {@link #restoreClipChildrenOnViewTree(View, ViewParent)}. - * - * Note that if parent is null or not a parent of the view, this method will be applied all the - * way to root view. - * - * @param v child view - * @param parent direct or indirect parent of child view - * @param clipChildren whether we should clip children - */ - public static void setClipChildrenOnViewTree( - @Nullable View v, - @Nullable ViewParent parent, - boolean clipChildren) { - if (v == null) { - return; - } - - if (v instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) v; - boolean oldClipChildren = viewGroup.getClipChildren(); - if (oldClipChildren != clipChildren) { - v.setTag(R.id.saved_clip_children_tag_id, oldClipChildren); - viewGroup.setClipChildren(clipChildren); - } - } - - if (v == parent) { - return; - } - - if (v.getParent() instanceof View) { - setClipChildrenOnViewTree((View) v.getParent(), parent, clipChildren); - } - } - - /** - * Recursively call {@link ViewGroup#setClipChildren(boolean)} to restore clip children value - * set in {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} on view to its parent - * (direct or indirect) inclusive. - * - * Note that if parent is null or not a parent of the view, this method will be applied all the - * way to root view. - * - * @param v child view - * @param parent direct or indirect parent of child view - */ - public static void restoreClipChildrenOnViewTree( - @Nullable View v, @Nullable ViewParent parent) { - if (v == null) { - return; - } - if (v instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) v; - Object viewTag = viewGroup.getTag(R.id.saved_clip_children_tag_id); - if (viewTag instanceof Boolean) { - viewGroup.setClipChildren((boolean) viewTag); - viewGroup.setTag(R.id.saved_clip_children_tag_id, null); - } - } - - if (v == parent) { - return; - } - - if (v.getParent() instanceof View) { - restoreClipChildrenOnViewTree((View) v.getParent(), parent); - } - } - - /** - * Similar to {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} but is calling - * {@link ViewGroup#setClipToPadding}. - */ - public static void setClipToPaddingOnViewTree( - @Nullable View v, - @Nullable ViewParent parent, - boolean clipToPadding) { - if (v == null) { - return; - } - - if (v instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) v; - boolean oldClipToPadding = viewGroup.getClipToPadding(); - if (oldClipToPadding != clipToPadding) { - v.setTag(R.id.saved_clip_to_padding_tag_id, oldClipToPadding); - viewGroup.setClipToPadding(clipToPadding); - } - } - - if (v == parent) { - return; - } - - if (v.getParent() instanceof View) { - setClipToPaddingOnViewTree((View) v.getParent(), parent, clipToPadding); - } - } - - /** - * Similar to {@link #restoreClipChildrenOnViewTree(View, ViewParent)} but is calling - * {@link ViewGroup#setClipToPadding}. - */ - public static void restoreClipToPaddingOnViewTree( - @Nullable View v, @Nullable ViewParent parent) { - if (v == null) { - return; - } - if (v instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) v; - Object viewTag = viewGroup.getTag(R.id.saved_clip_to_padding_tag_id); - if (viewTag instanceof Boolean) { - viewGroup.setClipToPadding((boolean) viewTag); - viewGroup.setTag(R.id.saved_clip_to_padding_tag_id, null); - } - } - - if (v == parent) { - return; - } - - if (v.getParent() instanceof View) { - restoreClipToPaddingOnViewTree((View) v.getParent(), parent); - } - } - /** * Translates the {@code targetView} so that it overlaps with {@code exclusionBounds} as little * as possible, while remaining within {@code inclusionBounds}. diff --git a/src/com/android/launcher3/UtilitiesKt.kt b/src/com/android/launcher3/UtilitiesKt.kt new file mode 100644 index 0000000000..a207d57591 --- /dev/null +++ b/src/com/android/launcher3/UtilitiesKt.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3 + +import android.view.View +import android.view.ViewGroup +import android.view.ViewParent + +object UtilitiesKt { + + /** + * Modify [ViewGroup]'s attribute with type [T]. The overridden attribute is saved by calling + * [View.setTag] and can be later restored by [View.getTag]. + * + * @param type of [ViewGroup] attribute. For example, [T] is [Boolean] if modifying + * [ViewGroup.setClipChildren] + */ + abstract class ViewGroupAttrModifier( + private val targetAttrValue: T, + private val tagKey: Int + ) { + /** + * If [targetAttrValue] is different from existing view attribute returned from + * [getAttribute], this method will save existing attribute by calling [ViewGroup.setTag]. + * Then call [setAttribute] to set attribute with [targetAttrValue]. + */ + fun saveAndChangeAttribute(viewGroup: ViewGroup) { + val oldAttrValue = getAttribute(viewGroup) + if (oldAttrValue !== targetAttrValue) { + viewGroup.setTag(tagKey, oldAttrValue) + setAttribute(viewGroup, targetAttrValue) + } + } + + /** Restore saved attribute in [saveAndChangeAttribute] by calling [ViewGroup.getTag]. */ + @Suppress("UNCHECKED_CAST") + fun restoreAttribute(viewGroup: ViewGroup) { + val oldAttrValue: T = viewGroup.getTag(tagKey) as T ?: return + setAttribute(viewGroup, oldAttrValue) + viewGroup.setTag(tagKey, null) + } + + /** Subclass will override this method to decide how to get [ViewGroup] attribute. */ + abstract fun getAttribute(viewGroup: ViewGroup): T + + /** Subclass will override this method to decide how to set [ViewGroup] attribute. */ + abstract fun setAttribute(viewGroup: ViewGroup, attr: T) + } + + /** [ViewGroupAttrModifier] to call [ViewGroup.setClipChildren] to false. */ + @JvmField + val CLIP_CHILDREN_FALSE_MODIFIER: ViewGroupAttrModifier = + object : ViewGroupAttrModifier(false, R.id.saved_clip_children_tag_id) { + override fun getAttribute(viewGroup: ViewGroup): Boolean { + return viewGroup.clipChildren + } + + override fun setAttribute(viewGroup: ViewGroup, clipChildren: Boolean) { + viewGroup.clipChildren = clipChildren + } + } + + /** [ViewGroupAttrModifier] to call [ViewGroup.setClipToPadding] to false. */ + @JvmField + val CLIP_TO_PADDING_FALSE_MODIFIER: ViewGroupAttrModifier = + object : ViewGroupAttrModifier(false, R.id.saved_clip_to_padding_tag_id) { + override fun getAttribute(viewGroup: ViewGroup): Boolean { + return viewGroup.clipToPadding + } + + override fun setAttribute(viewGroup: ViewGroup, clipToPadding: Boolean) { + viewGroup.clipToPadding = clipToPadding + } + } + + /** + * Recursively call [ViewGroupAttrModifier.saveAndChangeAttribute] from [View] to its parent + * (direct or indirect) inclusive. + * + * [ViewGroupAttrModifier.saveAndChangeAttribute] will save the existing attribute value on each + * view with [View.setTag], which can be restored in [restoreAttributesOnViewTree]. + * + * Note that if parent is null or not a parent of the view, this method will be applied all the + * way to root view. + * + * @param v child view + * @param parent direct or indirect parent of child view + * @param modifiers list of [ViewGroupAttrModifier] to modify view attribute + */ + @JvmStatic + fun modifyAttributesOnViewTree( + v: View?, + parent: ViewParent?, + vararg modifiers: ViewGroupAttrModifier<*> + ) { + if (v == null) { + return + } + if (v is ViewGroup) { + for (modifier in modifiers) { + modifier.saveAndChangeAttribute(v) + } + } + if (v === parent) { + return + } + if (v.parent is View) { + modifyAttributesOnViewTree(v.parent as View, parent, *modifiers) + } + } + + /** + * Recursively call [ViewGroupAttrModifier.restoreAttribute]} to restore view attributes + * previously saved in [ViewGroupAttrModifier.saveAndChangeAttribute] on view to its parent + * (direct or indirect) inclusive. + * + * Note that if parent is null or not a parent of the view, this method will be applied all the + * way to root view. + * + * @param v child view + * @param parent direct or indirect parent of child view + * @param modifiers list of [ViewGroupAttrModifier] to restore view attributes + */ + @JvmStatic + fun restoreAttributesOnViewTree( + v: View?, + parent: ViewParent?, + vararg modifiers: ViewGroupAttrModifier<*> + ) { + if (v == null) { + return + } + if (v is ViewGroup) { + for (modifier in modifiers) { + modifier.restoreAttribute(v) + } + } + if (v === parent) { + return + } + if (v.parent is View) { + restoreAttributesOnViewTree(v.parent as View, parent, *modifiers) + } + } +} diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index aeef5b8ae4..367810986f 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -24,8 +24,9 @@ import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.Utilities.restoreClipChildrenOnViewTree; -import static com.android.launcher3.Utilities.setClipChildrenOnViewTree; +import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER; +import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree; +import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree; import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; @@ -305,9 +306,11 @@ public class AllAppsTransitionController if (hasScaleEffect != mHasScaleEffect) { mHasScaleEffect = hasScaleEffect; if (mHasScaleEffect) { - setClipChildrenOnViewTree(rv, mLauncher.getAppsView(), false); + modifyAttributesOnViewTree(rv, mLauncher.getAppsView(), + CLIP_CHILDREN_FALSE_MODIFIER); } else { - restoreClipChildrenOnViewTree(rv, mLauncher.getAppsView()); + restoreAttributesOnViewTree(rv, mLauncher.getAppsView(), + CLIP_CHILDREN_FALSE_MODIFIER); } } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java index 1e17252308..a9487b72ff 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -17,10 +17,10 @@ package com.android.launcher3.widget.picker; import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions; import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker; -import static com.android.launcher3.Utilities.restoreClipChildrenOnViewTree; -import static com.android.launcher3.Utilities.restoreClipToPaddingOnViewTree; -import static com.android.launcher3.Utilities.setClipChildrenOnViewTree; -import static com.android.launcher3.Utilities.setClipToPaddingOnViewTree; +import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER; +import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER; +import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree; +import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree; import android.content.Context; import android.graphics.Outline; @@ -156,13 +156,15 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } mOldIsBackSwipeProgressing = isBackSwipeProgressing; if (isBackSwipeProgressing) { - setClipChildrenOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent, false); - setClipChildrenOnViewTree(mRightPaneScrollView, (ViewParent) mContent, false); - setClipToPaddingOnViewTree(mRightPaneScrollView, (ViewParent) mContent, false); + modifyAttributesOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent, + CLIP_CHILDREN_FALSE_MODIFIER); + modifyAttributesOnViewTree(mRightPaneScrollView, (ViewParent) mContent, + CLIP_CHILDREN_FALSE_MODIFIER, CLIP_TO_PADDING_FALSE_MODIFIER); } else { - restoreClipChildrenOnViewTree(mPrimaryWidgetListView, mContent); - restoreClipChildrenOnViewTree(mRightPaneScrollView, mContent); - restoreClipToPaddingOnViewTree(mRightPaneScrollView, mContent); + restoreAttributesOnViewTree(mPrimaryWidgetListView, mContent, + CLIP_CHILDREN_FALSE_MODIFIER); + restoreAttributesOnViewTree(mRightPaneScrollView, mContent, + CLIP_CHILDREN_FALSE_MODIFIER, CLIP_TO_PADDING_FALSE_MODIFIER); } } diff --git a/tests/src/com/android/launcher3/UtilitiesKtTest.kt b/tests/src/com/android/launcher3/UtilitiesKtTest.kt new file mode 100644 index 0000000000..dd8387196a --- /dev/null +++ b/tests/src/com/android/launcher3/UtilitiesKtTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3 + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER +import com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER +import com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree +import com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree +import com.android.launcher3.util.ActivityContextWrapper +import com.android.launcher3.views.WidgetsEduView +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class UtilitiesKtTest { + + val context: Context = ActivityContextWrapper(ApplicationProvider.getApplicationContext()) + + private lateinit var rootView: WidgetsEduView + private lateinit var midView: ViewGroup + private lateinit var childView: View + @Before + fun setup() { + rootView = + LayoutInflater.from(context).inflate(R.layout.widgets_edu, null) as WidgetsEduView + midView = rootView.requireViewById(R.id.edu_view) + childView = rootView.requireViewById(R.id.edu_header) + } + + @Test + fun set_clipChildren_false() { + assertThat(rootView.clipChildren).isTrue() + assertThat(midView.clipChildren).isTrue() + + modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER) + + assertThat(rootView.clipChildren).isFalse() + assertThat(midView.clipChildren).isFalse() + } + + @Test + fun restore_clipChildren_true() { + assertThat(rootView.clipChildren).isTrue() + assertThat(midView.clipChildren).isTrue() + modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER) + assertThat(rootView.clipChildren).isFalse() + assertThat(midView.clipChildren).isFalse() + + restoreAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER) + + assertThat(rootView.clipChildren).isTrue() + assertThat(midView.clipChildren).isTrue() + } + + @Test + fun restore_clipChildren_skipRestoreMidView() { + assertThat(rootView.clipChildren).isTrue() + assertThat(midView.clipChildren).isTrue() + rootView.clipChildren = false + modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER) + assertThat(rootView.clipChildren).isFalse() + assertThat(midView.clipChildren).isFalse() + + restoreAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER) + + assertThat(rootView.clipChildren).isFalse() + assertThat(midView.clipChildren).isTrue() + } + + @Test + fun set_clipToPadding_false() { + assertThat(rootView.clipToPadding).isTrue() + assertThat(midView.clipToPadding).isTrue() + + modifyAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER) + + assertThat(rootView.clipToPadding).isFalse() + assertThat(midView.clipToPadding).isFalse() + } + + @Test + fun restore_clipToPadding_true() { + assertThat(rootView.clipToPadding).isTrue() + assertThat(midView.clipToPadding).isTrue() + modifyAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER) + assertThat(rootView.clipToPadding).isFalse() + assertThat(midView.clipToPadding).isFalse() + + restoreAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER) + + assertThat(rootView.clipToPadding).isTrue() + assertThat(midView.clipToPadding).isTrue() + } +}