blob: 630504f9aaeef28cafa02b2826653717ef8f7ade [file] [log] [blame]
package io.flutter.plugin.platform;
import static android.view.View.OnFocusChangeListener;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.SurfaceTexture;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@TargetApi(31)
@RunWith(AndroidJUnit4.class)
public class PlatformViewWrapperTest {
private final Context ctx = ApplicationProvider.getApplicationContext();
@Test
public void invalidateChildInParent_callsInvalidate() {
final PlatformViewWrapper wrapper = spy(new PlatformViewWrapper(ctx));
// Mock Android framework calls.
wrapper.invalidateChildInParent(null, null);
// Verify.
verify(wrapper, times(1)).invalidate();
}
@Test
public void setTexture_writesToBuffer() {
final Surface surface = mock(Surface.class);
final PlatformViewWrapper wrapper =
new PlatformViewWrapper(ctx) {
@Override
protected Surface createSurface(@NonNull SurfaceTexture tx) {
return surface;
}
};
final SurfaceTexture tx = mock(SurfaceTexture.class);
when(tx.isReleased()).thenReturn(false);
final Canvas canvas = mock(Canvas.class);
when(surface.lockHardwareCanvas()).thenReturn(canvas);
// Test.
wrapper.setTexture(tx);
// Verify.
verify(surface, times(1)).lockHardwareCanvas();
verify(surface, times(1)).unlockCanvasAndPost(canvas);
verify(canvas, times(1)).drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
verifyNoMoreInteractions(surface);
verifyNoMoreInteractions(canvas);
}
@Test
public void draw_writesToBuffer() {
final Surface surface = mock(Surface.class);
final PlatformViewWrapper wrapper =
new PlatformViewWrapper(ctx) {
@Override
protected Surface createSurface(@NonNull SurfaceTexture tx) {
return surface;
}
};
wrapper.addView(
new View(ctx) {
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
canvas.drawColor(Color.RED);
}
});
final int size = 100;
wrapper.measure(size, size);
wrapper.layout(0, 0, size, size);
final SurfaceTexture tx = mock(SurfaceTexture.class);
when(tx.isReleased()).thenReturn(false);
when(surface.lockHardwareCanvas()).thenReturn(mock(Canvas.class));
wrapper.setTexture(tx);
reset(surface);
final Canvas canvas = mock(Canvas.class);
when(surface.lockHardwareCanvas()).thenReturn(canvas);
when(surface.isValid()).thenReturn(true);
// Test.
wrapper.invalidate();
wrapper.draw(new Canvas());
// Verify.
verify(canvas, times(1)).drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
verify(surface, times(1)).isValid();
verify(surface, times(1)).lockHardwareCanvas();
verify(surface, times(1)).unlockCanvasAndPost(canvas);
verifyNoMoreInteractions(surface);
verifyNoMoreInteractions(canvas);
}
@Test
@Config(
shadows = {
ShadowView.class,
})
public void draw_withoutSurface() {
final PlatformViewWrapper wrapper =
new PlatformViewWrapper(ctx) {
@Override
public void onDraw(Canvas canvas) {
canvas.drawColor(Color.RED);
}
};
// Test.
final Canvas canvas = mock(Canvas.class);
wrapper.draw(canvas);
// Verify.
verify(canvas, times(1)).drawColor(Color.RED);
}
@Test
public void release() {
final Surface surface = mock(Surface.class);
final PlatformViewWrapper wrapper =
new PlatformViewWrapper(ctx) {
@Override
protected Surface createSurface(@NonNull SurfaceTexture tx) {
return surface;
}
};
final SurfaceTexture tx = mock(SurfaceTexture.class);
when(tx.isReleased()).thenReturn(false);
final Canvas canvas = mock(Canvas.class);
when(surface.lockHardwareCanvas()).thenReturn(canvas);
wrapper.setTexture(tx);
reset(surface);
reset(tx);
// Test.
wrapper.release();
// Verify.
verify(surface, times(1)).release();
verifyNoMoreInteractions(surface);
verifyNoMoreInteractions(tx);
}
@Test
public void focusChangeListener_hasFocus() {
final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
when(viewTreeObserver.isAlive()).thenReturn(true);
final PlatformViewWrapper view =
new PlatformViewWrapper(ctx) {
@Override
public ViewTreeObserver getViewTreeObserver() {
return viewTreeObserver;
}
@Override
public boolean hasFocus() {
return true;
}
};
final OnFocusChangeListener focusListener = mock(OnFocusChangeListener.class);
view.setOnDescendantFocusChangeListener(focusListener);
final ArgumentCaptor<ViewTreeObserver.OnGlobalFocusChangeListener> focusListenerCaptor =
ArgumentCaptor.forClass(ViewTreeObserver.OnGlobalFocusChangeListener.class);
verify(viewTreeObserver).addOnGlobalFocusChangeListener(focusListenerCaptor.capture());
focusListenerCaptor.getValue().onGlobalFocusChanged(null, null);
verify(focusListener).onFocusChange(view, true);
}
@Test
public void focusChangeListener_doesNotHaveFocus() {
final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
when(viewTreeObserver.isAlive()).thenReturn(true);
final PlatformViewWrapper view =
new PlatformViewWrapper(ctx) {
@Override
public ViewTreeObserver getViewTreeObserver() {
return viewTreeObserver;
}
@Override
public boolean hasFocus() {
return false;
}
};
final OnFocusChangeListener focusListener = mock(OnFocusChangeListener.class);
view.setOnDescendantFocusChangeListener(focusListener);
final ArgumentCaptor<ViewTreeObserver.OnGlobalFocusChangeListener> focusListenerCaptor =
ArgumentCaptor.forClass(ViewTreeObserver.OnGlobalFocusChangeListener.class);
verify(viewTreeObserver).addOnGlobalFocusChangeListener(focusListenerCaptor.capture());
focusListenerCaptor.getValue().onGlobalFocusChanged(null, null);
verify(focusListener).onFocusChange(view, false);
}
@Test
public void focusChangeListener_viewTreeObserverIsAliveFalseDoesNotThrow() {
final PlatformViewWrapper view =
new PlatformViewWrapper(ctx) {
@Override
public ViewTreeObserver getViewTreeObserver() {
final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
when(viewTreeObserver.isAlive()).thenReturn(false);
return viewTreeObserver;
}
};
view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class));
}
@Test
public void setOnDescendantFocusChangeListener_keepsSingleListener() {
final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
when(viewTreeObserver.isAlive()).thenReturn(true);
final PlatformViewWrapper view =
new PlatformViewWrapper(ctx) {
@Override
public ViewTreeObserver getViewTreeObserver() {
return viewTreeObserver;
}
};
assertNull(view.activeFocusListener);
view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class));
assertNotNull(view.activeFocusListener);
final ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener =
view.activeFocusListener;
view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class));
assertNotNull(view.activeFocusListener);
verify(viewTreeObserver, times(1)).removeOnGlobalFocusChangeListener(activeFocusListener);
}
@Test
public void unsetOnDescendantFocusChangeListener_removesActiveListener() {
final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
when(viewTreeObserver.isAlive()).thenReturn(true);
final PlatformViewWrapper view =
new PlatformViewWrapper(ctx) {
@Override
public ViewTreeObserver getViewTreeObserver() {
return viewTreeObserver;
}
};
assertNull(view.activeFocusListener);
view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class));
assertNotNull(view.activeFocusListener);
final ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener =
view.activeFocusListener;
view.unsetOnDescendantFocusChangeListener();
assertNull(view.activeFocusListener);
view.unsetOnDescendantFocusChangeListener();
verify(viewTreeObserver, times(1)).removeOnGlobalFocusChangeListener(activeFocusListener);
}
@Test
@Config(
shadows = {
ShadowViewGroup.class,
})
public void ignoreAccessibilityEvents() {
final PlatformViewWrapper wrapperView = new PlatformViewWrapper(ctx);
final View embeddedView = mock(View.class);
wrapperView.addView(embeddedView);
when(embeddedView.getImportantForAccessibility())
.thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
final boolean eventSent =
wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class));
assertFalse(eventSent);
}
@Test
@Config(
shadows = {
ShadowViewGroup.class,
})
public void sendAccessibilityEvents() {
final PlatformViewWrapper wrapperView = new PlatformViewWrapper(ctx);
final View embeddedView = mock(View.class);
wrapperView.addView(embeddedView);
when(embeddedView.getImportantForAccessibility())
.thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
boolean eventSent =
wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class));
assertTrue(eventSent);
when(embeddedView.getImportantForAccessibility())
.thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
eventSent =
wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class));
assertTrue(eventSent);
}
@Implements(View.class)
public static class ShadowView {}
@Implements(ViewGroup.class)
public static class ShadowViewGroup extends org.robolectric.shadows.ShadowView {
@Implementation
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
return true;
}
}
}