Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import androidx.core.view.WindowCompat;
import io.flutter.Log;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.util.Preconditions;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterNativeView;
Expand Down Expand Up @@ -141,7 +141,14 @@ public void onCreate(Bundle savedInstanceState) {
Window window = activity.getWindow();
window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0x40000000);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
WindowCompat.setDecorFitsSystemWindows(window, false);
if (Build.VERSION.SDK_INT < 30) {
// This ensures that the navigation bar is not hidden for APIs < 30,
// as dictated by the implementation of WindowCompat.
View view = window.getDecorView();
view.setSystemUiVisibility(
view.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
}

String[] args = getArgsFromIntent(activity.getIntent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.WindowCompat;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
Expand Down Expand Up @@ -590,7 +591,14 @@ private void configureStatusBarForFullscreenFlutterExperience() {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0x40000000);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
WindowCompat.setDecorFitsSystemWindows(window, false);
if (Build.VERSION.SDK_INT < 30) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it need to also check for Build.VERSION.SDK_INT >= 19?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because it works for SDK >= 16! You can see that in the source code at the bottom of this page.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like this caused a regression for SDK_INT >= 30. See b/223628114

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm investigating a fix for this to re-land it.

// This ensures that the navigation bar is not hidden for APIs < 30,
// as dictated by the implementation of WindowCompat.
View view = window.getDecorView();
view.setSystemUiVisibility(
view.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.WindowCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.util.ViewUtils;

/**
Expand Down Expand Up @@ -495,7 +495,14 @@ private void configureStatusBarForFullscreenFlutterExperience() {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0x40000000);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
WindowCompat.setDecorFitsSystemWindows(window, false);
if (Build.VERSION.SDK_INT < 30) {
// This ensures that the navigation bar is not hidden for APIs < 30,
// as dictated by the implementation of WindowCompat.
View view = window.getDecorView();
view.setSystemUiVisibility(
view.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
}
}

Expand Down
190 changes: 139 additions & 51 deletions shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import io.flutter.Log;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
Expand All @@ -28,14 +30,13 @@

/** Android implementation of the platform plugin. */
public class PlatformPlugin {
public static final int DEFAULT_SYSTEM_UI =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;

private final Activity activity;
private final PlatformChannel platformChannel;
private final PlatformPluginDelegate platformPluginDelegate;
private PlatformChannel.SystemChromeStyle currentTheme;
private int mEnabledOverlays;
private PlatformChannel.SystemUiMode currentSystemUiMode;
private List<PlatformChannel.SystemUiOverlay> currentOverlays;
private static final String TAG = "PlatformPlugin";

/**
Expand Down Expand Up @@ -140,8 +141,6 @@ public PlatformPlugin(
this.platformChannel = platformChannel;
this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
this.platformPluginDelegate = delegate;

mEnabledOverlays = DEFAULT_SYSTEM_UI;
}

/**
Expand Down Expand Up @@ -238,17 +237,75 @@ public void onSystemUiVisibilityChange(int visibility) {
}

private void setSystemChromeEnabledSystemUIMode(PlatformChannel.SystemUiMode systemUiMode) {
if (Build.VERSION.SDK_INT <= 19) {
// As of API 30, the Android APIs for overlays/insets provides backwards compatibility back
// through API 19. Since Flutter currently supports API 19, the legacy code is used for
// that case.
setSystemChromeEnabledSystemUIModeLegacy(systemUiMode);
} else {
Window window = activity.getWindow();
View view = window.getDecorView();
WindowInsetsControllerCompat windowInsetsControllerCompat =
new WindowInsetsControllerCompat(window, view);

if (systemUiMode == PlatformChannel.SystemUiMode.LEAN_BACK) {
// LEAN BACK
// Available starting at SDK 16. Implemented for API 20+ here.
// Should not show overlays, tap to reveal overlays, needs onChange callback
// When the overlays come in on tap, the app does not receive the gesture and does not know
// the system overlay has changed. The overlays cannot be dismissed, so adding the callback
// support will allow users to restore the system ui and dismiss the overlays.
// Not compatible with top/bottom overlays enabled.
windowInsetsControllerCompat.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH);
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars());
WindowCompat.setDecorFitsSystemWindows(window, false);
} else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE) {
// IMMERSIVE
// Available starting at SDK 19. Implemented for API 20+ here.
// Should not show overlays, swipe from edges to reveal overlays, needs onChange callback
// When the overlays come in on swipe, the app does not receive the gesture and does not
// know the system overlay has changed. The overlays cannot be dismissed, so adding callback
// support will allow users to restore the system ui and dismiss the overlays.
// Not compatible with top/bottom overlays enabled.
windowInsetsControllerCompat.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE);
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars());
WindowCompat.setDecorFitsSystemWindows(window, false);
} else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE_STICKY) {
// STICKY IMMERSIVE
// Available starting at SDK 19. Implemented for API 20+ here.
// Should not show overlays, swipe from edges to reveal overlays. The app will also receive
// the swipe gesture. The overlays cannot be dismissed, so adding callback support will
// allow users to restore the system ui and dismiss the overlays.
// Not compatible with top/bottom overlays enabled.
windowInsetsControllerCompat.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars());
WindowCompat.setDecorFitsSystemWindows(window, false);
} else if (systemUiMode == PlatformChannel.SystemUiMode.EDGE_TO_EDGE
&& Build.VERSION.SDK_INT >= 29) {
// EDGE TO EDGE
// Available starting at SDK 29. See issue for context:
// https://github.com/flutter/flutter/issues/89774.
// Will apply a translucent body scrim behind 2/3 button navigation bars
// to ensure contrast with buttons on the nav and status bars, unless the contrast is not
// enforced in the overlay styling.
WindowCompat.setDecorFitsSystemWindows(window, false);
} else {
// When none of the conditions are matched, return without updating the system UI overlays.
return;
}
}
currentSystemUiMode = systemUiMode;
}

private void setSystemChromeEnabledSystemUIModeLegacy(PlatformChannel.SystemUiMode systemUiMode) {
int enabledOverlays;

if (systemUiMode == PlatformChannel.SystemUiMode.LEAN_BACK
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (systemUiMode == PlatformChannel.SystemUiMode.LEAN_BACK) {
// LEAN BACK
// Available starting at SDK 16
// Should not show overlays, tap to reveal overlays, needs onChange callback
// When the overlays come in on tap, the app does not receive the gesture and does not know
// the system overlay has changed. The overlays cannot be dismissed, so adding the callback
// support will allow users to restore the system ui and dismiss the overlays.
// Not compatible with top/bottom overlays enabled.
// Available starting at SDK 16. Implemented for APIs 16-19 here.
enabledOverlays =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Expand All @@ -258,12 +315,8 @@ private void setSystemChromeEnabledSystemUIMode(PlatformChannel.SystemUiMode sys
} else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// IMMERSIVE
// Available starting at 19
// Should not show overlays, swipe from edges to reveal overlays, needs onChange callback
// When the overlays come in on swipe, the app does not receive the gesture and does not know
// the system overlay has changed. The overlays cannot be dismissed, so adding callback
// support will allow users to restore the system ui and dismiss the overlays.
// Not compatible with top/bottom overlays enabled.
// Available starting at SDK 19. Implemented for API 19 here. Earlier versions will not be
// affected by this.
enabledOverlays =
View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
Expand All @@ -274,56 +327,78 @@ private void setSystemChromeEnabledSystemUIMode(PlatformChannel.SystemUiMode sys
} else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE_STICKY
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// STICKY IMMERSIVE
// Available starting at 19
// Should not show overlays, swipe from edges to reveal overlays. The app will also receive
// the swipe gesture. The overlays cannot be dismissed, so adding callback support will
// allow users to restore the system ui and dismiss the overlays.
// Not compatible with top/bottom overlays enabled.
// Available starting at SDK 19. Implemented for API 19 here. Earlier versions will not be
// affected by this.
enabledOverlays =
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN;
} else if (systemUiMode == PlatformChannel.SystemUiMode.EDGE_TO_EDGE
&& Build.VERSION.SDK_INT >= 29) {
// EDGE TO EDGE
// Available starting at 29
// SDK 29 and up will apply a translucent body scrim behind 2/3 button navigation bars
// to ensure contrast with buttons on the nav and status bars, unless the contrast is not
// enforced in the overlay styling.
enabledOverlays =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
} else {
// When none of the conditions are matched, return without updating the system UI overlays.
return;
}

mEnabledOverlays = enabledOverlays;
updateSystemUiOverlays();
activity.getWindow().getDecorView().setSystemUiVisibility(enabledOverlays);
}

private void setSystemChromeEnabledSystemUIOverlays(
List<PlatformChannel.SystemUiOverlay> overlaysToShow) {
// Start by assuming we want to hide all system overlays (like an immersive
// game).
if (Build.VERSION.SDK_INT <= 19) {
// As of API 30, the Android APIs for overlays/insets provides backwards compatibility back
// through API 19. Since Flutter currently supports API 19, the legacy code is used
// for that case.
setSystemChromeEnabledSystemUIOverlaysLegacy(overlaysToShow);
} else {
Window window = activity.getWindow();
View view = window.getDecorView();
WindowInsetsControllerCompat windowInsetsControllerCompat =
new WindowInsetsControllerCompat(window, view);

// Start by assuming we want to hide all system overlays (like an immersive
// game).
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars());
WindowCompat.setDecorFitsSystemWindows(window, false);

// We apply sticky immersive mode if desired. Available starting at SDK 20.
if (overlaysToShow.size() == 0) {
currentSystemUiMode = PlatformChannel.SystemUiMode.IMMERSIVE_STICKY;
windowInsetsControllerCompat.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}

// Re-add any desired system overlays.
for (int i = 0; i < overlaysToShow.size(); ++i) {
PlatformChannel.SystemUiOverlay overlayToShow = overlaysToShow.get(i);
switch (overlayToShow) {
case TOP_OVERLAYS:
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.statusBars());
break;
case BOTTOM_OVERLAYS:
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars());
break;
}
}
}
currentOverlays = overlaysToShow;
}

private void setSystemChromeEnabledSystemUIOverlaysLegacy(
List<PlatformChannel.SystemUiOverlay> overlaysToShow) {
int enabledOverlays =
DEFAULT_SYSTEM_UI
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| 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
// if desired, and if the current Android version is 19 or greater.
// apply it if desired.
if (overlaysToShow.size() == 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
currentSystemUiMode = PlatformChannel.SystemUiMode.IMMERSIVE_STICKY;
enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}

// Re-add any desired system overlays.
for (int i = 0; i < overlaysToShow.size(); ++i) {
PlatformChannel.SystemUiOverlay overlayToShow = overlaysToShow.get(i);
switch (overlayToShow) {
Expand All @@ -336,9 +411,7 @@ private void setSystemChromeEnabledSystemUIOverlays(
break;
}
}

mEnabledOverlays = enabledOverlays;
updateSystemUiOverlays();
activity.getWindow().getDecorView().setSystemUiVisibility(enabledOverlays);
}

/**
Expand All @@ -350,9 +423,24 @@ private void setSystemChromeEnabledSystemUIOverlays(
* PlatformPlugin}.
*/
public void updateSystemUiOverlays() {
activity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays);
if (currentTheme != null) {
setSystemChromeSystemUIOverlayStyle(currentTheme);
if (currentOverlays != null) {
setSystemChromeEnabledSystemUIOverlays(currentOverlays);

if (currentOverlays.size() > 0) {
if (currentSystemUiMode != null) {
setSystemChromeEnabledSystemUIMode(currentSystemUiMode);
} else {
Window window = activity.getWindow();
WindowCompat.setDecorFitsSystemWindows(window, false);
if (Build.VERSION.SDK_INT < 30) {
// This ensures that the navigation bar is not hidden for APIs < 30,
// as dictated by the implementation of WindowCompat.
View view = window.getDecorView();
view.setSystemUiVisibility(
view.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
}
}
}
}

Expand Down
Loading