File: Android\AppCompat\Platform.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Android.Content;
using Android.OS;
using Android.Views;
using Android.Views.Animations;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Graphics;
using AView = Android.Views.View;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
	[Obsolete]
	[TypeConverter(typeof(PlatformTypeConverter))]
	public class Platform : BindableObject, IPlatformLayout, INavigation
	{
		readonly Context _context;
		readonly PlatformRenderer _renderer;
		bool _disposed;
		bool _navAnimationInProgress;
		NavigationModel _navModel = new NavigationModel();
		NavigationModel _previousNavModel = null;
		readonly bool _embedded;
 
		internal static string PackageName { get; private set; }
		internal static string GetPackageName() => PackageName;
 
		internal const string CloseContextActionsSignalName = "Xamarin.CloseContextActions";
 
		internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer", typeof(IVisualElementRenderer), typeof(Platform), default(IVisualElementRenderer),
			propertyChanged: (bindable, oldvalue, newvalue) =>
			{
				var view = bindable as VisualElement;
				if (view != null)
					view.IsPlatformEnabled = newvalue != null;
 
				if (bindable is IView mauiView)
				{
					if (mauiView.Handler == null && newvalue is IVisualElementRenderer ver)
						mauiView.Handler = new RendererToHandlerShim(ver);
				}
 
			});
 
		internal Platform(Context context) : this(context, false)
		{
		}
 
		internal Platform(Context context, bool embedded)
		{
			_embedded = embedded;
			_context = context;
			PackageName = context?.PackageName;
			_renderer = new PlatformRenderer(context, this);
			var activity = _context.GetActivity();
 
			if (embedded && activity != null)
			{
				// Set up handling of DisplayAlert/DisplayActionSheet/UpdateProgressBarVisibility
				if (_context == null)
				{
					// Can't show dialogs if it's not an activity
					return;
				}
 
				PopupManager.Subscribe(_context.GetActivity());
				return;
			}
 
			FormsAppCompatActivity.BackPressed += HandleBackPressed;
		}
 
		internal bool NavAnimationInProgress
		{
			get { return _navAnimationInProgress; }
			set
			{
				if (_navAnimationInProgress == value)
					return;
				_navAnimationInProgress = value;
				if (value)
					MessagingCenter.Send(this, CloseContextActionsSignalName);
			}
		}
 
		Page Page { get; set; }
 
		IPageController CurrentPageController => _navModel.CurrentPage;
 
		internal void Dispose()
		{
			if (_disposed)
				return;
			_disposed = true;
 
			FormsAppCompatActivity.BackPressed -= HandleBackPressed;
 
			SetPage(null);
 
			var activity = _context?.GetActivity();
			if (_embedded && activity != null)
			{
				PopupManager.Unsubscribe(activity);
			}
		}
 
		void INavigation.InsertPageBefore(Page page, Page before)
		{
			throw new InvalidOperationException("InsertPageBefore is not supported globally on Android, please use a NavigationPage.");
		}
 
		IReadOnlyList<Page> INavigation.ModalStack => _navModel.Modals.ToList();
 
		IReadOnlyList<Page> INavigation.NavigationStack => new List<Page>();
 
		Task<Page> INavigation.PopAsync()
		{
			return ((INavigation)this).PopAsync(true);
		}
 
		Task<Page> INavigation.PopAsync(bool animated)
		{
			throw new InvalidOperationException("PopAsync is not supported globally on Android, please use a NavigationPage.");
		}
 
		Task<Page> INavigation.PopModalAsync()
		{
			return ((INavigation)this).PopModalAsync(true);
		}
 
		Task<Page> INavigation.PopModalAsync(bool animated)
		{
			Page modal = _navModel.PopModal();
			((IPageController)modal).SendDisappearing();
			var source = new TaskCompletionSource<Page>();
 
			IVisualElementRenderer modalRenderer = GetRenderer(modal);
			if (modalRenderer != null)
			{
				var modalContainer = modalRenderer.View.Parent as ModalContainer;
				if (animated)
				{
					modalContainer.Animate().TranslationY(_renderer.Height).SetInterpolator(new AccelerateInterpolator(1)).SetDuration(300).SetListener(new GenericAnimatorListener
					{
						OnEnd = a =>
						{
							modalContainer.RemoveFromParent();
							modalContainer.Dispose();
							source.TrySetResult(modal);
							CurrentPageController?.SendAppearing();
							modalContainer = null;
						}
					});
				}
				else
				{
					modalContainer.RemoveFromParent();
					modalContainer.Dispose();
					source.TrySetResult(modal);
					CurrentPageController?.SendAppearing();
				}
			}
 
			UpdateAccessibilityImportance(CurrentPageController as Page, ImportantForAccessibility.Auto, true);
 
			return source.Task;
		}
 
		Task INavigation.PopToRootAsync()
		{
			return ((INavigation)this).PopToRootAsync(true);
		}
 
		Task INavigation.PopToRootAsync(bool animated)
		{
			throw new InvalidOperationException("PopToRootAsync is not supported globally on Android, please use a NavigationPage.");
		}
 
		Task INavigation.PushAsync(Page root)
		{
			return ((INavigation)this).PushAsync(root, true);
		}
 
		Task INavigation.PushAsync(Page root, bool animated)
		{
			throw new InvalidOperationException("PushAsync is not supported globally on Android, please use a NavigationPage.");
		}
 
		Task INavigation.PushModalAsync(Page modal)
		{
			return ((INavigation)this).PushModalAsync(modal, true);
		}
 
		async Task INavigation.PushModalAsync(Page modal, bool animated)
		{
			CurrentPageController?.SendDisappearing();
			UpdateAccessibilityImportance(CurrentPageController as Page, ImportantForAccessibility.NoHideDescendants, false);
 
			_navModel.PushModal(modal);
 
			Task presentModal = PresentModal(modal, animated);
 
			await presentModal;
 
			UpdateAccessibilityImportance(modal, ImportantForAccessibility.Auto, true);
 
			// Verify that the modal is still on the stack
			if (_navModel.CurrentPage == modal)
				((IPageController)modal).SendAppearing();
		}
 
		void INavigation.RemovePage(Page page)
		{
			throw new InvalidOperationException("RemovePage is not supported globally on Android, please use a NavigationPage.");
		}
 
		public static SizeRequest GetNativeSize(
			IVisualElementRenderer visualElementRenderer,
			double widthConstraint,
			double heightConstraint)
		{
			var context = visualElementRenderer.View.Context;
 
			// negative numbers have special meanings to android they don't to us
			widthConstraint = widthConstraint <= -1 ? double.PositiveInfinity : context.ToPixels(widthConstraint);
			heightConstraint = heightConstraint <= -1 ? double.PositiveInfinity : context.ToPixels(heightConstraint);
 
			bool widthConstrained = !double.IsPositiveInfinity(widthConstraint);
			bool heightConstrained = !double.IsPositiveInfinity(heightConstraint);
 
			int widthMeasureSpec = widthConstrained
							? MeasureSpecFactory.MakeMeasureSpec((int)widthConstraint, MeasureSpecMode.AtMost)
							: MeasureSpecFactory.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
 
			int heightMeasureSpec = heightConstrained
							 ? MeasureSpecFactory.MakeMeasureSpec((int)heightConstraint, MeasureSpecMode.AtMost)
							 : MeasureSpecFactory.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
 
			SizeRequest rawResult = visualElementRenderer.GetDesiredSize(widthMeasureSpec, heightMeasureSpec);
			if (rawResult.Minimum == Size.Zero)
				rawResult.Minimum = rawResult.Request;
			var result = new SizeRequest(new Size(context.FromPixels(rawResult.Request.Width), context.FromPixels(rawResult.Request.Height)),
				new Size(context.FromPixels(rawResult.Minimum.Width), context.FromPixels(rawResult.Minimum.Height)));
 
			if ((widthConstrained && result.Request.Width < widthConstraint)
				|| (heightConstrained && result.Request.Height < heightConstraint))
			{
				// Do a final exact measurement in case the native control needs to fill the container
				(visualElementRenderer as IViewRenderer)?.MeasureExactly();
			}
 
			return result;
		}
 
		internal static SizeRequest GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint)
		{
			Performance.Start(out string reference);
 
			IVisualElementRenderer visualElementRenderer = GetRenderer(view);
			SizeRequest returnValue;
 
			if (visualElementRenderer != null && !visualElementRenderer.View.IsAlive())
			{
				returnValue = new SizeRequest(Size.Zero, Size.Zero);
			}
			else if ((visualElementRenderer == null || visualElementRenderer is HandlerToRendererShim) && view is IView iView)
			{
				Application.Current?.FindMauiContext()?.CreateLogger<Platform>()?.LogWarning(
					"Someone called Platform.GetNativeSize instead of going through the Handler.");
 
				returnValue = iView.Handler.GetDesiredSize(widthConstraint, heightConstraint);
			}
			else if (visualElementRenderer != null)
			{
				returnValue = GetNativeSize(visualElementRenderer, widthConstraint, heightConstraint);
			}
			else
			{
				returnValue = new SizeRequest(Size.Zero, Size.Zero);
			}
 
			Performance.Stop(reference);
 
			return returnValue;
		}
 
		public static void ClearRenderer(AView renderedView)
		{
			var element = (renderedView as IVisualElementRenderer)?.Element;
			var view = element as View;
			if (view != null)
			{
				var renderer = GetRenderer(view);
				if (renderer == renderedView)
					element.ClearValue(RendererProperty);
				renderer?.Dispose();
				renderer = null;
			}
			var layout = view as IVisualElementRenderer;
			layout?.Dispose();
			layout = null;
		}
 
		internal static IVisualElementRenderer CreateRenderer(
			VisualElement element,
			Context context,
			AndroidX.Fragment.App.FragmentManager fragmentManager = null,
			global::Android.Views.LayoutInflater layoutInflater = null)
		{
			IVisualElementRenderer renderer = null;
 
			// temporary hack to fix the following issues
			// https://github.com/xamarin/Microsoft.Maui.Controls.Compatibility/issues/13261
			// https://github.com/xamarin/Microsoft.Maui.Controls.Compatibility/issues/12484
			if (element is RadioButton tv && tv.ResolveControlTemplate() != null)
			{
				renderer = new DefaultRenderer(context);
			}
 
			// This code is duplicated across all platforms currently
			// So if any changes are made here please make sure to apply them to other platform.cs files
			if (renderer == null)
			{
				IViewHandler handler = null;
 
				//TODO: Handle this with AppBuilderHost
 
				if (Forms.MauiContext?.Handlers == null)
				{
					throw new InvalidOperationException("Forms.MauiContext.Handlers cannot be null here");
				}
 
				try
				{
					var mauiContext = Forms.MauiContext;
 
					if (fragmentManager != null || layoutInflater != null)
						mauiContext = mauiContext.MakeScoped(layoutInflater, fragmentManager);
 
					handler = mauiContext.Handlers.GetHandler(element.GetType()) as IViewHandler;
					handler.SetMauiContext(mauiContext);
				}
				catch (Exception e)
				{
					Microsoft.Extensions.Logging.LoggerExtensions
						.LogWarning(Forms.MauiContext.CreateLogger<Platform>(), $"{e}");
 
					// TODO define better catch response or define if this is needed?
				}
 
				if (handler == null)
				{
					renderer = Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element, context)
										?? new DefaultRenderer(context);
				}
				// This means the only thing registered is the RendererToHandlerShim
				// Which is only used when you are running a .NET MAUI app
				// This indicates that the user hasn't registered a specific handler for this given type
				else if (handler is RendererToHandlerShim shim)
				{
					renderer = shim.VisualElementRenderer;
 
					if (renderer == null)
					{
						renderer = Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element, context)
										?? new DefaultRenderer(context);
					}
				}
				else if (handler is IVisualElementRenderer ver)
					renderer = ver;
				else if (handler is IPlatformViewHandler vh)
				{
					renderer = new HandlerToRendererShim(vh);
					element.Handler = handler;
					SetRenderer(element, renderer);
				}
			}
 
			renderer.SetElement(element);
 
			if (fragmentManager != null)
			{
				var managesFragments = renderer as IManageFragments;
				managesFragments?.SetFragmentManager(fragmentManager);
			}
 
			return renderer;
		}
 
		public static IVisualElementRenderer CreateRendererWithContext(VisualElement element, Context context)
		{
			// This is an interim method to allow public access to CreateRenderer(element, context), which we 
			// can't make public yet because it will break the previewer
			return CreateRenderer(element, context);
		}
 
		public static IVisualElementRenderer GetRenderer(VisualElement bindable)
		{
			return (IVisualElementRenderer)bindable?.GetValue(RendererProperty);
		}
 
		public static void SetRenderer(VisualElement bindable, IVisualElementRenderer value)
		{
			bindable.SetValue(RendererProperty, value);
		}
 
		internal ViewGroup GetViewGroup()
		{
			return _renderer;
		}
 
		void IPlatformLayout.OnLayout(bool changed, int l, int t, int r, int b)
		{
			if (Page == null)
				return;
 
			if (changed)
			{
				LayoutRootPage(Page, r - l, b - t);
			}
 
			GetRenderer(Page)?.UpdateLayout();
 
			for (var i = 0; i < _renderer.ChildCount; i++)
			{
				AView child = _renderer.GetChildAt(i);
				if (child is ModalContainer)
				{
					child.Measure(MeasureSpecFactory.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(t - b, MeasureSpecMode.Exactly));
					child.Layout(l, t, r, b);
				}
			}
		}
 
		protected override void OnBindingContextChanged()
		{
			SetInheritedBindingContext(Page, BindingContext);
 
			base.OnBindingContextChanged();
		}
 
		internal void SettingNewPage()
		{
			if (Page != null)
			{
				_previousNavModel = _navModel;
				_navModel = new NavigationModel();
			}
		}
 
		internal void SetPage(Page newRoot)
		{
			if (Page == newRoot)
			{
				return;
			}
 
			if (Page != null)
			{
				var navModel = (_previousNavModel ?? _navModel);
				foreach (var rootPage in navModel.Roots)
				{
					if (GetRenderer(rootPage) is ILifeCycleState nr)
						nr.MarkedForDispose = true;
				}
 
				var viewsToRemove = new List<AView>();
				var renderersToDispose = new List<IVisualElementRenderer>();
 
				for (int i = 0; i < _renderer.ChildCount; i++)
					viewsToRemove.Add(_renderer.GetChildAt(i));
 
				foreach (var root in navModel.Roots)
					renderersToDispose.Add(GetRenderer(root));
 
				SetPageInternal(newRoot);
 
				Cleanup(viewsToRemove, renderersToDispose);
			}
			else
			{
				SetPageInternal(newRoot);
			}
		}
 
		void UpdateAccessibilityImportance(Page page, ImportantForAccessibility importantForAccessibility, bool forceFocus)
		{
 
			var pageRenderer = GetRenderer(page);
			if (pageRenderer?.View == null)
				return;
			pageRenderer.View.ImportantForAccessibility = importantForAccessibility;
			if (forceFocus)
				pageRenderer.View.SendAccessibilityEvent(global::Android.Views.Accessibility.EventTypes.ViewFocused);
 
		}
 
		void SetPageInternal(Page newRoot)
		{
			var layout = false;
 
			if (Page != null)
			{
				// if _previousNavModel has been set than _navModel has already been reinitialized
				if (_previousNavModel != null)
				{
					_previousNavModel = null;
					if (_navModel == null)
						_navModel = new NavigationModel();
				}
				else
					_navModel = new NavigationModel();
 
				layout = true;
			}
 
			if (newRoot == null)
			{
				Page = null;
 
				return;
			}
 
			_navModel.Push(newRoot, null);
 
			Page = newRoot;
 
			AddChild(Page, layout);
 
			Application.Current.NavigationProxy.Inner = this;
		}
 
		void Cleanup(List<AView> viewsToRemove, List<IVisualElementRenderer> renderersToDispose)
		{
			// If trigger by dispose, cleanup now, otherwise queue it for later
			if (_disposed)
			{
				DoCleanup();
			}
			else
			{
				new Handler(Looper.MainLooper).Post(DoCleanup);
			}
 
			void DoCleanup()
			{
				for (int i = 0; i < viewsToRemove.Count; i++)
				{
					AView view = viewsToRemove[i];
					_renderer?.RemoveView(view);
				}
 
				for (int i = 0; i < renderersToDispose.Count; i++)
				{
					IVisualElementRenderer rootRenderer = renderersToDispose[i];
					rootRenderer?.Element.ClearValue(RendererProperty);
					rootRenderer?.Dispose();
				}
			}
		}
 
		void AddChild(Page page, bool layout = false)
		{
			if (page == null)
				return;
 
			if (GetRenderer(page) != null)
				return;
 
			IVisualElementRenderer renderView = CreateRenderer(page, _context);
			SetRenderer(page, renderView);
 
			if (layout)
				LayoutRootPage(page, _renderer.Width, _renderer.Height);
 
			_renderer.AddView(renderView.View);
		}
 
		bool HandleBackPressed(object sender, EventArgs e)
		{
			if (NavAnimationInProgress)
				return true;
 
			Page root = _navModel.Roots.LastOrDefault();
			bool handled = root?.SendBackButtonPressed() ?? false;
 
			return handled;
		}
 
		void LayoutRootPage(Page page, int width, int height)
		{
			page.Layout(new Rect(0, 0, _context.FromPixels(width), _context.FromPixels(height)));
		}
 
		Task PresentModal(Page modal, bool animated)
		{
			var modalContainer = new ModalContainer(_context, modal);
 
			_renderer.AddView(modalContainer);
 
			var source = new TaskCompletionSource<bool>();
			NavAnimationInProgress = true;
			if (animated)
			{
				modalContainer.TranslationY = _renderer.Height;
				modalContainer.Animate().TranslationY(0).SetInterpolator(new DecelerateInterpolator(1)).SetDuration(300).SetListener(new GenericAnimatorListener
				{
					OnEnd = a =>
					{
						source.TrySetResult(false);
						modalContainer = null;
					},
					OnCancel = a =>
					{
						source.TrySetResult(true);
						modalContainer = null;
					}
				});
			}
			else
			{
				source.TrySetResult(true);
			}
 
			return source.Task.ContinueWith(task => NavAnimationInProgress = false);
		}
 
		sealed class ModalContainer : ViewGroup
		{
			AView _backgroundView;
			bool _disposed;
			Page _modal;
			IVisualElementRenderer _renderer;
 
			public ModalContainer(Context context, Page modal) : base(context)
			{
				_modal = modal;
 
				_backgroundView = new AView(context);
				UpdateBackgroundColor();
				AddView(_backgroundView);
 
				_renderer = CreateRenderer(modal, context);
				SetRenderer(modal, _renderer);
 
				AddView(_renderer.View);
 
				Id = Platform.GenerateViewId();
 
				_modal.PropertyChanged += OnModalPagePropertyChanged;
			}
 
			protected override void Dispose(bool disposing)
			{
				if (_disposed)
					return;
 
				if (disposing)
				{
					RemoveAllViews();
					if (_renderer != null)
					{
						_renderer.Dispose();
						_renderer = null;
						_modal.ClearValue(RendererProperty);
						_modal.PropertyChanged -= OnModalPagePropertyChanged;
						_modal = null;
					}
 
					if (_backgroundView != null)
					{
						_backgroundView.Dispose();
						_backgroundView = null;
					}
				}
 
				_disposed = true;
				base.Dispose(disposing);
			}
 
			protected override void OnLayout(bool changed, int l, int t, int r, int b)
			{
				if (changed)
				{
					_modal.Layout(new Rect(0, 0, Context.FromPixels(r - l), Context.FromPixels(b - t)));
					_backgroundView.Layout(0, 0, r - l, b - t);
				}
 
				_renderer.UpdateLayout();
			}
 
			void OnModalPagePropertyChanged(object sender, PropertyChangedEventArgs e)
			{
				if (e.PropertyName == Page.BackgroundColorProperty.PropertyName)
					UpdateBackgroundColor();
			}
 
			void UpdateBackgroundColor()
			{
				Color modalBkgndColor = _modal.BackgroundColor;
				if (modalBkgndColor == null)
					_backgroundView.SetWindowBackground();
				else
					_backgroundView.SetBackgroundColor(modalBkgndColor.ToAndroid());
			}
		}
 
		internal static int GenerateViewId() => global::Android.Views.View.GenerateViewId();
 
		#region Statics
 
		public static implicit operator ViewGroup(Platform canvas)
		{
			return canvas._renderer;
		}
 
		#endregion
 
		private sealed class PlatformTypeConverter : TypeConverter
		{
			public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => destinationType == typeof(ViewGroup);
			public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
				=> value switch
				{
					Platform platformValue when destinationType == typeof(ViewGroup) => (ViewGroup)platformValue,
					_ => throw new NotSupportedException(),
				};
 
			public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => false;
			public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) => throw new NotSupportedException();
		}
 
