1 /* 2 * Copyright (C) 2022 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 17 package com.android.systemui.accessibility.floatingmenu; 18 19 import static android.util.TypedValue.COMPLEX_UNIT_PX; 20 import static android.view.View.MeasureSpec.AT_MOST; 21 import static android.view.View.MeasureSpec.UNSPECIFIED; 22 23 import android.annotation.SuppressLint; 24 import android.content.ComponentCallbacks; 25 import android.content.Context; 26 import android.content.res.Configuration; 27 import android.content.res.Resources; 28 import android.graphics.CornerPathEffect; 29 import android.graphics.Paint; 30 import android.graphics.PointF; 31 import android.graphics.Rect; 32 import android.graphics.drawable.GradientDrawable; 33 import android.graphics.drawable.ShapeDrawable; 34 import android.text.method.LinkMovementMethod; 35 import android.view.LayoutInflater; 36 import android.view.View; 37 import android.widget.FrameLayout; 38 import android.widget.LinearLayout; 39 import android.widget.TextView; 40 41 import androidx.annotation.NonNull; 42 43 import com.android.settingslib.Utils; 44 import com.android.systemui.R; 45 import com.android.systemui.recents.TriangleShape; 46 47 /** 48 * The tooltip view shows the information about the operation of the anchor view {@link MenuView} 49 * . It's just shown on the left or right of the anchor view. 50 */ 51 @SuppressLint("ViewConstructor") 52 class MenuEduTooltipView extends FrameLayout implements ComponentCallbacks { 53 private int mFontSize; 54 private int mTextViewMargin; 55 private int mTextViewPadding; 56 private int mTextViewCornerRadius; 57 private int mArrowMargin; 58 private int mArrowWidth; 59 private int mArrowHeight; 60 private int mArrowCornerRadius; 61 private int mColorAccentPrimary; 62 private View mArrowLeftView; 63 private View mArrowRightView; 64 private TextView mMessageView; 65 private final MenuViewAppearance mMenuViewAppearance; 66 MenuEduTooltipView(@onNull Context context, MenuViewAppearance menuViewAppearance)67 MenuEduTooltipView(@NonNull Context context, MenuViewAppearance menuViewAppearance) { 68 super(context); 69 70 mMenuViewAppearance = menuViewAppearance; 71 72 updateResources(); 73 initViews(); 74 } 75 76 @Override onConfigurationChanged(@onNull Configuration newConfig)77 public void onConfigurationChanged(@NonNull Configuration newConfig) { 78 updateResources(); 79 updateMessageView(); 80 updateArrowView(); 81 82 updateLocationAndVisibility(); 83 } 84 85 @Override onLowMemory()86 public void onLowMemory() { 87 // Do nothing. 88 } 89 90 @Override onAttachedToWindow()91 protected void onAttachedToWindow() { 92 super.onAttachedToWindow(); 93 94 getContext().registerComponentCallbacks(this); 95 } 96 97 @Override onDetachedFromWindow()98 protected void onDetachedFromWindow() { 99 super.onDetachedFromWindow(); 100 101 getContext().unregisterComponentCallbacks(this); 102 } 103 show(CharSequence message)104 void show(CharSequence message) { 105 mMessageView.setText(message); 106 107 updateLocationAndVisibility(); 108 } 109 updateLocationAndVisibility()110 void updateLocationAndVisibility() { 111 final boolean isTooltipOnRightOfAnchor = mMenuViewAppearance.isMenuOnLeftSide(); 112 updateArrowVisibilityWith(isTooltipOnRightOfAnchor); 113 updateLocationWith(getMenuBoundsInParent(), isTooltipOnRightOfAnchor); 114 } 115 116 /** 117 * Gets the bounds of the {@link MenuView}. Besides, its parent view {@link MenuViewLayer} is 118 * also the root view of the tooltip view. 119 * 120 * @return The menu bounds based on its parent view. 121 */ getMenuBoundsInParent()122 private Rect getMenuBoundsInParent() { 123 final Rect bounds = new Rect(); 124 final PointF position = mMenuViewAppearance.getMenuPosition(); 125 126 bounds.set((int) position.x, (int) position.y, 127 (int) position.x + mMenuViewAppearance.getMenuWidth(), 128 (int) position.y + mMenuViewAppearance.getMenuHeight()); 129 130 return bounds; 131 } 132 updateResources()133 private void updateResources() { 134 final Resources res = getResources(); 135 136 mArrowWidth = 137 res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width); 138 mArrowHeight = 139 res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_height); 140 mArrowMargin = 141 res.getDimensionPixelSize( 142 R.dimen.accessibility_floating_tooltip_arrow_margin); 143 mArrowCornerRadius = 144 res.getDimensionPixelSize( 145 R.dimen.accessibility_floating_tooltip_arrow_corner_radius); 146 mFontSize = 147 res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_font_size); 148 mTextViewMargin = 149 res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_margin); 150 mTextViewPadding = 151 res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_padding); 152 mTextViewCornerRadius = 153 res.getDimensionPixelSize( 154 R.dimen.accessibility_floating_tooltip_text_corner_radius); 155 mColorAccentPrimary = Utils.getColorAttrDefaultColor(getContext(), 156 com.android.internal.R.attr.colorAccentPrimary); 157 } 158 updateLocationWith(Rect anchorBoundsInParent, boolean isTooltipOnRightOfAnchor)159 private void updateLocationWith(Rect anchorBoundsInParent, boolean isTooltipOnRightOfAnchor) { 160 final int widthSpec = MeasureSpec.makeMeasureSpec( 161 getAvailableTextViewWidth(isTooltipOnRightOfAnchor), AT_MOST); 162 final int heightSpec = MeasureSpec.makeMeasureSpec(/* size= */ 0, UNSPECIFIED); 163 mMessageView.measure(widthSpec, heightSpec); 164 final LinearLayout.LayoutParams textViewParams = 165 (LinearLayout.LayoutParams) mMessageView.getLayoutParams(); 166 textViewParams.width = mMessageView.getMeasuredWidth(); 167 mMessageView.setLayoutParams(textViewParams); 168 169 final int layoutWidth = mMessageView.getMeasuredWidth() + mArrowWidth + mArrowMargin; 170 setTranslationX(isTooltipOnRightOfAnchor 171 ? anchorBoundsInParent.right 172 : anchorBoundsInParent.left - layoutWidth); 173 174 setTranslationY(anchorBoundsInParent.centerY() - (mMessageView.getMeasuredHeight() / 2.0f)); 175 } 176 updateMessageView()177 private void updateMessageView() { 178 mMessageView.setTextSize(COMPLEX_UNIT_PX, mFontSize); 179 mMessageView.setPadding(mTextViewPadding, mTextViewPadding, mTextViewPadding, 180 mTextViewPadding); 181 182 final GradientDrawable gradientDrawable = (GradientDrawable) mMessageView.getBackground(); 183 gradientDrawable.setCornerRadius(mTextViewCornerRadius); 184 gradientDrawable.setColor(mColorAccentPrimary); 185 } 186 updateArrowView()187 private void updateArrowView() { 188 drawArrow(mArrowLeftView, /* isPointingLeft= */ true); 189 drawArrow(mArrowRightView, /* isPointingLeft= */ false); 190 } 191 updateArrowVisibilityWith(boolean isTooltipOnRightOfAnchor)192 private void updateArrowVisibilityWith(boolean isTooltipOnRightOfAnchor) { 193 if (isTooltipOnRightOfAnchor) { 194 mArrowLeftView.setVisibility(VISIBLE); 195 mArrowRightView.setVisibility(GONE); 196 } else { 197 mArrowLeftView.setVisibility(GONE); 198 mArrowRightView.setVisibility(VISIBLE); 199 } 200 } 201 drawArrow(View arrowView, boolean isPointingLeft)202 private void drawArrow(View arrowView, boolean isPointingLeft) { 203 final TriangleShape triangleShape = 204 TriangleShape.createHorizontal(mArrowWidth, mArrowHeight, isPointingLeft); 205 final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape); 206 final Paint arrowPaint = arrowDrawable.getPaint(); 207 arrowPaint.setColor(mColorAccentPrimary); 208 209 final CornerPathEffect effect = new CornerPathEffect(mArrowCornerRadius); 210 arrowPaint.setPathEffect(effect); 211 212 arrowView.setBackground(arrowDrawable); 213 } 214 initViews()215 private void initViews() { 216 final View contentView = LayoutInflater.from(getContext()).inflate( 217 R.layout.accessibility_floating_menu_tooltip, /* root= */ this, /* attachToRoot= */ 218 false); 219 220 mMessageView = contentView.findViewById(R.id.text); 221 mMessageView.setMovementMethod(LinkMovementMethod.getInstance()); 222 223 mArrowLeftView = contentView.findViewById(R.id.arrow_left); 224 mArrowRightView = contentView.findViewById(R.id.arrow_right); 225 226 updateMessageView(); 227 updateArrowView(); 228 229 addView(contentView); 230 } 231 getAvailableTextViewWidth(boolean isOnRightOfAnchor)232 private int getAvailableTextViewWidth(boolean isOnRightOfAnchor) { 233 final PointF position = mMenuViewAppearance.getMenuPosition(); 234 final int availableWidth = isOnRightOfAnchor 235 ? mMenuViewAppearance.getMenuDraggableBounds().width() - (int) position.x 236 : (int) position.x; 237 238 return availableWidth - mArrowWidth - mArrowMargin - mTextViewMargin; 239 } 240 } 241