Skip to content

Commit 673cefc

Browse files
raajkumarsafohrman
authored andcommitted
[TextInputLayout] Fix for TextInputLayout leak via AccessibilityManager.
Resolves #2615 PiperOrigin-RevId: 449517571
1 parent be1b38c commit 673cefc

File tree

3 files changed

+82
-17
lines changed

3 files changed

+82
-17
lines changed

lib/java/com/google/android/material/textfield/DropdownMenuEndIconDelegate.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import androidx.annotation.NonNull;
4545
import androidx.annotation.Nullable;
4646
import androidx.core.view.ViewCompat;
47+
import androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener;
4748
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
4849
import com.google.android.material.animation.AnimationUtils;
4950
import com.google.android.material.textfield.TextInputLayout.BoxBackgroundMode;
@@ -71,8 +72,16 @@ class DropdownMenuEndIconDelegate extends EndIconDelegate {
7172
}
7273
};
7374

74-
private boolean editTextHasFocus;
75+
private final TouchExplorationStateChangeListener touchExplorationStateChangeListener =
76+
(boolean enabled) -> {
77+
if (autoCompleteTextView != null && !isEditable(autoCompleteTextView)) {
78+
ViewCompat.setImportantForAccessibility(
79+
endIconView,
80+
enabled ? IMPORTANT_FOR_ACCESSIBILITY_NO : IMPORTANT_FOR_ACCESSIBILITY_YES);
81+
}
82+
};
7583

84+
private boolean editTextHasFocus;
7685
private boolean dropdownPopupDirty;
7786
private boolean isEndIconChecked;
7887
private long dropdownPopupActivatedAt = Long.MAX_VALUE;
@@ -89,15 +98,6 @@ void setUp() {
8998
initAnimators();
9099
accessibilityManager =
91100
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
92-
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
93-
accessibilityManager.addTouchExplorationStateChangeListener(enabled -> {
94-
if (autoCompleteTextView != null && !isEditable(autoCompleteTextView)) {
95-
ViewCompat.setImportantForAccessibility(
96-
endIconView,
97-
enabled ? IMPORTANT_FOR_ACCESSIBILITY_NO : IMPORTANT_FOR_ACCESSIBILITY_YES);
98-
}
99-
});
100-
}
101101
}
102102

103103
@SuppressLint("ClickableViewAccessibility") // There's an accessibility delegate that handles
@@ -113,6 +113,11 @@ void tearDown() {
113113
}
114114
}
115115

