Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit c4d07fb

Browse files
committed
[Android] Fix the missing surface issue in the shared engine scenario
1 parent 5f99a6c commit c4d07fb

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import android.view.SurfaceView;
1313
import androidx.annotation.NonNull;
1414
import androidx.annotation.Nullable;
15+
import androidx.annotation.VisibleForTesting;
1516
import io.flutter.Log;
1617
import io.flutter.embedding.engine.renderer.FlutterRenderer;
1718
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
@@ -168,6 +169,10 @@ public FlutterRenderer getAttachedRenderer() {
168169
return flutterRenderer;
169170
}
170171

172+
@VisibleForTesting
173+
/* package */ boolean isSurfaceAvailableForRendering() {
174+
return isSurfaceAvailableForRendering;
175+
}
171176
/**
172177
* Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering a
173178
* Flutter UI to this {@code FlutterSurfaceView}.
@@ -215,8 +220,6 @@ public void detachFromRenderer() {
215220
disconnectSurfaceFromRenderer();
216221
}
217222

218-
pause();
219-
220223
// Make the SurfaceView invisible to avoid showing a black rectangle.
221224
setAlpha(0.0f);
222225
flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
@@ -248,7 +251,7 @@ public void resume() {
248251

249252
// If we're already attached to an Android window then we're now attached to both a renderer
250253
// and the Android window. We can begin rendering now.
251-
if (isSurfaceAvailableForRendering) {
254+
if (isSurfaceAvailableForRendering()) {
252255
Log.v(
253256
TAG,
254257
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");

shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ public FlutterRenderer getAttachedRenderer() {
124124
return flutterRenderer;
125125
}
126126

127+
@VisibleForTesting
128+
/* package */ boolean isSurfaceAvailableForRendering() {
129+
return isSurfaceAvailableForRendering;
130+
}
131+
127132
/**
128133
* Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering a
129134
* Flutter UI to this {@code FlutterTextureView}.
@@ -170,8 +175,6 @@ public void detachFromRenderer() {
170175
disconnectSurfaceFromRenderer();
171176
}
172177

173-
pause();
174-
175178
flutterRenderer = null;
176179
} else {
177180
Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
@@ -198,7 +201,7 @@ public void resume() {
198201

199202
// If we're already attached to an Android window then we're now attached to both a renderer
200203
// and the Android window. We can begin rendering now.
201-
if (isSurfaceAvailableForRendering) {
204+
if (isSurfaceAvailableForRendering()) {
202205
Log.v(
203206
TAG,
204207
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
@@ -217,6 +220,12 @@ public void setRenderSurface(Surface renderSurface) {
217220
this.renderSurface = renderSurface;
218221
}
219222

223+
/** This only be used for testing purposes. */
224+
@VisibleForTesting
225+
public void setSurfaceAvailableForRendering(boolean isAvailable) {
226+
this.isSurfaceAvailableForRendering = isAvailable;
227+
}
228+
220229
// FlutterRenderer and getSurfaceTexture() must both be non-null.
221230
private void connectSurfaceToRenderer() {
222231
if (flutterRenderer == null || getSurfaceTexture() == null) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.flutter.embedding.android;
2+
3+
import static org.mockito.ArgumentMatchers.any;
4+
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.never;
6+
import static org.mockito.Mockito.spy;
7+
import static org.mockito.Mockito.times;
8+
import static org.mockito.Mockito.verify;
9+
import static org.mockito.Mockito.when;
10+
11+
import android.annotation.TargetApi;
12+
import android.view.Surface;
13+
import android.view.SurfaceHolder;
14+
import androidx.test.core.app.ApplicationProvider;
15+
import androidx.test.ext.junit.runners.AndroidJUnit4;
16+
import io.flutter.embedding.engine.FlutterJNI;
17+
import io.flutter.embedding.engine.renderer.FlutterRenderer;
18+
import org.junit.Test;
19+
import org.junit.runner.RunWith;
20+
import org.robolectric.annotation.Config;
21+
22+
@Config(manifest = Config.NONE)
23+
@RunWith(AndroidJUnit4.class)
24+
@TargetApi(30)
25+
public class FlutterSurfaceViewTest {
26+
@Test
27+
public void itShouldCreateANewSurfaceWhenReattachedAfterDetachingFromRenderer() {
28+
// Setup the test.
29+
final FlutterSurfaceView surfaceView =
30+
spy(new FlutterSurfaceView(ApplicationProvider.getApplicationContext()));
31+
32+
FlutterJNI fakeFlutterJNI = mock(FlutterJNI.class);
33+
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
34+
35+
SurfaceHolder fakeSurfaceHolder = mock(SurfaceHolder.class);
36+
Surface fakeSurface = mock(Surface.class);
37+
when(surfaceView.getHolder()).thenReturn(fakeSurfaceHolder);
38+
when(fakeSurfaceHolder.getSurface()).thenReturn(fakeSurface);
39+
when(surfaceView.isSurfaceAvailableForRendering()).thenReturn(true);
40+
when(surfaceView.getWindowToken()).thenReturn(mock(android.os.IBinder.class));
41+
42+
// Execute the behavior under test.
43+
surfaceView.attachToRenderer(flutterRenderer);
44+
45+
// Verify the behavior under test.
46+
verify(fakeFlutterJNI, times(1)).onSurfaceCreated(any(Surface.class));
47+
48+
// Execute the behavior under test.
49+
surfaceView.detachFromRenderer();
50+
51+
// Verify the behavior under test.
52+
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
53+
54+
// Execute the behavior under test.
55+
surfaceView.attachToRenderer(flutterRenderer);
56+
57+
// Verify the behavior under test.
58+
verify(fakeFlutterJNI, never()).onSurfaceWindowChanged(any(Surface.class));
59+
// 2 times: twice for |attachToRenderer|
60+
verify(fakeFlutterJNI, times(2)).onSurfaceCreated(any(Surface.class));
61+
}
62+
}

shell/platform/android/test/io/flutter/embedding/android/FlutterTextureViewTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package io.flutter.embedding.android;
22

3+
import static org.mockito.ArgumentMatchers.any;
34
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.never;
6+
import static org.mockito.Mockito.spy;
7+
import static org.mockito.Mockito.times;
48
import static org.mockito.Mockito.verify;
9+
import static org.mockito.Mockito.when;
510

611
import android.annotation.TargetApi;
712
import android.graphics.SurfaceTexture;
813
import android.view.Surface;
914
import android.view.TextureView;
1015
import androidx.test.core.app.ApplicationProvider;
1116
import androidx.test.ext.junit.runners.AndroidJUnit4;
17+
import io.flutter.embedding.engine.FlutterJNI;
18+
import io.flutter.embedding.engine.renderer.FlutterRenderer;
1219
import org.junit.Test;
1320
import org.junit.runner.RunWith;
1421
import org.robolectric.annotation.Config;
@@ -30,4 +37,38 @@ public void surfaceTextureListenerReleasesRenderer() {
3037

3138
verify(mockRenderSurface).release();
3239
}
40+
41+
@Test
42+
public void itShouldCreateANewSurfaceWhenReattachedAfterDetachingFromRenderer() {
43+
// Setup the test.
44+
final FlutterTextureView textureView =
45+
spy(new FlutterTextureView(ApplicationProvider.getApplicationContext()));
46+
47+
FlutterJNI fakeFlutterJNI = mock(FlutterJNI.class);
48+
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
49+
50+
when(textureView.isSurfaceAvailableForRendering()).thenReturn(true);
51+
when(textureView.getSurfaceTexture()).thenReturn(mock(SurfaceTexture.class));
52+
when(textureView.getWindowToken()).thenReturn(mock(android.os.IBinder.class));
53+
54+
// Execute the behavior under test.
55+
textureView.attachToRenderer(flutterRenderer);
56+
57+
// Verify the behavior under test.
58+
verify(fakeFlutterJNI, times(1)).onSurfaceCreated(any(Surface.class));
59+
60+
// Execute the behavior under test.
61+
textureView.detachFromRenderer();
62+
63+
// Verify the behavior under test.
64+
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
65+
66+
// Execute the behavior under test.
67+
textureView.attachToRenderer(flutterRenderer);
68+
69+
// Verify the behavior under test.
70+
verify(fakeFlutterJNI, never()).onSurfaceWindowChanged(any(Surface.class));
71+
// 2 times: twice for |attachToRenderer|
72+
verify(fakeFlutterJNI, times(2)).onSurfaceCreated(any(Surface.class));
73+
}
3374
}

0 commit comments

Comments
 (0)