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.ViewGroup.LayoutParams.WRAP_CONTENT;
21 
22 import android.annotation.IntDef;
23 import android.content.ComponentCallbacks;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.graphics.Rect;
28 import android.text.Layout;
29 import android.view.Gravity;
30 import android.view.View;
31 import android.view.ViewTreeObserver;
32 import android.widget.Button;
33 import android.widget.FrameLayout;
34 import android.widget.LinearLayout;
35 import android.widget.TextView;
36 
37 import androidx.annotation.NonNull;
38 
39 import com.android.systemui.res.R;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 
44 /**
45  * The message view with the action prompt to whether to undo operation for users when removing
46  * the {@link MenuView}.
47  */
48 class MenuMessageView extends LinearLayout implements
49         ViewTreeObserver.OnComputeInternalInsetsListener, ComponentCallbacks {
50     private final TextView mTextView;
51     private final Button mUndoButton;
52 
53     @IntDef({
54             Index.TEXT_VIEW,
55             Index.UNDO_BUTTON
56     })
57     @Retention(RetentionPolicy.SOURCE)
58     @interface Index {
59         int TEXT_VIEW = 0;
60         int UNDO_BUTTON = 1;
61     }
62 
MenuMessageView(Context context)63     MenuMessageView(Context context) {
64         super(context);
65 
66         setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
67         setVisibility(GONE);
68 
69         mTextView = new TextView(context);
70         mUndoButton = new Button(context);
71 
72         addView(mTextView, Index.TEXT_VIEW,
73                 new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
74         addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
75 
76         // The message box is not focusable, but will announce its contents when it appears.
77         // The textView and button are still interactable.
78         setClickable(false);
79         setFocusable(false);
80         setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
81     }
82 
83     @Override
onConfigurationChanged(@onNull Configuration newConfig)84     public void onConfigurationChanged(@NonNull Configuration newConfig) {
85         updateResources();
86     }
87 
88     @Override
onLowMemory()89     public void onLowMemory() {
90         // Do nothing.
91     }
92 
93     @Override
onAttachedToWindow()94     protected void onAttachedToWindow() {
95         super.onAttachedToWindow();
96 
97         final FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(WRAP_CONTENT,
98                 WRAP_CONTENT);
99         containerParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
100         setLayoutParams(containerParams);
101         setGravity(Gravity.CENTER_VERTICAL);
102 
103         mUndoButton.setBackground(null);
104 
105         updateResources();
106 
107         getContext().registerComponentCallbacks(this);
108         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
109     }
110 
111     @Override
onDetachedFromWindow()112     protected void onDetachedFromWindow() {
113         super.onDetachedFromWindow();
114 
115         getContext().unregisterComponentCallbacks(this);
116         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
117     }
118 
119     @Override
onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo)120     public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
121         inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
122 
123         if (getVisibility() == VISIBLE) {
124             final int x = (int) getX();
125             final int y = (int) getY();
126             inoutInfo.touchableRegion.union(new Rect(x, y, x + getWidth(), y + getHeight()));
127         }
128     }
129 
130     /**
131      * Registers a listener to be invoked when this undo action button is clicked. It should be
132      * called after {@link View#onAttachedToWindow()}.
133      *
134      * @param listener The listener that will run
135      */
setUndoListener(OnClickListener listener)136     void setUndoListener(OnClickListener listener) {
137         mUndoButton.setOnClickListener(listener);
138     }
139 
updateResources()140     private void updateResources() {
141         final Resources res = getResources();
142 
143         final int containerPadding =
144                 res.getDimensionPixelSize(
145                         R.dimen.accessibility_floating_menu_message_container_horizontal_padding);
146         final int margin = res.getDimensionPixelSize(
147                 R.dimen.accessibility_floating_menu_message_margin);
148         final FrameLayout.LayoutParams containerParams =
149                 (FrameLayout.LayoutParams) getLayoutParams();
150         containerParams.setMargins(margin, margin, margin, margin);
151         setLayoutParams(containerParams);
152         setBackground(res.getDrawable(R.drawable.accessibility_floating_message_background));
153         setPadding(containerPadding, /* top= */ 0, containerPadding, /* bottom= */ 0);
154         setMinimumWidth(
155                 res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_width));
156         setMinimumHeight(
157                 res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_height));
158         setElevation(
159                 res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_elevation));
160 
161         final int textPadding =
162                 res.getDimensionPixelSize(
163                         R.dimen.accessibility_floating_menu_message_text_vertical_padding);
164         final int textColor = res.getColor(R.color.accessibility_floating_menu_message_text);
165         final int textSize = res.getDimensionPixelSize(
166                 R.dimen.accessibility_floating_menu_message_text_size);
167         mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding);
168         mTextView.setTextSize(COMPLEX_UNIT_PX, textSize);
169         mTextView.setTextColor(textColor);
170         mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
171 
172         mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
173         mUndoButton.setTextSize(COMPLEX_UNIT_PX, textSize);
174         mUndoButton.setTextColor(textColor);
175         mUndoButton.setAllCaps(true);
176     }
177 }
178