未验证 提交 a35e3fe4 编写于 作者: X xster 提交者: GitHub

Let FlutterFragment not pop the whole activity by default when more fragments...

Let FlutterFragment not pop the whole activity by default when more fragments are in the activity (#22692)
上级 81af789e
......@@ -902,11 +902,7 @@ public class FlutterActivity extends Activity
@Override
public PlatformPlugin providePlatformPlugin(
@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
if (activity != null) {
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
} else {
return null;
}
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel(), this);
}
/**
......@@ -1032,6 +1028,12 @@ public class FlutterActivity extends Activity
return true;
}
@Override
public boolean popSystemNavigator() {
// Hook for subclass. No-op if returns false.
return false;
}
private boolean stillAttachedForEvent(String event) {
if (delegate == null) {
Log.v(TAG, "FlutterActivity " + hashCode() + " " + event + " called after release.");
......
......@@ -22,7 +22,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.app.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterShellArgs;
......@@ -752,7 +751,10 @@ import java.util.Arrays;
* FlutterActivityAndFragmentDelegate}.
*/
/* package */ interface Host
extends SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator {
extends SplashScreenProvider,
FlutterEngineProvider,
FlutterEngineConfigurator,
PlatformPlugin.PlatformPluginDelegate {
/** Returns the {@link Context} that backs the host {@link Activity} or {@code Fragment}. */
@NonNull
Context getContext();
......
......@@ -432,7 +432,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
this(FlutterFragment.class, engineId);
}
protected CachedEngineFragmentBuilder(
public CachedEngineFragmentBuilder(
@NonNull Class<? extends FlutterFragment> subclass, @NonNull String engineId) {
this.fragmentClass = subclass;
this.engineId = engineId;
......@@ -984,7 +984,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
public PlatformPlugin providePlatformPlugin(
@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
if (activity != null) {
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel(), this);
} else {
return null;
}
......@@ -1110,6 +1110,12 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
return true;
}
@Override
public boolean popSystemNavigator() {
// Hook for subclass. No-op if returns false.
return false;
}
private boolean stillAttachedForEvent(String event) {
if (delegate == null) {
Log.v(TAG, "FlutterFragment " + hashCode() + " " + event + " called after release.");
......
......@@ -15,6 +15,7 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.activity.OnBackPressedDispatcherOwner;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
......@@ -30,10 +31,30 @@ public class PlatformPlugin {
private final Activity activity;
private final PlatformChannel platformChannel;
private final PlatformPluginDelegate platformPluginDelegate;
private PlatformChannel.SystemChromeStyle currentTheme;
private int mEnabledOverlays;
private static final String TAG = "PlatformPlugin";
/**
* The {@link PlatformPlugin} generally has default behaviors implemented for platform
* functionalities requested by the Flutter framework. However, functionalities exposed through
* this interface could be customized by the more public-facing APIs that implement this interface
* such as the {@link io.flutter.embedding.android.FlutterActivity} or the {@link
* io.flutter.embedding.android.FlutterFragment}.
*/
public interface PlatformPluginDelegate {
/**
* Allow implementer to customize the behavior needed when the Flutter framework calls to pop
* the Android-side navigation stack.
*
* @return true if the implementation consumed the pop signal. If false, a default behavior of
* finishing the activity or sending the signal to {@link
* androidx.activity.OnBackPressedDispatcher} will be executed.
*/
boolean popSystemNavigator();
}
@VisibleForTesting
final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler =
new PlatformChannel.PlatformMessageHandler() {
......@@ -101,9 +122,15 @@ public class PlatformPlugin {
};
public PlatformPlugin(Activity activity, PlatformChannel platformChannel) {
this(activity, platformChannel, null);
}
public PlatformPlugin(
Activity activity, PlatformChannel platformChannel, PlatformPluginDelegate delegate) {
this.activity = activity;
this.platformChannel = platformChannel;
this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
this.platformPluginDelegate = delegate;
mEnabledOverlays = DEFAULT_SYSTEM_UI;
}
......@@ -161,13 +188,14 @@ public class PlatformPlugin {
return;
}
// Linter refuses to believe we're only executing this code in API 28 unless we use distinct if
// Linter refuses to believe we're only executing this code in API 28 unless we
// use distinct if
// blocks and
// hardcode the API 28 constant.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P
&& Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
activity.setTaskDescription(
new TaskDescription(description.label, /*icon=*/ null, description.color));
new TaskDescription(description.label, /* icon= */ null, description.color));
}
if (Build.VERSION.SDK_INT >= 28) {
TaskDescription taskDescription =
......@@ -178,14 +206,16 @@ public class PlatformPlugin {
private void setSystemChromeEnabledSystemUIOverlays(
List<PlatformChannel.SystemUiOverlay> overlaysToShow) {
// Start by assuming we want to hide all system overlays (like an immersive game).
// Start by assuming we want to hide all system overlays (like an immersive
// game).
int enabledOverlays =
DEFAULT_SYSTEM_UI
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
// The SYSTEM_UI_FLAG_IMMERSIVE_STICKY flag was introduced in API 19, so we apply it
// The SYSTEM_UI_FLAG_IMMERSIVE_STICKY flag was introduced in API 19, so we
// apply it
// if desired, and if the current Android version is 19 or greater.
if (overlaysToShow.size() == 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
......@@ -233,7 +263,8 @@ public class PlatformPlugin {
View view = window.getDecorView();
int flags = view.getSystemUiVisibility();
// You can change the navigation bar color (including translucent colors)
// in Android, but you can't change the color of the navigation buttons until Android O.
// in Android, but you can't change the color of the navigation buttons until
// Android O.
// LIGHT vs DARK effectively isn't supported until then.
// Build.VERSION_CODES.O
if (Build.VERSION.SDK_INT >= 26) {
......@@ -279,8 +310,17 @@ public class PlatformPlugin {
}
private void popSystemNavigator() {
if (platformPluginDelegate.popSystemNavigator()) {
// A custom behavior was executed by the delegate. Don't execute default behavior.
return;
}
if (activity instanceof OnBackPressedDispatcherOwner) {
((OnBackPressedDispatcherOwner) activity).getOnBackPressedDispatcher().onBackPressed();
} else {
activity.finish();
}
}
private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat format) {
ClipboardManager clipboard =
......
......@@ -382,5 +382,10 @@ public class FlutterAndroidComponentTest {
@Override
public void detachFromFlutterEngine() {}
@Override
public boolean popSystemNavigator() {
return false;
}
}
}
......@@ -6,6 +6,9 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
......@@ -18,9 +21,12 @@ import android.net.Uri;
import android.os.Build;
import android.view.View;
import android.view.Window;
import androidx.activity.OnBackPressedDispatcher;
import androidx.fragment.app.FragmentActivity;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.ClipboardContentFormat;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.SystemChromeStyle;
import io.flutter.plugin.platform.PlatformPlugin.PlatformPluginDelegate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
......@@ -133,4 +139,87 @@ public class PlatformPluginTest {
assertEquals(0XFF000000, fakeActivity.getWindow().getNavigationBarColor());
}
}
@Test
public void popSystemNavigatorFlutterActivity() {
Activity mockActivity = mock(Activity.class);
PlatformChannel mockPlatformChannel = mock(PlatformChannel.class);
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(false);
PlatformPlugin platformPlugin =
new PlatformPlugin(mockActivity, mockPlatformChannel, mockPlatformPluginDelegate);
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
verify(mockActivity, times(1)).finish();
}
@Test
public void doesNotDoAnythingByDefaultIfPopSystemNavigatorOverridden() {
Activity mockActivity = mock(Activity.class);
PlatformChannel mockPlatformChannel = mock(PlatformChannel.class);
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(true);
PlatformPlugin platformPlugin =
new PlatformPlugin(mockActivity, mockPlatformChannel, mockPlatformPluginDelegate);
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
// No longer perform the default action when overridden.
verify(mockActivity, never()).finish();
}
@Test
public void popSystemNavigatorFlutterFragment() {
FragmentActivity mockFragmentActivity = mock(FragmentActivity.class);
OnBackPressedDispatcher onBackPressedDispatcher = mock(OnBackPressedDispatcher.class);
when(mockFragmentActivity.getOnBackPressedDispatcher()).thenReturn(onBackPressedDispatcher);
PlatformChannel mockPlatformChannel = mock(PlatformChannel.class);
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(false);
PlatformPlugin platformPlugin =
new PlatformPlugin(mockFragmentActivity, mockPlatformChannel, mockPlatformPluginDelegate);
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
verify(mockFragmentActivity, never()).finish();
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
verify(mockFragmentActivity, times(1)).getOnBackPressedDispatcher();
verify(onBackPressedDispatcher, times(1)).onBackPressed();
}
@Test
public void doesNotDoAnythingByDefaultIfFragmentPopSystemNavigatorOverridden() {
FragmentActivity mockFragmentActivity = mock(FragmentActivity.class);
OnBackPressedDispatcher onBackPressedDispatcher = mock(OnBackPressedDispatcher.class);
when(mockFragmentActivity.getOnBackPressedDispatcher()).thenReturn(onBackPressedDispatcher);
PlatformChannel mockPlatformChannel = mock(PlatformChannel.class);
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(true);
PlatformPlugin platformPlugin =
new PlatformPlugin(mockFragmentActivity, mockPlatformChannel, mockPlatformPluginDelegate);
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
// No longer perform the default action when overridden.
verify(mockFragmentActivity, never()).finish();
verify(mockFragmentActivity, never()).getOnBackPressedDispatcher();
}
@Test
public void setRequestedOrientationFlutterFragment() {
FragmentActivity mockFragmentActivity = mock(FragmentActivity.class);
PlatformChannel mockPlatformChannel = mock(PlatformChannel.class);
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(false);
PlatformPlugin platformPlugin =
new PlatformPlugin(mockFragmentActivity, mockPlatformChannel, mockPlatformPluginDelegate);
platformPlugin.mPlatformMessageHandler.setPreferredOrientations(0);
verify(mockFragmentActivity, times(1)).setRequestedOrientation(0);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册