diff options
4 files changed, 147 insertions, 5 deletions
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml index 904b78c7f165..6f504efbaaf0 100644 --- a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml +++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml @@ -15,7 +15,7 @@ limitations under the License. --> -<LinearLayout +<com.android.settingslib.widget.BannerMessageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -23,6 +23,7 @@ style="@style/Banner.Preference.SettingsLib"> <RelativeLayout + android:id="@+id/top_row" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="8dp" @@ -49,6 +50,7 @@ android:id="@+id/banner_title" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingTop="0dp" android:paddingBottom="4dp" android:textAppearance="@style/Banner.Title.SettingsLib"/> @@ -56,6 +58,7 @@ android:id="@+id/banner_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingTop="0dp" android:paddingBottom="4dp" android:textAppearance="@style/Banner.Subtitle.SettingsLib" android:visibility="gone"/> @@ -87,4 +90,4 @@ android:layout_height="wrap_content" style="@style/Banner.ButtonText.SettingsLib"/> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</com.android.settingslib.widget.BannerMessageView>
\ No newline at end of file diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java new file mode 100644 index 000000000000..5ca6fb64db2d --- /dev/null +++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 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.settingslib.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.TouchDelegate; +import android.view.View; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; + +/** + * The view providing {@link BannerMessagePreference}. + * + * <p>Callers should not instantiate this view directly but rather through adding a + * {@link BannerMessagePreference} to a {@code PreferenceScreen}. + */ +public class BannerMessageView extends LinearLayout { + private Rect mTouchTargetForDismissButton; + + public BannerMessageView(Context context) { + super(context); + } + + public BannerMessageView(Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public BannerMessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public BannerMessageView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setupIncreaseTouchTargetForDismissButton(); + } + + private void setupIncreaseTouchTargetForDismissButton() { + if (mTouchTargetForDismissButton != null) { + // Already set up + return; + } + + // The dismiss button is in the 'top row' RelativeLayout for positioning, but this element + // does not have enough space to provide large touch targets. We therefore set the top + // target on this view. + View topRow = findViewById(R.id.top_row); + View dismissButton = findViewById(R.id.banner_dismiss_btn); + if (topRow == null || dismissButton == null || dismissButton.getVisibility() != VISIBLE) { + return; + } + + int minimum = + getResources() + .getDimensionPixelSize(R.dimen.settingslib_preferred_minimum_touch_target); + int width = dismissButton.getWidth(); + int height = dismissButton.getHeight(); + int widthIncrease = width < minimum ? minimum - width : 0; + int heightIncrease = height < minimum ? minimum - height : 0; + + // Compute the hit rect of dismissButton within the local co-orindate reference of this view + // (rather than it's direct parent topRow). + Rect hitRectWithinTopRow = new Rect(); + dismissButton.getHitRect(hitRectWithinTopRow); + Rect hitRectOfTopRowWithinThis = new Rect(); + topRow.getHitRect(hitRectOfTopRowWithinThis); + mTouchTargetForDismissButton = new Rect(); + mTouchTargetForDismissButton.left = + hitRectOfTopRowWithinThis.left + hitRectWithinTopRow.left; + mTouchTargetForDismissButton.right = + hitRectOfTopRowWithinThis.left + hitRectWithinTopRow.right; + mTouchTargetForDismissButton.top = + hitRectOfTopRowWithinThis.top + hitRectWithinTopRow.top; + mTouchTargetForDismissButton.bottom = + hitRectOfTopRowWithinThis.top + hitRectWithinTopRow.bottom; + + // Adjust the touch target rect to apply the necessary increase in width and height. + mTouchTargetForDismissButton.left -= + widthIncrease % 2 == 1 ? (widthIncrease / 2) + 1 : widthIncrease / 2; + mTouchTargetForDismissButton.top -= + heightIncrease % 2 == 1 ? (heightIncrease / 2) + 1 : heightIncrease / 2; + mTouchTargetForDismissButton.right += widthIncrease / 2; + mTouchTargetForDismissButton.bottom += heightIncrease / 2; + + setTouchDelegate(new TouchDelegate(mTouchTargetForDismissButton, dismissButton)); + } + +} diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml index acbf35946126..ddcc83eee4bf 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml @@ -18,4 +18,5 @@ <resources> <dimen name="app_preference_padding_start">20dp</dimen> <dimen name="app_icon_min_width">52dp</dimen> + <dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen> </resources> diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java index 6670ed307816..0a48f19a3021 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java @@ -20,7 +20,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.robolectric.Robolectric.setupActivity; +import static org.robolectric.Shadows.shadowOf; +import android.app.Activity; import android.content.Context; import android.graphics.ColorFilter; import android.graphics.PorterDuff; @@ -44,8 +47,8 @@ import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.Shadows; import org.robolectric.shadows.ShadowDrawable; +import org.robolectric.shadows.ShadowTouchDelegate; import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) @@ -58,6 +61,9 @@ public class BannerMessagePreferenceTest { private boolean mClickListenerCalled = false; private final View.OnClickListener mClickListener = v -> mClickListenerCalled = true; + private final int mMinimumTargetSize = + RuntimeEnvironment.application.getResources() + .getDimensionPixelSize(R.dimen.settingslib_preferred_minimum_touch_target); private static final int TEST_STRING_RES_ID = R.string.accessibility_banner_message_dismiss; @@ -81,6 +87,23 @@ public class BannerMessagePreferenceTest { } @Test + public void onBindViewHolder_andOnLayoutView_dismissButtonTouchDelegate_isCorrectSize() { + assumeAndroidS(); + mBannerPreference.setTitle("Title"); + mBannerPreference.setDismissButtonOnClickListener(mClickListener); + + mBannerPreference.onBindViewHolder(mHolder); + setupActivity(Activity.class).setContentView(mRootView); + + assertThat(mRootView.getTouchDelegate()).isNotNull(); + ShadowTouchDelegate delegate = shadowOf(mRootView.getTouchDelegate()); + assertThat(delegate.getBounds().width()).isAtLeast(mMinimumTargetSize); + assertThat(delegate.getBounds().height()).isAtLeast(mMinimumTargetSize); + assertThat(delegate.getDelegateView()) + .isEqualTo(mRootView.findViewById(R.id.banner_dismiss_btn)); + } + + @Test public void onBindViewHolder_whenSummarySet_shouldSetSummary() { mBannerPreference.setSummary("test"); @@ -157,7 +180,7 @@ public class BannerMessagePreferenceTest { mBannerPreference.onBindViewHolder(mHolder); ImageView mIcon = mRootView.findViewById(R.id.banner_icon); - ShadowDrawable shadowDrawable = Shadows.shadowOf(mIcon.getDrawable()); + ShadowDrawable shadowDrawable = shadowOf(mIcon.getDrawable()); assertThat(shadowDrawable.getCreatedFromResId()) .isEqualTo(R.drawable.settingslib_ic_cross); } @@ -168,7 +191,7 @@ public class BannerMessagePreferenceTest { mBannerPreference.onBindViewHolder(mHolder); ImageView mIcon = mRootView.findViewById(R.id.banner_icon); - ShadowDrawable shadowDrawable = Shadows.shadowOf(mIcon.getDrawable()); + ShadowDrawable shadowDrawable = shadowOf(mIcon.getDrawable()); assertThat(shadowDrawable.getCreatedFromResId()).isEqualTo(R.drawable.ic_warning); } @@ -478,11 +501,15 @@ public class BannerMessagePreferenceTest { private void assumeAndroidR() { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 30); + ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "R"); + ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false); // Reset view holder to use correct layout. } private void assumeAndroidS() { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 31); + ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "S"); + ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true); // Re-inflate view to update layout. setUpViewHolder(); } |