Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b7e9427d authored by Fengjiang Li's avatar Fengjiang Li Committed by Android (Google) Code Review
Browse files

Merge "[Predictive Back] Refactor...

Merge "[Predictive Back] Refactor setClipChildrenOnViewTree/restoreClipChildrenOnViewTree to support clipToPadding" into main
parents eef12ba5 ea7cb497
Loading
Loading
Loading
Loading
+0 −132
Original line number Diff line number Diff line
@@ -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}.
+159 −0
Original line number Diff line number Diff line
/*
 * 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 <T> type of [ViewGroup] attribute. For example, [T] is [Boolean] if modifying
     *   [ViewGroup.setClipChildren]
     */
    abstract class ViewGroupAttrModifier<T>(
        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<Boolean> =
        object : ViewGroupAttrModifier<Boolean>(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<Boolean> =
        object : ViewGroupAttrModifier<Boolean>(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)
        }
    }
}
+7 −4
Original line number Diff line number Diff line
@@ -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);
            }
        }
    }
+12 −10
Original line number Diff line number Diff line
@@ -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;
@@ -162,13 +162,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);
        }
    }

+118 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}