From 74e8ce7efad55c078b1e91bc11bfe21ab78053c8 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 4 Aug 2025 08:16:31 +0000 Subject: [PATCH] restrict souce of input event sto parent view on android (#19289) --- .../Avalonia.Android/AvaloniaActivity.cs | 5 +- .../Avalonia.Android/AvaloniaView.Input.cs | 67 +++++++++++++++++++ src/Android/Avalonia.Android/AvaloniaView.cs | 21 +----- .../Platform/Input/AndroidInputMethod.cs | 3 - .../Platform/SkiaPlatform/TopLevelImpl.cs | 61 +++-------------- 5 files changed, 82 insertions(+), 75 deletions(-) create mode 100644 src/Android/Avalonia.Android/AvaloniaView.Input.cs diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs index fa3484f058..cf425d279e 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs @@ -8,10 +8,10 @@ using Android.OS; using Android.Runtime; using Android.Views; using AndroidX.AppCompat.App; -using Avalonia.Platform; using Avalonia.Android.Platform; using Avalonia.Android.Platform.Storage; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform; namespace Avalonia.Android; @@ -48,6 +48,9 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity SetContentView(_view); + // By default, the view isn't focused if the activity is created anew, so we force focus. + _view.RequestFocus(); + _listener = new GlobalLayoutListener(_view); _view.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener); diff --git a/src/Android/Avalonia.Android/AvaloniaView.Input.cs b/src/Android/Avalonia.Android/AvaloniaView.Input.cs new file mode 100644 index 0000000000..c829be56be --- /dev/null +++ b/src/Android/Avalonia.Android/AvaloniaView.Input.cs @@ -0,0 +1,67 @@ +using System; +using Android.Views; +using Android.Views.InputMethods; +using Avalonia.Android.Platform.SkiaPlatform; + +namespace Avalonia.Android +{ + public partial class AvaloniaView : IInitEditorInfo + { + private Func? _initEditorInfo; + + public override IInputConnection OnCreateInputConnection(EditorInfo? outAttrs) + { + return _initEditorInfo?.Invoke(_view, outAttrs!)!; + } + + void IInitEditorInfo.InitEditorInfo(Func init) + { + _initEditorInfo = init; + } + + protected override void OnFocusChanged(bool gainFocus, FocusSearchDirection direction, global::Android.Graphics.Rect? previouslyFocusedRect) + { + base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect); + _accessHelper.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect); + } + + protected override bool DispatchHoverEvent(MotionEvent? e) + { + return _accessHelper.DispatchHoverEvent(e!) || base.DispatchHoverEvent(e); + } + + protected override bool DispatchGenericPointerEvent(MotionEvent? e) + { + var result = _view.PointerHelper.DispatchMotionEvent(e, out var callBase); + + var baseResult = callBase && base.DispatchGenericPointerEvent(e); + + return result ?? baseResult; + } + + public override bool DispatchTouchEvent(MotionEvent? e) + { + var result = _view.PointerHelper.DispatchMotionEvent(e, out var callBase); + var baseResult = callBase && base.DispatchTouchEvent(e); + + if(result == true) + { + // Request focus for this view + RequestFocus(); + } + + return result ?? baseResult; + } + + public override bool DispatchKeyEvent(KeyEvent? e) + { + var res = _view.KeyboardHelper.DispatchKeyEvent(e, out var callBase); + if (res == false) + callBase = !_accessHelper.DispatchKeyEvent(e!) && callBase; + + var baseResult = callBase && base.DispatchKeyEvent(e); + + return res ?? baseResult; + } + } +} diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index ced2f11077..665feb2e2b 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -17,7 +17,7 @@ using Avalonia.Rendering; namespace Avalonia.Android { - public class AvaloniaView : FrameLayout + public partial class AvaloniaView : FrameLayout { private EmbeddableControlRoot _root; private readonly ViewImpl _view; @@ -71,24 +71,6 @@ namespace Avalonia.Android _root = null!; } - protected override void OnFocusChanged(bool gainFocus, FocusSearchDirection direction, global::Android.Graphics.Rect? previouslyFocusedRect) - { - base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect); - _accessHelper.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect); - } - - protected override bool DispatchHoverEvent(MotionEvent? e) - { - return _accessHelper.DispatchHoverEvent(e!) || base.DispatchHoverEvent(e); - } - - public override bool DispatchKeyEvent(KeyEvent? e) - { - if (!_view.View.DispatchKeyEvent(e)) - return _accessHelper.DispatchKeyEvent(e!) || base.DispatchKeyEvent(e); - return true; - } - [SupportedOSPlatform("android24.0")] public override void OnVisibilityAggregated(bool isVisible) { @@ -149,7 +131,6 @@ namespace Avalonia.Android { public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView) { - View.Focusable = true; View.FocusChange += ViewImpl_FocusChange; } diff --git a/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs b/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs index 8003db6607..2e8e145ef8 100644 --- a/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs @@ -44,9 +44,6 @@ namespace Avalonia.Android.Platform.Input public AndroidInputMethod(TView host) { - if (host.OnCheckIsTextEditor() == false) - throw new InvalidOperationException("Host should return true from OnCheckIsTextEditor()"); - _host = host; _imm = host.Context?.GetSystemService(Context.InputMethodService).JavaCast() ?? throw new InvalidOperationException("Context.InputMethodService is expected to be not null."); diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 00ae95abaf..142657c8ce 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -5,9 +5,7 @@ using Android.Content; using Android.Graphics; using Android.Graphics.Drawables; using Android.Runtime; -using Android.Text; using Android.Views; -using Android.Views.InputMethods; using AndroidX.AppCompat.App; using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Specific; @@ -15,13 +13,11 @@ using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Storage; using Avalonia.Controls; using Avalonia.Controls.Platform; -using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.OpenGL.Egl; -using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Rendering.Composition; @@ -34,7 +30,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform { private readonly AndroidKeyboardEventsHelper _keyboardHelper; private readonly AndroidMotionEventsHelper _pointerHelper; - private readonly AndroidInputMethod _textInputMethod; + private readonly AndroidInputMethod _textInputMethod; private readonly INativeControlHostImpl _nativeControlHost; private readonly IStorageProvider? _storageProvider; private readonly AndroidSystemNavigationManagerImpl _systemNavigationManager; @@ -42,7 +38,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly ClipboardImpl _clipboard; private readonly AndroidLauncher? _launcher; private readonly AndroidScreens? _screens; - private ViewImpl _view; + private SurfaceViewImpl _view; private WindowTransparencyLevel _transparencyLevel; public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false) @@ -52,8 +48,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform throw new ArgumentException("AvaloniaView.Context must not be null"); } - _view = new ViewImpl(avaloniaView.Context, this, placeOnTop); - _textInputMethod = new AndroidInputMethod(_view); + _view = new SurfaceViewImpl(avaloniaView.Context, this, placeOnTop); + _textInputMethod = new AndroidInputMethod(avaloniaView); _keyboardHelper = new AndroidKeyboardEventsHelper(this); _pointerHelper = new AndroidMotionEventsHelper(this); _clipboard = new ClipboardImpl(avaloniaView.Context.GetSystemService(Context.ClipboardService).JavaCast()); @@ -141,13 +137,13 @@ namespace Avalonia.Android.Platform.SkiaPlatform Resized?.Invoke(size, WindowResizeReason.Layout); } - sealed class ViewImpl : InvalidationAwareSurfaceView, IInitEditorInfo + sealed class SurfaceViewImpl : InvalidationAwareSurfaceView { private readonly TopLevelImpl _tl; private Size _oldSize; private double _oldScaling; - public ViewImpl(Context context, TopLevelImpl tl, bool placeOnTop) : base(context) + public SurfaceViewImpl(Context context, TopLevelImpl tl, bool placeOnTop) : base(context) { _tl = tl; if (placeOnTop) @@ -176,30 +172,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform base.DispatchDraw(canvas); } - protected override bool DispatchGenericPointerEvent(MotionEvent? e) - { - var result = _tl._pointerHelper.DispatchMotionEvent(e, out var callBase); - var baseResult = callBase && base.DispatchGenericPointerEvent(e); - - return result ?? baseResult; - } - - public override bool DispatchTouchEvent(MotionEvent? e) - { - var result = _tl._pointerHelper.DispatchMotionEvent(e, out var callBase); - var baseResult = callBase && base.DispatchTouchEvent(e); - - return result ?? baseResult; - } - - public override bool DispatchKeyEvent(KeyEvent? e) - { - var res = _tl._keyboardHelper.DispatchKeyEvent(e, out var callBase); - var baseResult = callBase && base.DispatchKeyEvent(e); - - return res ?? baseResult; - } - public override void SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height) { base.SurfaceChanged(holder, format, width, height); @@ -232,23 +204,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform _tl.Compositor.RequestCompositionUpdate(drawingFinished.Run); base.SurfaceRedrawNeededAsync(holder, drawingFinished); } - - public override bool OnCheckIsTextEditor() - { - return true; - } - - private Func? _initEditorInfo; - - public void InitEditorInfo(Func init) - { - _initEditorInfo = init; - } - - public override IInputConnection OnCreateInputConnection(EditorInfo? outAttrs) - { - return _initEditorInfo?.Invoke(_tl, outAttrs!)!; - } } public IPopupImpl? CreatePopup() => null; @@ -291,6 +246,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size => _view.Size; double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => _view.Scaling; + internal AndroidKeyboardEventsHelper KeyboardHelper => _keyboardHelper; + + internal AndroidMotionEventsHelper PointerHelper => _pointerHelper; + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { if (_view.Context is not AvaloniaMainActivity activity)