Ignore calls to AccessibilityBridge listeners after release (#20701)

AccessibilityBridge installs various listeners for Android events
that invoke Flutter engine APIs.  These listeners are removed in
AccessibilityBridge.release.  However, in some environments there may
be deferred calls to the listener that will still execute even after
the listener has been removed.  This change sets a flag during release
and ignores any listener invocations that happen after the flag is set.

See https://github.com/flutter/flutter/issues/63555 and
https://github.com/flutter/engine/pull/17311
This commit is contained in:
Jason Simmons 2020-08-27 12:24:33 -07:00 committed by GitHub
parent f0a8230fca
commit fc4da62b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 31 additions and 1 deletions

View File

@ -223,6 +223,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
@Nullable private OnAccessibilityChangeListener onAccessibilityChangeListener;
// Set to true after {@code release} has been invoked.
private boolean isReleased = false;
// Handler for all messages received from Flutter via the {@code accessibilityChannel}
private final AccessibilityChannel.AccessibilityMessageHandler accessibilityMessageHandler =
new AccessibilityChannel.AccessibilityMessageHandler() {
@ -274,6 +277,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
new AccessibilityManager.AccessibilityStateChangeListener() {
@Override
public void onAccessibilityStateChanged(boolean accessibilityEnabled) {
if (isReleased) {
return;
}
if (accessibilityEnabled) {
accessibilityChannel.setAccessibilityMessageHandler(accessibilityMessageHandler);
accessibilityChannel.onAndroidAccessibilityEnabled();
@ -307,6 +313,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (isReleased) {
return;
}
// Retrieve the current value of TRANSITION_ANIMATION_SCALE from the OS.
String value =
Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1
@ -374,6 +383,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
new AccessibilityManager.TouchExplorationStateChangeListener() {
@Override
public void onTouchExplorationStateChanged(boolean isTouchExplorationEnabled) {
if (isReleased) {
return;
}
if (isTouchExplorationEnabled) {
accessibilityFeatureFlags |= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
} else {
@ -421,6 +433,7 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
* on this {@code AccessibilityBridge} after invoking {@code release()} is undefined.
*/
public void release() {
isReleased = true;
// platformViewsAccessibilityDelegate should be @NonNull once the plumbing
// for io.flutter.embedding.engine.android.FlutterView is done.
// TODO(mattcarrol): Remove the null check once the plumbing is done.

View File

@ -7,11 +7,15 @@ package io.flutter.view;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
import android.view.MotionEvent;
@ -33,6 +37,7 @@ import org.robolectric.annotation.Config;
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
@TargetApi(19)
public class AccessibilityBridgeTest {
@Test
@ -197,14 +202,26 @@ public class AccessibilityBridgeTest {
public void releaseDropsChannelMessageHandler() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
AccessibilityManager mockManager = mock(AccessibilityManager.class);
ContentResolver mockContentResolver = mock(ContentResolver.class);
when(mockManager.isEnabled()).thenReturn(true);
AccessibilityBridge accessibilityBridge =
setUpBridge(null, mockChannel, mockManager, null, null, null);
setUpBridge(null, mockChannel, mockManager, mockContentResolver, null, null);
verify(mockChannel)
.setAccessibilityMessageHandler(
any(AccessibilityChannel.AccessibilityMessageHandler.class));
ArgumentCaptor<AccessibilityManager.AccessibilityStateChangeListener> stateListenerCaptor =
ArgumentCaptor.forClass(AccessibilityManager.AccessibilityStateChangeListener.class);
ArgumentCaptor<AccessibilityManager.TouchExplorationStateChangeListener> touchListenerCaptor =
ArgumentCaptor.forClass(AccessibilityManager.TouchExplorationStateChangeListener.class);
verify(mockManager).addAccessibilityStateChangeListener(stateListenerCaptor.capture());
verify(mockManager).addTouchExplorationStateChangeListener(touchListenerCaptor.capture());
accessibilityBridge.release();
verify(mockChannel).setAccessibilityMessageHandler(null);
reset(mockChannel);
stateListenerCaptor.getValue().onAccessibilityStateChanged(true);
verify(mockChannel, never()).onAndroidAccessibilityEnabled();
touchListenerCaptor.getValue().onTouchExplorationStateChanged(true);
verify(mockChannel, never()).setAccessibilityFeatures(anyInt());
}
AccessibilityBridge setUpBridge() {