blob: 61fc7edcd9a029448bdf2e60ed5c7ac35151ce94 [file] [log] [blame]
/*
* 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.systemui.accessibility.floatingmenu;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.UNSPECIFIED;
import android.annotation.UiContext;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import android.text.method.MovementMethod;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
/**
* Base tooltip view that shows the information about the operation of the
* Accessibility floating menu. In addition, the anchor view is only for {@link
* AccessibilityFloatingMenuView}, it should be more suited for displaying one-off menus to avoid
* the performance hit for the extra window.
*/
class BaseTooltipView extends FrameLayout {
private int mFontSize;
private int mTextViewMargin;
private int mTextViewPadding;
private int mTextViewCornerRadius;
private int mArrowMargin;
private int mArrowWidth;
private int mArrowHeight;
private int mArrowCornerRadius;
private int mScreenWidth;
private boolean mIsShowing;
private TextView mTextView;
private final WindowManager.LayoutParams mCurrentLayoutParams;
private final WindowManager mWindowManager;
private final AccessibilityFloatingMenuView mAnchorView;
BaseTooltipView(@UiContext Context context, AccessibilityFloatingMenuView anchorView) {
super(context);
mWindowManager = context.getSystemService(WindowManager.class);
mAnchorView = anchorView;
mCurrentLayoutParams = createDefaultLayoutParams();
initViews();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mAnchorView.onConfigurationChanged(newConfig);
updateTooltipView();
mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
hide();
}
return super.onTouchEvent(event);
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.addAction(AccessibilityAction.ACTION_DISMISS);
}
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (action == AccessibilityAction.ACTION_DISMISS.getId()) {
hide();
return true;
}
return super.performAccessibilityAction(action, arguments);
}
void show() {
if (isShowing()) {
return;
}
mIsShowing = true;
updateTooltipView();
mWindowManager.addView(this, mCurrentLayoutParams);
}
void hide() {
if (!isShowing()) {
return;
}
mIsShowing = false;
mWindowManager.removeView(this);
}
void setDescription(CharSequence text) {
mTextView.setText(text);
}
void setMovementMethod(MovementMethod movement) {
mTextView.setMovementMethod(movement);
}
private boolean isShowing() {
return mIsShowing;
}
private void initViews() {
final View contentView =
LayoutInflater.from(getContext()).inflate(
R.layout.accessibility_floating_menu_tooltip, this, false);
mTextView = contentView.findViewById(R.id.text);
addView(contentView);
}
private static WindowManager.LayoutParams createDefaultLayoutParams() {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);
params.windowAnimations = android.R.style.Animation_Translucent;
params.gravity = Gravity.START | Gravity.TOP;
return params;
}
private void updateDimensions() {
final Resources res = getResources();
final DisplayMetrics dm = res.getDisplayMetrics();
mScreenWidth = dm.widthPixels;
mArrowWidth =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width);
mArrowHeight =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_height);
mArrowMargin =
res.getDimensionPixelSize(
R.dimen.accessibility_floating_tooltip_arrow_margin);
mArrowCornerRadius =
res.getDimensionPixelSize(
R.dimen.accessibility_floating_tooltip_arrow_corner_radius);
mFontSize =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_font_size);
mTextViewMargin =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_margin);
mTextViewPadding =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_padding);
mTextViewCornerRadius =
res.getDimensionPixelSize(
R.dimen.accessibility_floating_tooltip_text_corner_radius);
}
private void updateTooltipView() {
updateDimensions();
updateTextView();
final Rect anchorViewLocation = mAnchorView.getWindowLocationOnScreen();
updateArrowWith(anchorViewLocation);
updateWidthWith(anchorViewLocation);
updateLocationWith(anchorViewLocation);
}
private void updateTextView() {
mTextView.setTextSize(COMPLEX_UNIT_PX, mFontSize);
mTextView.setPadding(mTextViewPadding, mTextViewPadding, mTextViewPadding,
mTextViewPadding);
final GradientDrawable gradientDrawable = (GradientDrawable) mTextView.getBackground();
gradientDrawable.setCornerRadius(mTextViewCornerRadius);
gradientDrawable.setColor(Utils.getColorAttrDefaultColor(getContext(),
com.android.internal.R.attr.colorAccentPrimary));
}
private void updateArrowWith(Rect anchorViewLocation) {
final boolean isAnchorViewOnLeft = isAnchorViewOnLeft(anchorViewLocation);
final View arrowView = findViewById(isAnchorViewOnLeft
? R.id.arrow_left
: R.id.arrow_right);
arrowView.setVisibility(VISIBLE);
drawArrow(arrowView, isAnchorViewOnLeft);
final LinearLayout.LayoutParams layoutParams =
(LinearLayout.LayoutParams) arrowView.getLayoutParams();
layoutParams.width = mArrowWidth;
layoutParams.height = mArrowHeight;
final int leftMargin = isAnchorViewOnLeft ? 0 : mArrowMargin;
final int rightMargin = isAnchorViewOnLeft ? mArrowMargin : 0;
layoutParams.setMargins(leftMargin, 0, rightMargin, 0);
arrowView.setLayoutParams(layoutParams);
}
private void updateWidthWith(Rect anchorViewLocation) {
final ViewGroup.LayoutParams layoutParams = mTextView.getLayoutParams();
layoutParams.width = getTextWidthWith(anchorViewLocation);
mTextView.setLayoutParams(layoutParams);
}
private void updateLocationWith(Rect anchorViewLocation) {
mCurrentLayoutParams.x = isAnchorViewOnLeft(anchorViewLocation)
? anchorViewLocation.width()
: mScreenWidth - getWindowWidthWith(anchorViewLocation)
- anchorViewLocation.width();
mCurrentLayoutParams.y =
anchorViewLocation.centerY() - (getTextHeightWith(anchorViewLocation) / 2);
}
private void drawArrow(View view, boolean isPointingLeft) {
final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
final TriangleShape triangleShape =
TriangleShape.createHorizontal(layoutParams.width, layoutParams.height,
isPointingLeft);
final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape);
final Paint arrowPaint = arrowDrawable.getPaint();
arrowPaint.setColor(Utils.getColorAttrDefaultColor(getContext(),
com.android.internal.R.attr.colorAccentPrimary));
final CornerPathEffect effect = new CornerPathEffect(mArrowCornerRadius);
arrowPaint.setPathEffect(effect);
view.setBackground(arrowDrawable);
}
private boolean isAnchorViewOnLeft(Rect anchorViewLocation) {
return anchorViewLocation.left < (mScreenWidth / 2);
}
private int getTextWidthWith(Rect anchorViewLocation) {
final int widthSpec =
MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST);
final int heightSpec =
MeasureSpec.makeMeasureSpec(0, UNSPECIFIED);
mTextView.measure(widthSpec, heightSpec);
return mTextView.getMeasuredWidth();
}
private int getTextHeightWith(Rect anchorViewLocation) {
final int widthSpec =
MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST);
final int heightSpec =
MeasureSpec.makeMeasureSpec(0, UNSPECIFIED);
mTextView.measure(widthSpec, heightSpec);
return mTextView.getMeasuredHeight();
}
private int getAvailableTextWidthWith(Rect anchorViewLocation) {
return mScreenWidth - anchorViewLocation.width() - mArrowWidth - mArrowMargin
- mTextViewMargin;
}
private int getWindowWidthWith(Rect anchorViewLocation) {
return getTextWidthWith(anchorViewLocation) + mArrowWidth + mArrowMargin;
}
}