diff --git a/res/layout/widgets_list_row_header_two_pane.xml b/res/layout/widgets_list_row_header_two_pane.xml index c0a6ea8226..bdb2aed604 100644 --- a/res/layout/widgets_list_row_header_two_pane.xml +++ b/res/layout/widgets_list_row_header_two_pane.xml @@ -23,6 +23,7 @@ android:importantForAccessibility="yes" android:focusable="true" launcher:appIconSize="48dp" + launcher:collapsable="false" android:descendantFocusability="afterDescendants" android:background="@drawable/bg_widgets_header_two_pane" > diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 754f0cbe47..4a0b5e8637 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -582,6 +582,7 @@ + diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java index b5e7401e10..3383299136 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java @@ -52,7 +52,11 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd private static final int[] EXPANDED_DRAWABLE_STATE = new int[] {android.R.attr.state_expanded}; private final int mIconSize; - + /** + * Indicates if the header is collapsable. For example, when displayed in a two pane layout, + * widget apps aren't collapsable. + */ + private final boolean mIsCollapsable; @Nullable private HandlerRunnable mIconLoadRequest; @Nullable private Drawable mIconDrawable; @Nullable private WidgetsListDrawableState mListDrawableState; @@ -79,6 +83,7 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0); mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize, grid.iconSizePx); + mIsCollapsable = a.getBoolean(R.styleable.WidgetsListRowHeader_collapsable, true); } @Override @@ -87,32 +92,36 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd mAppIcon = findViewById(R.id.app_icon); mTitle = findViewById(R.id.app_title); mSubtitle = findViewById(R.id.app_subtitle); - setAccessibilityDelegate(new AccessibilityDelegate() { + // Lists that cannot collapse, don't need EXPAND or COLLAPSE accessibility actions. + if (mIsCollapsable) { + setAccessibilityDelegate(new AccessibilityDelegate() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - if (mIsExpanded) { - info.removeAction(AccessibilityNodeInfo.ACTION_EXPAND); - info.addAction(AccessibilityNodeInfo.ACTION_COLLAPSE); - } else { - info.removeAction(AccessibilityNodeInfo.ACTION_COLLAPSE); - info.addAction(AccessibilityNodeInfo.ACTION_EXPAND); + @Override + public void onInitializeAccessibilityNodeInfo(View host, + AccessibilityNodeInfo info) { + if (mIsExpanded) { + info.removeAction(AccessibilityNodeInfo.ACTION_EXPAND); + info.addAction(AccessibilityNodeInfo.ACTION_COLLAPSE); + } else { + info.removeAction(AccessibilityNodeInfo.ACTION_COLLAPSE); + info.addAction(AccessibilityNodeInfo.ACTION_EXPAND); + } + super.onInitializeAccessibilityNodeInfo(host, info); } - super.onInitializeAccessibilityNodeInfo(host, info); - } - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - switch (action) { - case AccessibilityNodeInfo.ACTION_EXPAND: - case AccessibilityNodeInfo.ACTION_COLLAPSE: - callOnClick(); - return true; - default: - return super.performAccessibilityAction(host, action, args); + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + switch (action) { + case AccessibilityNodeInfo.ACTION_EXPAND: + case AccessibilityNodeInfo.ACTION_COLLAPSE: + callOnClick(); + return true; + default: + return super.performAccessibilityAction(host, action, args); + } } - } - }); + }); + } } /** Sets the expand toggle to expand / collapse. */ diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java new file mode 100644 index 0000000000..b347f07aca --- /dev/null +++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderAccessibilityTest.java @@ -0,0 +1,87 @@ +/* + * 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.widget.picker; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.R; +import com.android.launcher3.util.ActivityContextWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class WidgetsListHeaderAccessibilityTest { + private Context mContext; + private LayoutInflater mLayoutInflater; + @Mock + private View.OnClickListener mOnClickListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = new ActivityContextWrapper(getApplicationContext()); + mLayoutInflater = LayoutInflater.from( + new ContextThemeWrapper(mContext, R.style.WidgetContainerTheme)); + } + + @Test + public void singlePaneCollapsable_hasCustomAccessibilityActions() { + WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate( + R.layout.widgets_list_row_header, + new FrameLayout(mContext), false); + + assertThat(header.getAccessibilityDelegate()).isNotNull(); + + header.setOnClickListener(mOnClickListener); + header.getAccessibilityDelegate().performAccessibilityAction(header, + AccessibilityNodeInfo.ACTION_EXPAND, null); + header.getAccessibilityDelegate().performAccessibilityAction(header, + AccessibilityNodeInfo.ACTION_COLLAPSE, null); + + verify(mOnClickListener, times(2)).onClick(header); + } + + @Test + public void twoPaneNonCollapsable_noCustomAccessibilityDelegate() { + WidgetsListHeader header = (WidgetsListHeader) mLayoutInflater.inflate( + R.layout.widgets_list_row_header_two_pane, + new FrameLayout(mContext), false); + + assertThat(header.getAccessibilityDelegate()).isNull(); + } +}