#pragma warning disable CS0618 // Type or member is obsolete
		internal class DefaultRenderer : VisualElementRenderer<View>, ILayoutChanges
#pragma warning restore CS0618 // Type or member is obsolete
		{
			public bool NotReallyHandled { get; private set; }
 
			IOnTouchListener _touchListener;
			bool _disposed;
			bool _hasLayoutOccurred;
 
			readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
 
			public DefaultRenderer(Context context) : base(context)
			{
				ChildrenDrawingOrderEnabled = true;
			}
 
			internal void NotifyFakeHandling()
			{
				NotReallyHandled = true;
			}
 
			public override bool OnTouchEvent(MotionEvent e)
			{
				if (base.OnTouchEvent(e))
					return true;
 
				return _motionEventHelper.HandleMotionEvent(Parent, e);
			}
 
			protected override void OnElementChanged(ElementChangedEventArgs<View> e)
			{
				base.OnElementChanged(e);
 
				_motionEventHelper.UpdateElement(e.NewElement);
			}
 
			public override bool DispatchTouchEvent(MotionEvent e)
			{
				#region Excessive explanation
				// Normally dispatchTouchEvent feeds the touch events to its children one at a time, top child first,
				// (and only to the children in the hit-test area of the event) stopping as soon as one of them has handled
				// the event. 
 
				// But to be consistent across the platforms, we don't want this behavior; if an element is not input transparent
				// we don't want an event to "pass through it" and be handled by an element "behind/under" it. We just want the processing
				// to end after the first non-transparent child, regardless of whether the event has been handled.
 
				// This is only an issue for a couple of controls; the interactive controls (switch, button, slider, etc) already "handle" their touches 
				// and the events don't propagate to other child controls. But for image, label, and box that doesn't happen. We can't have those controls 
				// lie about their events being handled because then the events won't propagate to *parent* controls (e.g., a frame with a label in it would
				// never get a tap gesture from the label). In other words, we *want* parent propagation, but *do not want* sibling propagation. So we need to short-circuit 
				// base.DispatchTouchEvent here, but still return "false".
 
				// Duplicating the logic of ViewGroup.dispatchTouchEvent and modifying it slightly for our purposes is a non-starter; the method is too
				// complex and does a lot of micro-optimization. Instead, we provide a signalling mechanism for the controls which don't already "handle" touch
				// events to tell us that they will be lying about handling their event; they then return "true" to short-circuit base.DispatchTouchEvent.
 
				// The container gets this message and after it gets the "handled" result from dispatchTouchEvent, 
				// it then knows to ignore that result and return false/unhandled. This allows the event to propagate up the tree.
				#endregion
 
				NotReallyHandled = false;
 
				var result = base.DispatchTouchEvent(e);
 
				if (result && NotReallyHandled)
				{
					// If the child control returned true from its touch event handler but signalled that it was a fake "true", then we
					// don't consider the event truly "handled" yet. 
					// Since a child control short-circuited the normal dispatchTouchEvent stuff, this layout never got the chance for
					// IOnTouchListener.OnTouch and the OnTouchEvent override to try handling the touches; we'll do that now
					// Any associated Touch Listeners are called from DispatchTouchEvents if all children of this view return false
					// So here we are simulating both calls that would have typically been called from inside DispatchTouchEvent
					// but were not called due to the fake "true"
					result = _touchListener?.OnTouch(this, e) ?? false;
					return result || OnTouchEvent(e);
				}
 
				return result;
			}
 
			public override void SetOnTouchListener(IOnTouchListener l)
			{
				_touchListener = l;
				base.SetOnTouchListener(l);
			}
 
			protected override void Dispose(bool disposing)
			{
				if (_disposed)
				{
					return;
				}
 
				_disposed = true;
 
				if (disposing)
					SetOnTouchListener(null);
 
				base.Dispose(disposing);
			}
 
			bool ILayoutChanges.HasLayoutOccurred => _hasLayoutOccurred;
 
			protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
			{
				base.OnLayout(changed, left, top, right, bottom);
				_hasLayoutOccurred = true;
			}
		}
 
		internal static string ResolveMsAppDataUri(Uri uri)
		{
			if (uri.Scheme == "ms-appdata")
			{
				string filePath = string.Empty;
 
				if (uri.LocalPath.StartsWith("/local"))
				{
					filePath = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments), uri.LocalPath.Substring(7));
				}
				else if (uri.LocalPath.StartsWith("/temp"))
				{
					filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), uri.LocalPath.Substring(6));
				}
				else
				{
					throw new ArgumentException("Invalid Uri", "Source");
				}
 
				return filePath;
			}
			else
			{
				throw new ArgumentException("uri");
			}
 
		}
	}
}