-
Notifications
You must be signed in to change notification settings - Fork 6k
Android 10+ View.setSystemGestureExclusionRects #11441
Changes from all commits
7b7fa25
361eb40
5245ad3
b37fa16
c155aec
53e858b
8dc8646
e4dac7c
3df584b
676192e
21577fb
87ddf16
13617ee
cdd4e6a
773a7e9
2dca160
57a3bf5
fc972ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,10 @@ | |
| package io.flutter.embedding.engine.systemchannels; | ||
|
|
||
| import android.content.pm.ActivityInfo; | ||
| import android.graphics.Rect; | ||
| import android.support.annotation.NonNull; | ||
| import android.support.annotation.Nullable; | ||
| import android.support.annotation.VisibleForTesting; | ||
|
|
||
| import org.json.JSONArray; | ||
| import org.json.JSONException; | ||
|
|
@@ -32,129 +34,9 @@ public class PlatformChannel { | |
| public final MethodChannel channel; | ||
| @Nullable | ||
| private PlatformMessageHandler platformMessageHandler; | ||
|
|
||
| private final MethodChannel.MethodCallHandler parsingMethodCallHandler = new MethodChannel.MethodCallHandler() { | ||
| @Override | ||
| public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { | ||
| if (platformMessageHandler == null) { | ||
| // If no explicit PlatformMessageHandler has been registered then we don't | ||
| // need to forward this call to an API. Return. | ||
| return; | ||
| } | ||
|
|
||
| String method = call.method; | ||
| Object arguments = call.arguments; | ||
| Log.v(TAG, "Received '" + method + "' message."); | ||
| try { | ||
| switch (method) { | ||
| case "SystemSound.play": | ||
| try { | ||
| SoundType soundType = SoundType.fromValue((String) arguments); | ||
| platformMessageHandler.playSystemSound(soundType); | ||
| result.success(null); | ||
| } catch (NoSuchFieldException exception) { | ||
| // The desired sound type does not exist. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "HapticFeedback.vibrate": | ||
| try { | ||
| HapticFeedbackType feedbackType = HapticFeedbackType.fromValue((String) arguments); | ||
| platformMessageHandler.vibrateHapticFeedback(feedbackType); | ||
| result.success(null); | ||
| } catch (NoSuchFieldException exception) { | ||
| // The desired feedback type does not exist. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemChrome.setPreferredOrientations": | ||
| try { | ||
| int androidOrientation = decodeOrientations((JSONArray) arguments); | ||
| platformMessageHandler.setPreferredOrientations(androidOrientation); | ||
| result.success(null); | ||
| } catch (JSONException | NoSuchFieldException exception) { | ||
| // JSONException: One or more expected fields were either omitted or referenced an invalid type. | ||
| // NoSuchFieldException: One or more expected fields were either omitted or referenced an invalid type. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemChrome.setApplicationSwitcherDescription": | ||
| try { | ||
| AppSwitcherDescription description = decodeAppSwitcherDescription((JSONObject) arguments); | ||
| platformMessageHandler.setApplicationSwitcherDescription(description); | ||
| result.success(null); | ||
| } catch (JSONException exception) { | ||
| // One or more expected fields were either omitted or referenced an invalid type. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemChrome.setEnabledSystemUIOverlays": | ||
| try { | ||
| List<SystemUiOverlay> overlays = decodeSystemUiOverlays((JSONArray) arguments); | ||
| platformMessageHandler.showSystemOverlays(overlays); | ||
| result.success(null); | ||
| } catch (JSONException | NoSuchFieldException exception) { | ||
| // JSONException: One or more expected fields were either omitted or referenced an invalid type. | ||
| // NoSuchFieldException: One or more of the overlay names are invalid. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemChrome.restoreSystemUIOverlays": | ||
| platformMessageHandler.restoreSystemUiOverlays(); | ||
| result.success(null); | ||
| break; | ||
| case "SystemChrome.setSystemUIOverlayStyle": | ||
| try { | ||
| SystemChromeStyle systemChromeStyle = decodeSystemChromeStyle((JSONObject) arguments); | ||
| platformMessageHandler.setSystemUiOverlayStyle(systemChromeStyle); | ||
| result.success(null); | ||
| } catch (JSONException | NoSuchFieldException exception) { | ||
| // JSONException: One or more expected fields were either omitted or referenced an invalid type. | ||
| // NoSuchFieldException: One or more of the brightness names are invalid. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemNavigator.pop": | ||
| platformMessageHandler.popSystemNavigator(); | ||
| result.success(null); | ||
| break; | ||
| case "Clipboard.getData": { | ||
| String contentFormatName = (String) arguments; | ||
| ClipboardContentFormat clipboardFormat = null; | ||
| if (contentFormatName != null) { | ||
| try { | ||
| clipboardFormat = ClipboardContentFormat.fromValue(contentFormatName); | ||
| } catch (NoSuchFieldException exception) { | ||
| // An unsupported content format was requested. Return failure. | ||
| result.error("error", "No such clipboard content format: " + contentFormatName, null); | ||
| } | ||
| } | ||
|
|
||
| CharSequence clipboardContent = platformMessageHandler.getClipboardData(clipboardFormat); | ||
| if (clipboardContent != null) { | ||
| JSONObject response = new JSONObject(); | ||
| response.put("text", clipboardContent); | ||
| result.success(response); | ||
| } else { | ||
| result.success(null); | ||
| } | ||
| break; | ||
| } | ||
| case "Clipboard.setData": { | ||
| String clipboardContent = ((JSONObject) arguments).getString("text"); | ||
| platformMessageHandler.setClipboardData(clipboardContent); | ||
| result.success(null); | ||
| break; | ||
| } | ||
| default: | ||
| result.notImplemented(); | ||
| break; | ||
| } | ||
| } catch (JSONException e) { | ||
| result.error("error", "JSON error: " + e.getMessage(), null); | ||
| } | ||
| } | ||
| }; | ||
| @NonNull | ||
| @VisibleForTesting | ||
| protected final PlatformMethodCallHandler parsingMethodCallHandler = new PlatformMethodCallHandler(); | ||
|
|
||
| /** | ||
| * Constructs a {@code PlatformChannel} that connects Android to the Dart code | ||
|
|
@@ -257,6 +139,40 @@ private int decodeOrientations(@NonNull JSONArray encodedOrientations) throws JS | |
| return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; | ||
| } | ||
|
|
||
| /** | ||
| * Decodes a JSONArray of rectangle data into an ArrayList<Rect>. | ||
| * | ||
| * @throws JSONException if {@code inputRects} does not contain expected keys and value types. | ||
| */ | ||
| @NonNull | ||
| private ArrayList<Rect> decodeRects(@NonNull JSONArray inputRects) throws JSONException { | ||
| ArrayList<Rect> exclusionRects = new ArrayList<Rect>(); | ||
| for (int i = 0; i < inputRects.length(); i++) { | ||
| JSONObject rect = inputRects.getJSONObject(i); | ||
| int top; | ||
| int right; | ||
| int bottom; | ||
| int left; | ||
|
|
||
| try { | ||
| top = rect.getInt("top"); | ||
| right = rect.getInt("right"); | ||
| bottom = rect.getInt("bottom"); | ||
| left = rect.getInt("left"); | ||
| } catch (JSONException exception) { | ||
| throw new JSONException( | ||
| "Incorrect JSON data shape. To set system gesture exclusion rects, \n" + | ||
| "a JSONObject with top, right, bottom and left values need to be set to int values." | ||
| ); | ||
| } | ||
|
|
||
| Rect gestureRect = new Rect(left, top, right, bottom); | ||
| exclusionRects.add(gestureRect); | ||
| } | ||
|
|
||
| return exclusionRects; | ||
| } | ||
|
|
||
| @NonNull | ||
| private AppSwitcherDescription decodeAppSwitcherDescription(@NonNull JSONObject encodedDescription) throws JSONException { | ||
| int color = encodedDescription.getInt("primaryColor"); | ||
|
|
@@ -420,6 +336,12 @@ public interface PlatformMessageHandler { | |
| * clipboard to the given {@code text}. | ||
| */ | ||
| void setClipboardData(@NonNull String text); | ||
|
|
||
| /** | ||
| * The Flutter application would like to set the system gesture exclusion | ||
| * rects through the given {@code rects}. | ||
| */ | ||
| void setSystemGestureExclusionRects(@NonNull ArrayList<Rect> rects); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -581,6 +503,147 @@ public SystemChromeStyle( | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * A handler of incoming platform channel method calls received from Flutter. | ||
| * It first determines the platform's API to be called. If it exists, it then | ||
| * decodes incoming arguments, if needed, into a format that is necessary for the API. | ||
| */ | ||
| @VisibleForTesting | ||
| protected class PlatformMethodCallHandler implements MethodChannel.MethodCallHandler { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like for us to avoid non-static inner classes. I think in this case I'd like for us to revert the movement of this handler definition back to where it was. But in future cases where we do define inner classes, they should be static to avoid the possibility of accidentally leaking references to the outer class.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a discussion with @mklim and we felt that it was difficult to read an inline class definition within another class. I'll revert this in a subsequent PR |
||
| @Override | ||
| public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wish the diff were better here. I spot checked it, assuming that nothing in this was changed from the original besides the new method call. |
||
| if (platformMessageHandler == null) { | ||
| // If no explicit PlatformMessageHandler has been registered then we don't | ||
| // need to forward this call to an API. Return. | ||
| return; | ||
| } | ||
|
|
||
| String method = call.method; | ||
| Object arguments = call.arguments; | ||
| Log.v(TAG, "Received '" + method + "' message."); | ||
| try { | ||
| switch (method) { | ||
| case "SystemSound.play": | ||
| try { | ||
| SoundType soundType = SoundType.fromValue((String) arguments); | ||
| platformMessageHandler.playSystemSound(soundType); | ||
| result.success(null); | ||
| } catch (NoSuchFieldException exception) { | ||
| // The desired sound type does not exist. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "HapticFeedback.vibrate": | ||
| try { | ||
| HapticFeedbackType feedbackType = HapticFeedbackType.fromValue((String) arguments); | ||
| platformMessageHandler.vibrateHapticFeedback(feedbackType); | ||
| result.success(null); | ||
| } catch (NoSuchFieldException exception) { | ||
| // The desired feedback type does not exist. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemChrome.setPreferredOrientations": | ||
| try { | ||
| int androidOrientation = decodeOrientations((JSONArray) arguments); | ||
| platformMessageHandler.setPreferredOrientations(androidOrientation); | ||
| result.success(null); | ||
| } catch (JSONException | NoSuchFieldException exception) { | ||
| // JSONException: One or more expected fields were either omitted or referenced an invalid type. | ||
| // NoSuchFieldException: One or more expected fields were either omitted or referenced an invalid type. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemChrome.setApplicationSwitcherDescription": | ||
| try { | ||
| AppSwitcherDescription description = decodeAppSwitcherDescription((JSONObject) arguments); | ||
| platformMessageHandler.setApplicationSwitcherDescription(description); | ||
| result.success(null); | ||
| } catch (JSONException exception) { | ||
| // One or more expected fields were either omitted or referenced an invalid type. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemChrome.setEnabledSystemUIOverlays": | ||
| try { | ||
| List<SystemUiOverlay> overlays = decodeSystemUiOverlays((JSONArray) arguments); | ||
| platformMessageHandler.showSystemOverlays(overlays); | ||
| result.success(null); | ||
| } catch (JSONException | NoSuchFieldException exception) { | ||
| // JSONException: One or more expected fields were either omitted or referenced an invalid type. | ||
| // NoSuchFieldException: One or more of the overlay names are invalid. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemChrome.restoreSystemUIOverlays": | ||
| platformMessageHandler.restoreSystemUiOverlays(); | ||
| result.success(null); | ||
| break; | ||
| case "SystemChrome.setSystemUIOverlayStyle": | ||
| try { | ||
| SystemChromeStyle systemChromeStyle = decodeSystemChromeStyle((JSONObject) arguments); | ||
| platformMessageHandler.setSystemUiOverlayStyle(systemChromeStyle); | ||
| result.success(null); | ||
| } catch (JSONException | NoSuchFieldException exception) { | ||
| // JSONException: One or more expected fields were either omitted or referenced an invalid type. | ||
| // NoSuchFieldException: One or more of the brightness names are invalid. | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| case "SystemNavigator.pop": | ||
| platformMessageHandler.popSystemNavigator(); | ||
| result.success(null); | ||
| break; | ||
| case "SystemGestures.setSystemGestureExclusionRects": | ||
| if (!(arguments instanceof JSONArray)) { | ||
| String inputTypeError = "Input type is incorrect. Ensure that a List<Map<String, int>> is passed as the input for SystemGestureExclusionRects.setSystemGestureExclusionRects."; | ||
| result.error("inputTypeError", inputTypeError, null); | ||
| break; | ||
| } | ||
|
|
||
| JSONArray inputRects = (JSONArray) arguments; | ||
| ArrayList<Rect> decodedRects = decodeRects(inputRects); | ||
| platformMessageHandler.setSystemGestureExclusionRects(decodedRects); | ||
| result.success(null); | ||
| break; | ||
| case "Clipboard.getData": { | ||
| String contentFormatName = (String) arguments; | ||
| ClipboardContentFormat clipboardFormat = null; | ||
| if (contentFormatName != null) { | ||
| try { | ||
| clipboardFormat = ClipboardContentFormat.fromValue(contentFormatName); | ||
| } catch (NoSuchFieldException exception) { | ||
| // An unsupported content format was requested. Return failure. | ||
| result.error("error", "No such clipboard content format: " + contentFormatName, null); | ||
| } | ||
| } | ||
|
|
||
| CharSequence clipboardContent = platformMessageHandler.getClipboardData(clipboardFormat); | ||
| if (clipboardContent != null) { | ||
| JSONObject response = new JSONObject(); | ||
| response.put("text", clipboardContent); | ||
| result.success(response); | ||
| } else { | ||
| result.success(null); | ||
| } | ||
| break; | ||
| } | ||
| case "Clipboard.setData": { | ||
| String clipboardContent = ((JSONObject) arguments).getString("text"); | ||
| platformMessageHandler.setClipboardData(clipboardContent); | ||
| result.success(null); | ||
| break; | ||
| } | ||
| default: | ||
| result.notImplemented(); | ||
| break; | ||
| } | ||
| } catch (JSONException e) { | ||
| result.error("error", "JSON error: " + e.getMessage(), null); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public enum Brightness { | ||
| LIGHT("Brightness.light"), | ||
| DARK("Brightness.dark"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| import android.content.ClipData; | ||
| import android.content.ClipboardManager; | ||
| import android.content.Context; | ||
| import android.graphics.Rect; | ||
| import android.os.Build; | ||
| import android.support.annotation.NonNull; | ||
| import android.support.annotation.Nullable; | ||
|
|
@@ -17,6 +18,8 @@ | |
| import android.view.View; | ||
| import android.view.Window; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
|
|
||
| import io.flutter.embedding.engine.systemchannels.PlatformChannel; | ||
|
|
@@ -84,6 +87,11 @@ public CharSequence getClipboardData(@Nullable PlatformChannel.ClipboardContentF | |
| public void setClipboardData(@NonNull String text) { | ||
| PlatformPlugin.this.setClipboardData(text); | ||
| } | ||
|
|
||
| @Override | ||
| public void setSystemGestureExclusionRects(@NonNull ArrayList rects) { | ||
| PlatformPlugin.this.setSystemGestureExclusionRects(rects); | ||
| } | ||
| }; | ||
|
|
||
| public PlatformPlugin(Activity activity, PlatformChannel platformChannel) { | ||
|
|
@@ -272,4 +280,14 @@ private void setClipboardData(String text) { | |
| ClipData clip = ClipData.newPlainText("text label?", text); | ||
| clipboard.setPrimaryClip(clip); | ||
| } | ||
|
|
||
| private void setSystemGestureExclusionRects(ArrayList<Rect> rects) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the rest of the PR besides this method could be tested, even with the SDK limitations. Even though this is never called, I think you could check that the arguments are parsed safely in
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense! I'll give it a shot |
||
| if (Build.VERSION.SDK_INT < 29) { | ||
| return; | ||
| } | ||
|
|
||
| Window window = activity.getWindow(); | ||
| View view = window.getDecorView(); | ||
| view.setSystemGestureExclusionRects(rects); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shihaohong why was this moved?