#region using System; using System.Collections.Concurrent; using System.ComponentModel; using System.Linq; using Android.App; using Android.Content; using Android.Content.Res; using Android.OS; using Android.Runtime; using Android.Support.V4.Content; using Android.Support.V7.App; using Android.Util; using Android.Views; using Android.Widget; using Xamarin.Forms.Platform.Android.AppCompat; using Xamarin.Forms.PlatformConfiguration.AndroidSpecific; using Xamarin.Forms.PlatformConfiguration.AndroidSpecific.AppCompat; using AToolbar = Android.Support.V7.Widget.Toolbar; using AColor = Android.Graphics.Color; using AlertDialog = Android.Support.V7.App.AlertDialog; using ARelativeLayout = Android.Widget.RelativeLayout; using Xamarin.Forms.Internals; using System.Threading.Tasks; #endregion namespace Xamarin.Forms.Platform.Android { public class FormsAppCompatActivity : AppCompatActivity, IDeviceInfoProvider, IStartActivityForResult { public delegate bool BackButtonPressedEventHandler(object sender, EventArgs e); readonly ConcurrentDictionary> _activityResultCallbacks = new ConcurrentDictionary>(); Application _application; int _busyCount; AndroidApplicationLifecycleState _currentState; ARelativeLayout _layout; int _nextActivityResultCallbackKey; AppCompat.Platform _platform; AndroidApplicationLifecycleState _previousState; bool _renderersAdded; // Override this if you want to handle the default Android behavior of restoring fragments on an application restart protected virtual bool AllowFragmentRestore => false; protected FormsAppCompatActivity() { _previousState = AndroidApplicationLifecycleState.Uninitialized; _currentState = AndroidApplicationLifecycleState.Uninitialized; } IApplicationController Controller => _application; public event EventHandler ConfigurationChanged; int IStartActivityForResult.RegisterActivityResultCallback(Action callback) { int requestCode = _nextActivityResultCallbackKey; while (!_activityResultCallbacks.TryAdd(requestCode, callback)) { _nextActivityResultCallbackKey += 1; requestCode = _nextActivityResultCallbackKey; } _nextActivityResultCallbackKey += 1; return requestCode; } void IStartActivityForResult.UnregisterActivityResultCallback(int requestCode) { Action callback; _activityResultCallbacks.TryRemove(requestCode, out callback); } public override void OnBackPressed() { if (BackPressed != null && BackPressed(this, EventArgs.Empty)) return; base.OnBackPressed(); } public override void OnConfigurationChanged(Configuration newConfig) { base.OnConfigurationChanged(newConfig); ConfigurationChanged?.Invoke(this, new EventArgs()); } public override bool OnOptionsItemSelected(IMenuItem item) { if (item.ItemId == global::Android.Resource.Id.Home) BackPressed?.Invoke(this, EventArgs.Empty); return base.OnOptionsItemSelected(item); } public void SetStatusBarColor(AColor color) { if (Forms.IsLollipopOrNewer) { Window.SetStatusBarColor(color); } } protected void LoadApplication(Application application) { if (!_renderersAdded) { RegisterHandlerForDefaultRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer), typeof(NavigationRenderer)); RegisterHandlerForDefaultRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer), typeof(TabbedRenderer)); RegisterHandlerForDefaultRenderer(typeof(MasterDetailPage), typeof(MasterDetailPageRenderer), typeof(MasterDetailRenderer)); RegisterHandlerForDefaultRenderer(typeof(Switch), typeof(AppCompat.SwitchRenderer), typeof(SwitchRenderer)); RegisterHandlerForDefaultRenderer(typeof(Picker), typeof(AppCompat.PickerRenderer), typeof(PickerRenderer)); RegisterHandlerForDefaultRenderer(typeof(CarouselPage), typeof(AppCompat.CarouselPageRenderer), typeof(CarouselPageRenderer)); if (Forms.Flags.Contains(Flags.FastRenderersExperimental)) { RegisterHandlerForDefaultRenderer(typeof(Button), typeof(FastRenderers.ButtonRenderer), typeof(ButtonRenderer)); RegisterHandlerForDefaultRenderer(typeof(Label), typeof(FastRenderers.LabelRenderer), typeof(LabelRenderer)); RegisterHandlerForDefaultRenderer(typeof(Image), typeof(FastRenderers.ImageRenderer), typeof(ImageRenderer)); RegisterHandlerForDefaultRenderer(typeof(Frame), typeof(FastRenderers.FrameRenderer), typeof(FrameRenderer)); } else { RegisterHandlerForDefaultRenderer(typeof(Button), typeof(AppCompat.ButtonRenderer), typeof(ButtonRenderer)); RegisterHandlerForDefaultRenderer(typeof(Frame), typeof(AppCompat.FrameRenderer), typeof(FrameRenderer)); } _renderersAdded = true; } if (application == null) throw new ArgumentNullException("application"); _application = application; (application as IApplicationController)?.SetAppIndexingProvider(new AndroidAppIndexProvider(this)); Xamarin.Forms.Application.SetCurrentApplication(application); SetSoftInputMode(); CheckForAppLink(Intent); application.PropertyChanged += AppOnPropertyChanged; var iver = Platform.GetRenderer(application.MainPage); if (iver != null) { iver.Dispose(); application.MainPage.ClearValue(Platform.RendererProperty); } SetMainPage(); } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); Action callback; if (_activityResultCallbacks.TryGetValue(requestCode, out callback)) callback(resultCode, data); } protected override async void OnCreate(Bundle savedInstanceState) { if (!AllowFragmentRestore) { // Remove the automatically persisted fragment structure; we don't need them // because we're rebuilding everything from scratch. This saves a bit of memory // and prevents loading errors from child fragment managers savedInstanceState?.Remove("android:support:fragments"); } base.OnCreate(savedInstanceState); AToolbar bar; if (ToolbarResource != 0) { bar = LayoutInflater.Inflate(ToolbarResource, null).JavaCast(); if (bar == null) throw new InvalidOperationException("ToolbarResource must be set to a Android.Support.V7.Widget.Toolbar"); } else bar = new AToolbar(this); SetSupportActionBar(bar); _layout = new ARelativeLayout(BaseContext); SetContentView(_layout); Xamarin.Forms.Application.ClearCurrent(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnCreate; await OnStateChanged(); if (Forms.IsLollipopOrNewer) { // Allow for the status bar color to be changed Window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds); } } protected override void OnDestroy() { MessagingCenter.Unsubscribe(this, Page.AlertSignalName); MessagingCenter.Unsubscribe(this, Page.BusySetSignalName); MessagingCenter.Unsubscribe(this, Page.ActionSheetSignalName); _platform?.Dispose(); // call at the end to avoid race conditions with Platform dispose base.OnDestroy(); } protected override void OnNewIntent(Intent intent) { base.OnNewIntent(intent); CheckForAppLink(intent); } protected override async void OnPause() { _layout.HideKeyboard(true); // Stop animations or other ongoing actions that could consume CPU // Commit unsaved changes, build only if users expect such changes to be permanently saved when thy leave such as a draft email // Release system resources, such as broadcast receivers, handles to sensors (like GPS), or any resources that may affect battery life when your activity is paused. // Avoid writing to permanent storage and CPU intensive tasks base.OnPause(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnPause; await OnStateChanged(); } protected override async void OnRestart() { base.OnRestart(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnRestart; await OnStateChanged(); } protected override async void OnResume() { // counterpart to OnPause base.OnResume(); if (_application != null && _application.OnThisPlatform().GetShouldPreserveKeyboardOnResume()) { if (CurrentFocus != null && (CurrentFocus is EditText || CurrentFocus is TextView || CurrentFocus is SearchView)) { CurrentFocus.ShowKeyboard(); } } _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnResume; await OnStateChanged(); } protected override async void OnStart() { base.OnStart(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnStart; await OnStateChanged(); } // Scenarios that stop and restart your app // -- Switches from your app to another app, activity restarts when clicking on the app again. // -- Action in your app that starts a new Activity, the current activity is stopped and the second is created, pressing back restarts the activity // -- The user receives a phone call while using your app on his or her phone protected override async void OnStop() { // writing to storage happens here! // full UI obstruction // users focus in another activity // perform heavy load shutdown operations // clean up resources // clean up everything that may leak memory base.OnStop(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnStop; await OnStateChanged(); } void AppOnPropertyChanged(object sender, PropertyChangedEventArgs args) { if (args.PropertyName == "MainPage") InternalSetPage(_application.MainPage); if (args.PropertyName == PlatformConfiguration.AndroidSpecific.Application.WindowSoftInputModeAdjustProperty.PropertyName) SetSoftInputMode(); } void CheckForAppLink(Intent intent) { string action = intent.Action; string strLink = intent.DataString; if (Intent.ActionView != action || string.IsNullOrWhiteSpace(strLink)) return; var link = new Uri(strLink); _application?.SendOnAppLinkRequestReceived(link); } void InternalSetPage(Page page) { if (!Forms.IsInitialized) throw new InvalidOperationException("Call Forms.Init (Activity, Bundle) before this"); if (_platform != null) { _platform.SetPage(page); return; } _busyCount = 0; MessagingCenter.Subscribe(this, Page.BusySetSignalName, OnPageBusy); MessagingCenter.Subscribe(this, Page.AlertSignalName, OnAlertRequested); MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, OnActionSheetRequested); _platform = new AppCompat.Platform(this); if (_application != null) _application.Platform = _platform; _platform.SetPage(page); _layout.AddView(_platform); _layout.BringToFront(); } void OnActionSheetRequested(Page sender, ActionSheetArguments arguments) { var builder = new AlertDialog.Builder(this); builder.SetTitle(arguments.Title); string[] items = arguments.Buttons.ToArray(); builder.SetItems(items, (o, args) => arguments.Result.TrySetResult(items[args.Which])); if (arguments.Cancel != null) builder.SetPositiveButton(arguments.Cancel, (o, args) => arguments.Result.TrySetResult(arguments.Cancel)); if (arguments.Destruction != null) builder.SetNegativeButton(arguments.Destruction, (o, args) => arguments.Result.TrySetResult(arguments.Destruction)); AlertDialog dialog = builder.Create(); builder.Dispose(); //to match current functionality of renderer we set cancelable on outside //and return null dialog.SetCanceledOnTouchOutside(true); dialog.CancelEvent += (o, e) => arguments.SetResult(null); dialog.Show(); } void OnAlertRequested(Page sender, AlertArguments arguments) { AlertDialog alert = new AlertDialog.Builder(this).Create(); alert.SetTitle(arguments.Title); alert.SetMessage(arguments.Message); if (arguments.Accept != null) alert.SetButton((int)DialogButtonType.Positive, arguments.Accept, (o, args) => arguments.SetResult(true)); alert.SetButton((int)DialogButtonType.Negative, arguments.Cancel, (o, args) => arguments.SetResult(false)); alert.CancelEvent += (o, args) => { arguments.SetResult(false); }; alert.Show(); } void OnPageBusy(Page sender, bool enabled) { _busyCount = Math.Max(0, enabled ? _busyCount + 1 : _busyCount - 1); UpdateProgressBarVisibility(_busyCount > 0); } async Task OnStateChanged() { if (_application == null) return; if (_previousState == AndroidApplicationLifecycleState.OnCreate && _currentState == AndroidApplicationLifecycleState.OnStart) _application.SendStart(); else if (_previousState == AndroidApplicationLifecycleState.OnStop && _currentState == AndroidApplicationLifecycleState.OnRestart) _application.SendResume(); else if (_previousState == AndroidApplicationLifecycleState.OnPause && _currentState == AndroidApplicationLifecycleState.OnStop) await _application.SendSleepAsync(); } void RegisterHandlerForDefaultRenderer(Type target, Type handler, Type filter) { Type current = Registrar.Registered.GetHandlerType(target); if (current != filter) return; Registrar.Registered.Register(target, handler); } void SetMainPage() { InternalSetPage(_application.MainPage); } void SetSoftInputMode() { SoftInput adjust = SoftInput.AdjustPan; if (Xamarin.Forms.Application.Current != null) { var elementValue = Xamarin.Forms.Application.Current.OnThisPlatform().GetWindowSoftInputModeAdjust(); switch (elementValue) { default: case WindowSoftInputModeAdjust.Pan: adjust = SoftInput.AdjustPan; break; case WindowSoftInputModeAdjust.Resize: adjust = SoftInput.AdjustResize; break; } } Window.SetSoftInputMode(adjust); } public override void OnWindowAttributesChanged(WindowManagerLayoutParams @params) { base.OnWindowAttributesChanged(@params); if (Xamarin.Forms.Application.Current == null || Xamarin.Forms.Application.Current.MainPage == null) return; // sync between Window flag and Forms property if (@params.Flags.HasFlag(WindowManagerFlags.Fullscreen)) { if (Forms.TitleBarVisibility != AndroidTitleBarVisibility.Never) Forms.TitleBarVisibility = AndroidTitleBarVisibility.Never; } else { if (Forms.TitleBarVisibility != AndroidTitleBarVisibility.Default) Forms.TitleBarVisibility = AndroidTitleBarVisibility.Default; } } void UpdateProgressBarVisibility(bool isBusy) { if (!Forms.SupportsProgress) return; #pragma warning disable 612, 618 SetProgressBarIndeterminate(true); SetProgressBarIndeterminateVisibility(isBusy); #pragma warning restore 612, 618 } internal class DefaultApplication : Application { } #region Statics public static event BackButtonPressedEventHandler BackPressed; public static int TabLayoutResource { get; set; } public static int ToolbarResource { get; set; } #endregion } }