diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java index 0ad84d74db89b..11cf6c48225c6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java @@ -15,6 +15,7 @@ import org.json.JSONObject; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import io.flutter.Log; @@ -145,7 +146,7 @@ private int decodeOrientations(@NonNull JSONArray encodedOrientations) throws JS * @throws JSONException if {@code inputRects} does not contain expected keys and value types. */ @NonNull - private ArrayList decodeRects(@NonNull JSONArray inputRects) throws JSONException { + private ArrayList decodeExclusionRects(@NonNull JSONArray inputRects) throws JSONException { ArrayList exclusionRects = new ArrayList(); for (int i = 0; i < inputRects.length(); i++) { JSONObject rect = inputRects.getJSONObject(i); @@ -173,6 +174,31 @@ private ArrayList decodeRects(@NonNull JSONArray inputRects) throws JSONEx return exclusionRects; } + /** + * Encodes a List provided by the Android host into an + * ArrayList>. + * + * Since View.getSystemGestureExclusionRects returns a list of Rects, these + * Rects need to be transformed into UTF-8 encoded JSON messages to be + * properly decoded by the Flutter framework. + * + * This method is used by the SystemGestures.getSystemGestureExclusionRects + * platform channel. + */ + private ArrayList> encodeExclusionRects(List exclusionRects) { + ArrayList> encodedExclusionRects = new ArrayList>(); + for (Rect rect : exclusionRects) { + HashMap rectMap = new HashMap(); + rectMap.put("top", rect.top); + rectMap.put("right", rect.right); + rectMap.put("bottom", rect.bottom); + rectMap.put("left", rect.left); + encodedExclusionRects.add(rectMap); + } + + return encodedExclusionRects; + } + @NonNull private AppSwitcherDescription decodeAppSwitcherDescription(@NonNull JSONObject encodedDescription) throws JSONException { int color = encodedDescription.getInt("primaryColor"); @@ -337,6 +363,12 @@ public interface PlatformMessageHandler { */ void setClipboardData(@NonNull String text); + /** + * The Flutter application would like to get the system gesture exclusion + * rects. + */ + List getSystemGestureExclusionRects(); + /** * The Flutter application would like to set the system gesture exclusion * rects through the given {@code rects}. @@ -594,18 +626,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result platformMessageHandler.popSystemNavigator(); result.success(null); break; - case "SystemGestures.setSystemGestureExclusionRects": - if (!(arguments instanceof JSONArray)) { - String inputTypeError = "Input type is incorrect. Ensure that a List> is passed as the input for SystemGestureExclusionRects.setSystemGestureExclusionRects."; - result.error("inputTypeError", inputTypeError, null); - break; - } - - JSONArray inputRects = (JSONArray) arguments; - ArrayList decodedRects = decodeRects(inputRects); - platformMessageHandler.setSystemGestureExclusionRects(decodedRects); - result.success(null); - break; case "Clipboard.getData": { String contentFormatName = (String) arguments; ClipboardContentFormat clipboardFormat = null; @@ -634,6 +654,30 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result.success(null); break; } + case "SystemGestures.setSystemGestureExclusionRects": + if (!(arguments instanceof JSONArray)) { + String inputTypeError = "Input type is incorrect. Ensure that a List> is passed as the input for SystemGestureExclusionRects.setSystemGestureExclusionRects."; + result.error("inputTypeError", inputTypeError, null); + break; + } + + JSONArray inputRects = (JSONArray) arguments; + ArrayList decodedRects = decodeExclusionRects(inputRects); + platformMessageHandler.setSystemGestureExclusionRects(decodedRects); + result.success(null); + break; + case "SystemGestures.getSystemGestureExclusionRects": { + List exclusionRects = platformMessageHandler.getSystemGestureExclusionRects(); + if (exclusionRects == null) { + String incorrectApiLevel = "Exclusion rects only exist for Android API 29+."; + result.error("error", incorrectApiLevel, null); + break; + } + + ArrayList> encodedExclusionRects = encodeExclusionRects(exclusionRects); + result.success(encodedExclusionRects); + break; + } default: result.notImplemented(); break; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index 7a9c9c8059867..789366c6b4133 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -88,6 +88,11 @@ public void setClipboardData(@NonNull String text) { PlatformPlugin.this.setClipboardData(text); } + @Override + public List getSystemGestureExclusionRects() { + return PlatformPlugin.this.getSystemGestureExclusionRects(); + } + @Override public void setSystemGestureExclusionRects(@NonNull ArrayList rects) { PlatformPlugin.this.setSystemGestureExclusionRects(rects); @@ -281,6 +286,16 @@ private void setClipboardData(String text) { clipboard.setPrimaryClip(clip); } + private List getSystemGestureExclusionRects() { + if (Build.VERSION.SDK_INT >= 29) { + Window window = activity.getWindow(); + View view = window.getDecorView(); + return view.getSystemGestureExclusionRects(); + } + + return null; + } + private void setSystemGestureExclusionRects(ArrayList rects) { if (Build.VERSION.SDK_INT < 29) { return; diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java index 31d7d71c39fd8..93b06c2e961ac 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java @@ -3,6 +3,7 @@ import android.graphics.Rect; import java.util.ArrayList; +import java.util.HashMap; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.systemchannels.PlatformChannel; @@ -21,6 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @Config(manifest=Config.NONE) @RunWith(RobolectricTestRunner.class) @@ -50,12 +52,12 @@ public void setSystemExclusionRectsSendsSuccessMessageToFramework() throws JSONE Rect gestureRect = new Rect(left, top, right, bottom); expectedDecodedRects.add(gestureRect); - MethodCall callSystemGestureExclusionRects = new MethodCall( + MethodCall callSetSystemGestureExclusionRects = new MethodCall( "SystemGestures.setSystemGestureExclusionRects", inputRects ); - platformChannel.parsingMethodCallHandler.onMethodCall(callSystemGestureExclusionRects, resultsMock); + platformChannel.parsingMethodCallHandler.onMethodCall(callSetSystemGestureExclusionRects, resultsMock); verify(platformMessageHandler, times(1)).setSystemGestureExclusionRects(expectedDecodedRects); verify(resultsMock, times(1)).success(null); } @@ -69,11 +71,11 @@ public void setSystemExclusionRectsRequiresJSONArrayInput() { ResultsMock resultsMock = mock(ResultsMock.class); String nonJsonInput = "Non-JSON"; - MethodCall callSystemGestureExclusionRects = new MethodCall( + MethodCall callSetSystemGestureExclusionRects = new MethodCall( "SystemGestures.setSystemGestureExclusionRects", nonJsonInput ); - platformChannel.parsingMethodCallHandler.onMethodCall(callSystemGestureExclusionRects, resultsMock); + platformChannel.parsingMethodCallHandler.onMethodCall(callSetSystemGestureExclusionRects, resultsMock); String inputTypeError = "Input type is incorrect. Ensure that a List> is passed as the input for SystemGestureExclusionRects.setSystemGestureExclusionRects."; verify(resultsMock, times(1)).error( @@ -100,11 +102,11 @@ public void setSystemExclusionRectsSendsJSONExceptionOnIncorrectDataShape() thro JSONArray inputArray = new JSONArray(); inputArray.put(jsonObject); - MethodCall callSystemGestureExclusionRects = new MethodCall( + MethodCall callSetSystemGestureExclusionRects = new MethodCall( "SystemGestures.setSystemGestureExclusionRects", inputArray ); - platformChannel.parsingMethodCallHandler.onMethodCall(callSystemGestureExclusionRects, resultsMock); + platformChannel.parsingMethodCallHandler.onMethodCall(callSetSystemGestureExclusionRects, resultsMock); verify(resultsMock, times(1)).error( "error", "JSON error: Incorrect JSON data shape. To set system gesture exclusion rects, \n" + @@ -113,6 +115,67 @@ public void setSystemExclusionRectsSendsJSONExceptionOnIncorrectDataShape() thro ); } + @Test + public void itSendsSuccessMessageToFrameworkWhenGettingSystemGestureExclusionRects() throws JSONException { + // --- Test Setup --- + DartExecutor dartExecutor = mock(DartExecutor.class); + PlatformChannel platformChannel = new PlatformChannel(dartExecutor); + PlatformMessageHandler platformMessageHandler = mock(PlatformMessageHandler.class); + platformChannel.setPlatformMessageHandler(platformMessageHandler); + Result result = mock(Result.class); + + // Fake API output setup + ArrayList fakeExclusionRects = new ArrayList(); + Rect gestureRect = new Rect(0, 0, 500, 250); + fakeExclusionRects.add(gestureRect); + when(platformMessageHandler.getSystemGestureExclusionRects()).thenReturn(fakeExclusionRects); + + // Parsed API output that should be passed to result.success() + ArrayList> expectedEncodedOutputRects = new ArrayList>(); + HashMap rectMap = new HashMap(); + rectMap.put("top", 0); + rectMap.put("right", 500); + rectMap.put("bottom", 250); + rectMap.put("left", 0); + expectedEncodedOutputRects.add(rectMap); + MethodCall callGetSystemGestureExclusionRects = new MethodCall( + "SystemGestures.getSystemGestureExclusionRects", + null + ); + + // --- Execute Test --- + platformChannel.parsingMethodCallHandler.onMethodCall(callGetSystemGestureExclusionRects, result); + + // --- Verify Results --- + verify(result, times(1)).success(expectedEncodedOutputRects); + } + + @Test + public void itSendsAPILevelErrorWhenAndroidVersionIsTooLowWhenGettingSystemGestureExclusionRects() { + // --- Test Setup --- + DartExecutor dartExecutor = mock(DartExecutor.class); + PlatformChannel platformChannel = new PlatformChannel(dartExecutor); + PlatformMessageHandler platformMessageHandler = mock(PlatformMessageHandler.class); + platformChannel.setPlatformMessageHandler(platformMessageHandler); + when(platformMessageHandler.getSystemGestureExclusionRects()).thenReturn(null); + Result result = mock(Result.class); + + MethodCall callGetSystemGestureExclusionRects = new MethodCall( + "SystemGestures.getSystemGestureExclusionRects", + null + ); + + // --- Execute Test --- + platformChannel.parsingMethodCallHandler.onMethodCall(callGetSystemGestureExclusionRects, result); + + // --- Verify Results --- + verify(result, times(1)).error( + "error", + "Exclusion rects only exist for Android API 29+.", + null + ); + } + private class ResultsMock implements Result { @Override public void success(Object result) {}