1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settings.inputmethod; 17 18 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK; 19 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLUE; 20 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_GREEN; 21 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_PINK; 22 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_YELLOW; 23 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.graphics.BlendMode; 28 import android.graphics.BlendModeColorFilter; 29 import android.graphics.Color; 30 import android.graphics.drawable.Drawable; 31 import android.graphics.drawable.GradientDrawable; 32 import android.graphics.drawable.StateListDrawable; 33 import android.provider.Settings; 34 import android.util.AttributeSet; 35 import android.util.StateSet; 36 import android.view.Gravity; 37 import android.view.PointerIcon; 38 import android.view.View; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 42 import androidx.annotation.NonNull; 43 import androidx.annotation.Nullable; 44 import androidx.preference.Preference; 45 import androidx.preference.PreferenceViewHolder; 46 47 import com.android.settings.R; 48 import com.android.settingslib.Utils; 49 50 51 public class PointerFillStylePreference extends Preference { 52 53 @Nullable private LinearLayout mButtonHolder; 54 PointerFillStylePreference(@onNull Context context, @Nullable AttributeSet attrs)55 public PointerFillStylePreference(@NonNull Context context, @Nullable AttributeSet attrs) { 56 super(context, attrs); 57 setLayoutResource(R.layout.pointer_icon_fill_style_layout); 58 } 59 60 @Override onBindViewHolder(@onNull PreferenceViewHolder holder)61 public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { 62 super.onBindViewHolder(holder); 63 64 mButtonHolder = (LinearLayout) holder.findViewById(R.id.button_holder); 65 // Intercept hover events so setting row does not highlight when hovering buttons. 66 if (mButtonHolder != null) { 67 mButtonHolder.setOnHoverListener((v, e) -> true); 68 } 69 70 int currentStyle = getPreferenceDataStore().getInt(Settings.System.POINTER_FILL_STYLE, 71 POINTER_ICON_VECTOR_STYLE_FILL_BLACK); 72 initStyleButton(holder, R.id.button_black, POINTER_ICON_VECTOR_STYLE_FILL_BLACK, 73 currentStyle); 74 initStyleButton(holder, R.id.button_green, POINTER_ICON_VECTOR_STYLE_FILL_GREEN, 75 currentStyle); 76 initStyleButton(holder, R.id.button_yellow, POINTER_ICON_VECTOR_STYLE_FILL_YELLOW, 77 currentStyle); 78 initStyleButton(holder, R.id.button_pink, POINTER_ICON_VECTOR_STYLE_FILL_PINK, 79 currentStyle); 80 initStyleButton(holder, R.id.button_blue, POINTER_ICON_VECTOR_STYLE_FILL_BLUE, 81 currentStyle); 82 } 83 initStyleButton(@onNull PreferenceViewHolder holder, int id, int style, int currentStyle)84 private void initStyleButton(@NonNull PreferenceViewHolder holder, int id, int style, 85 int currentStyle) { 86 ImageView button = (ImageView) holder.findViewById(id); 87 if (button == null) { 88 return; 89 } 90 int[] attrs = {com.android.internal.R.attr.pointerIconVectorFill}; 91 try (TypedArray ta = getContext().obtainStyledAttributes( 92 PointerIcon.vectorFillStyleToResource(style), attrs)) { 93 button.setBackground(getBackgroundSelector(ta.getColor(0, Color.BLACK))); 94 } 95 button.setForeground(getForegroundDrawable(style, currentStyle)); 96 button.setForegroundGravity(Gravity.CENTER); 97 button.setOnClickListener( 98 (v) -> { 99 getPreferenceDataStore().putInt(Settings.System.POINTER_FILL_STYLE, style); 100 setButtonChecked(id); 101 }); 102 button.setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_ARROW)); 103 } 104 105 // Generate background instead of defining in XML so we can use res color from the platform. getBackgroundSelector(int color)106 private StateListDrawable getBackgroundSelector(int color) { 107 StateListDrawable background = new StateListDrawable(); 108 Resources res = getContext().getResources(); 109 int ovalSize = res.getDimensionPixelSize(R.dimen.pointer_fill_style_circle_diameter); 110 background.setBounds(0, 0, ovalSize, ovalSize); 111 112 // Add hovered state first! The order states are added matters for a StateListDrawable. 113 GradientDrawable hoveredOval = new GradientDrawable(); 114 hoveredOval.setColor(color); 115 int textColor = Utils.getColorAttr(getContext(), 116 com.android.internal.R.attr.materialColorOutline).getDefaultColor(); 117 hoveredOval.setStroke( 118 res.getDimensionPixelSize(R.dimen.pointer_fill_style_shape_hovered_stroke), 119 textColor); 120 hoveredOval.setSize(ovalSize, ovalSize); 121 hoveredOval.setBounds(0, 0, ovalSize, ovalSize); 122 hoveredOval.setCornerRadius(ovalSize / 2f); 123 background.addState(new int[]{android.R.attr.state_hovered}, hoveredOval); 124 125 GradientDrawable defaultOval = new GradientDrawable(); 126 defaultOval.setColor(color); 127 defaultOval.setStroke( 128 res.getDimensionPixelSize(R.dimen.pointer_fill_style_shape_default_stroke), 129 textColor); 130 defaultOval.setSize(ovalSize, ovalSize); 131 defaultOval.setBounds(0, 0, ovalSize, ovalSize); 132 defaultOval.setCornerRadius(ovalSize / 2f); 133 background.addState(StateSet.WILD_CARD, defaultOval); 134 135 return background; 136 } 137 getForegroundDrawable(int style, int currentStyle)138 private Drawable getForegroundDrawable(int style, int currentStyle) { 139 Resources res = getContext().getResources(); 140 int ovalSize = res.getDimensionPixelSize(R.dimen.pointer_fill_style_circle_diameter); 141 Drawable checkMark = getContext().getDrawable(R.drawable.ic_check_24dp); 142 int padding = res.getDimensionPixelSize(R.dimen.pointer_fill_style_circle_padding) / 2; 143 checkMark.setBounds(padding, padding, ovalSize - padding, ovalSize - padding); 144 checkMark.setColorFilter(new BlendModeColorFilter(Color.WHITE, BlendMode.SRC_IN)); 145 checkMark.setAlpha(style == currentStyle ? 255 : 0); 146 return checkMark; 147 } 148 setButtonChecked(int id)149 private void setButtonChecked(int id) { 150 if (mButtonHolder == null) { 151 return; 152 } 153 for (int i = 0; i < mButtonHolder.getChildCount(); i++) { 154 View child = mButtonHolder.getChildAt(i); 155 if (child != null) { 156 child.getForeground().setAlpha(child.getId() == id ? 255 : 0); 157 } 158 } 159 } 160 } 161