116+
@Override
117+
public TouchExplorationStateChangeListener getTouchExplorationStateChangeListener() {
118+
return touchExplorationStateChangeListener;
119+
}
120+
116121
@Override
117122
int getIconDrawableResId() {
118123
// For lollipop+, the arrow icon changes orientation based on dropdown popup, otherwise it

lib/java/com/google/android/material/textfield/EndCompoundLayout.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE;
3131

3232
import android.annotation.SuppressLint;
33+
import android.content.Context;
3334
import android.content.res.ColorStateList;
3435
import android.graphics.PorterDuff;
3536
import android.graphics.drawable.Drawable;
@@ -43,7 +44,9 @@
4344
import android.view.Gravity;
4445
import android.view.LayoutInflater;
4546
import android.view.View;
47+
import android.view.View.OnAttachStateChangeListener;
4648
import android.view.ViewGroup;
49+
import android.view.accessibility.AccessibilityManager;
4750
import android.widget.EditText;
4851
import android.widget.FrameLayout;
4952
import android.widget.LinearLayout;
@@ -57,6 +60,8 @@
5760
import androidx.core.graphics.drawable.DrawableCompat;
5861
import androidx.core.view.MarginLayoutParamsCompat;
5962
import androidx.core.view.ViewCompat;
63+
import androidx.core.view.accessibility.AccessibilityManagerCompat;
64+
import androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener;
6065
import androidx.core.widget.TextViewCompat;
6166
import com.google.android.material.internal.CheckableImageButton;
6267
import com.google.android.material.internal.TextWatcherAdapter;
@@ -97,6 +102,8 @@ class EndCompoundLayout extends LinearLayout {
97102
private boolean hintExpanded;
98103

99104
private EditText editText;
105+
@Nullable private final AccessibilityManager accessibilityManager;
106+
@Nullable private TouchExplorationStateChangeListener touchExplorationStateChangeListener;
100107

101108
private final TextWatcher editTextWatcher =
102109
new TextWatcherAdapter() {
@@ -137,6 +144,9 @@ public void onEditTextAttached(@NonNull TextInputLayout textInputLayout) {
137144
EndCompoundLayout(TextInputLayout textInputLayout, TintTypedArray a) {
138145
super(textInputLayout.getContext());
139146

147+
accessibilityManager =
148+
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
149+
140150
this.textInputLayout = textInputLayout;
141151

142152
setVisibility(GONE);
@@ -170,6 +180,18 @@ public void onEditTextAttached(@NonNull TextInputLayout textInputLayout) {
170180
addView(errorIconView);
171181

172182
textInputLayout.addOnEditTextAttachedListener(onEditTextAttachedListener);
183+
addOnAttachStateChangeListener(
184+
new OnAttachStateChangeListener() {
185+
@Override
186+
public void onViewAttachedToWindow(View ignored) {
187+
addTouchExplorationStateChangeListenerIfNeeded();
188+
}
189+
190+
@Override
191+
public void onViewDetachedFromWindow(View ignored) {
192+
removeTouchExplorationStateChangeListenerIfNeeded();
193+
}
194+
});
173195
}
174196

175197
private CheckableImageButton createIconView(
@@ -324,7 +346,7 @@ void setEndIconMode(@EndIconMode int endIconMode) {
324346
if (this.endIconMode == endIconMode) {
325347
return;
326348
}
327-
getEndIconDelegate().tearDown();
349+
tearDownDelegate(getEndIconDelegate());
328350
int previousEndIconMode = this.endIconMode;
329351
this.endIconMode = endIconMode;
330352
dispatchOnEndIconChanged(previousEndIconMode);
@@ -334,7 +356,7 @@ void setEndIconMode(@EndIconMode int endIconMode) {
334356
setEndIconContentDescription(delegate.getIconContentDescriptionResId());
335357
setEndIconCheckable(delegate.isIconCheckable());
336358
if (delegate.isBoxBackgroundModeSupported(textInputLayout.getBoxBackgroundMode())) {
337-
delegate.setUp();
359+
setUpDelegate(delegate);
338360
} else {
339361
throw new IllegalStateException(
340362
"The current box background mode "
@@ -373,6 +395,35 @@ void refreshIconState(boolean force) {
373395
}
374396
}
375397

398+
private void setUpDelegate(@NonNull EndIconDelegate delegate) {
399+
delegate.setUp();
400+
401+
touchExplorationStateChangeListener = delegate.getTouchExplorationStateChangeListener();
402+
addTouchExplorationStateChangeListenerIfNeeded();
403+
}
404+
405+
private void tearDownDelegate(@NonNull EndIconDelegate delegate) {
406+
removeTouchExplorationStateChangeListenerIfNeeded();
407+
touchExplorationStateChangeListener = null;
408+
delegate.tearDown();
409+
}
410+
411+
private void addTouchExplorationStateChangeListenerIfNeeded() {
412+
if (touchExplorationStateChangeListener != null
413+
&& accessibilityManager != null
414+
&& ViewCompat.isAttachedToWindow(this)) {
415+
AccessibilityManagerCompat.addTouchExplorationStateChangeListener(
416+
accessibilityManager, touchExplorationStateChangeListener);
417+
}
418+
}
419+
420+
private void removeTouchExplorationStateChangeListenerIfNeeded() {
421+
if (touchExplorationStateChangeListener != null && accessibilityManager != null) {
422+
AccessibilityManagerCompat.removeTouchExplorationStateChangeListener(
423+
accessibilityManager, touchExplorationStateChangeListener);
424+
}
425+
}
426+
376427
private int getIconResId(EndIconDelegate delegate) {
377428
int customIconResId = endIconDelegates.customEndIconDrawableId;
378429
return customIconResId == 0 ? delegate.getIconDrawableResId() : customIconResId;

lib/java/com/google/android/material/textfield/EndIconDelegate.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import androidx.annotation.NonNull;
2828
import androidx.annotation.Nullable;
2929
import androidx.annotation.StringRes;
30+
import androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener;
3031
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
3132
import com.google.android.material.internal.CheckableImageButton;
3233
import com.google.android.material.textfield.TextInputLayout.BoxBackgroundMode;
@@ -130,29 +131,37 @@ boolean isBoxBackgroundModeSupported(@BoxBackgroundMode int boxBackgroundMode) {
130131
void onSuffixVisibilityChanged(boolean visible) {}
131132

132133
/**
133-
* Overrides this method to provides an {@link OnClickListener} to handle click events of the end
134+
* Override this method to provide an {@link OnClickListener} to handle click events of the end
134135
* icon.
135136
*/
136137
OnClickListener getOnIconClickListener() {
137138
return null;
138139
}
139140

140141
/**
141-
* Overrides this method to provides an {@link OnFocusChangeListener} to handle focus change
142-
* events of the edit text.
142+
* Override this method to provide an {@link OnFocusChangeListener} to handle focus change events
143+
* of the edit text.
143144
*/
144145
OnFocusChangeListener getOnEditTextFocusChangeListener() {
145146
return null;
146147
}
147148

148149
/**
149-
* Overrides this method to provides an {@link OnFocusChangeListener} to handle focus change
150-
* events of the end icon.
150+
* Override this method to provide an {@link OnFocusChangeListener} to handle focus change events
151+
* of the end icon.
151152
*/
152153
OnFocusChangeListener getOnIconViewFocusChangeListener() {
153154
return null;
154155
}
155156

157+
/**
158+
* Override this method to provide a {@link TouchExplorationStateChangeListener} to handle touch
159+
* exploration state changes of the end icon.
160+
*/
161+
TouchExplorationStateChangeListener getTouchExplorationStateChangeListener() {
162+
return null;
163+
}
164+
156165
/** This method will be called when the edit text of the text input layout is attached. */
157166
void onEditTextAttached(@Nullable EditText editText) {}
158167

0 commit comments

Comments
 (0)