blob: bfd190fa006a03fe8153ec14e0d9859f8d471ac0 [file]
/*
* Copyright (C) 2025 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.settings.accessibility;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.widget.FocusIndicatorDrawable;
import com.android.settingslib.widget.SettingsPreferenceGroupAdapter;
import com.android.settingslib.widget.SettingsThemeHelper;
/**
* PreferenceAdapterInSuw is a temporary fix on the padding issue introduced in the expressive style
* in SettingsPreferenceGroupAdapter. This adapter should be removed once the padding issue is
* resolved in the SettingsLib.
*
* <p>TODO(b/403645956): Remove this adapter
*/
public class PreferenceAdapterInSuw extends SettingsPreferenceGroupAdapter {
private static final int FOCUS_INDICATOR_CORNER_RADIUS_DP = 16;
private static final int FOCUS_INDICATOR_HORIZONTAL_PADDING_ADJUSTMENT_DP = 45;
private static final int FOCUS_INDICATOR_VERTICAL_PADDING_ADJUSTMENT_DP = -3;
private final int mListPreferredItemPaddingStart;
private final int mListPreferredItemPaddingEnd;
private final int mContentPadding;
public PreferenceAdapterInSuw(@NonNull PreferenceGroup preferenceGroup) {
super(preferenceGroup);
TypedArray resolvedAttributes =
preferenceGroup
.getContext()
.obtainStyledAttributes(
new int[] {
android.R.attr.listPreferredItemPaddingStart,
android.R.attr.listPreferredItemPaddingEnd
});
mListPreferredItemPaddingStart = resolvedAttributes.getDimensionPixelSize(0, 0);
mListPreferredItemPaddingEnd = resolvedAttributes.getDimensionPixelSize(1, 0);
resolvedAttributes.recycle();
mContentPadding =
preferenceGroup
.getContext()
.getResources()
.getDimensionPixelSize(
com.android.settingslib.widget.theme.R.dimen
.settingslib_expressive_space_small1);
}
@Override
public void onBindViewHolder(@NonNull PreferenceViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
View view = holder.itemView;
if (Flags.enableInsetFocusRingsInSuwReadOnly()) {
Context context = view.getContext();
if (context != null
&& context.getResources()
.getBoolean(
com.android.internal.R.bool.config_enableInsetFocusRingsInSuw)
&& Settings.Global.getInt(
context.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED,
0)
== 0) {
// These changes add the focus ring indicator to rows and options across the
// top-level Vision Settings page and most subpages.
FocusIndicatorDrawable.Builder builder =
new FocusIndicatorDrawable.Builder(context);
EffectivePositionInfo info = calculateEffectivePositionAndCount(position);
if (info.mPosition != -1) {
configureFocusIndicator(builder, info.mPosition, info.mCount);
} else {
// Default to rounding all corners if the item is not part of a selectable
// block.
builder.withCornerRadius(16);
}
Drawable focusDrawable = builder.build();
view.setForeground(focusDrawable);
}
}
int paddingTop = view.getPaddingTop();
int paddingBottom = view.getPaddingBottom();
if (shouldApplyPadding(view.getContext(), position)) {
view.setPaddingRelative(
mListPreferredItemPaddingStart + mContentPadding,
paddingTop,
mListPreferredItemPaddingEnd + mContentPadding,
paddingBottom);
} else {
view.setPaddingRelative(
mListPreferredItemPaddingStart,
paddingTop,
mListPreferredItemPaddingEnd,
paddingBottom);
}
}
/**
* Configures the {@link FocusIndicatorDrawable.Builder} for a given item. Subclasses can
* override this to provide custom focus ring appearances.
*
* @param builder The builder to configure.
* @param position The adapter position of the item.
* @param count The number of items in the adapter.
*/
protected void configureFocusIndicator(
@NonNull FocusIndicatorDrawable.Builder builder,
int position,
int count) {
builder.withHorizontalPaddingAdjustment(FOCUS_INDICATOR_HORIZONTAL_PADDING_ADJUSTMENT_DP)
.withVerticalPaddingAdjustment(FOCUS_INDICATOR_VERTICAL_PADDING_ADJUSTMENT_DP)
.withCornerRadius(FOCUS_INDICATOR_CORNER_RADIUS_DP)
.withPositionalCornerRadii(position, count);
}
private boolean shouldApplyPadding(@NonNull Context context, int position) {
if (SettingsThemeHelper.isExpressiveTheme(context)) {
return getRoundCornerDrawableRes(position, /* isSelected= */ false) != 0;
}
return false;
}
/**
* A data class to hold information about an item's position within a contiguous block of
* selectable preferences. This is used to apply correct positional styling, such as rounding
* the corners of the first and last items in the block.
*/
private static class EffectivePositionInfo {
/**
* The 0-indexed mPosition of the item within its contiguous block of selectable items. If
* the original item is not selectable, this will be {@code -1}.
*/
final int mPosition;
/**
* The total number of selectable items in the contiguous block. If the original item is not
* selectable, this will be {@code 0}.
*/
final int mCount;
EffectivePositionInfo(int position, int mCount) {
this.mPosition = position;
this.mCount = mCount;
}
}
/**
* Calculates the "effective" position and count for an item within a contiguous block of
* selectable preferences. This allows for correct styling of items that are visually grouped,
* such as applying rounded corners to only the top and bottom items of the group.
*
* <p>For example, in a list with [Unselectable, Selectable, Selectable, Unselectable], an item
* at adapter position 2 (the second selectable one) would have an effective position of 1 and
* an effective count of 2.
*
* @param currentPosition The adapter position of the item to check.
* @return An {@link EffectivePositionInfo} containing the item's relative position and the
* total count of the block. If the item at {@code currentPosition} is not selectable, the
* returned position will be -1 and the count will be 0.
*/
private EffectivePositionInfo calculateEffectivePositionAndCount(int currentPosition) {
if (!getItem(currentPosition).isSelectable()) {
return new EffectivePositionInfo(-1, 0);
}
// Find the start of the contiguous block of selectable items by searching backwards.
int blockStartIndex = currentPosition;
while (blockStartIndex > 0 && getItem(blockStartIndex - 1).isSelectable()) {
blockStartIndex--;
}
// Find the end of the contiguous block of selectable items by searching forwards.
int blockEndIndex = currentPosition;
while (blockEndIndex < getItemCount() - 1 && getItem(blockEndIndex + 1).isSelectable()) {
blockEndIndex++;
}
int effectiveCount = (blockEndIndex - blockStartIndex) + 1;
int effectivePosition = currentPosition - blockStartIndex;
return new EffectivePositionInfo(effectivePosition, effectiveCount);
}
}