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