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.Views; using Android.Widget; using Xamarin.Forms.PlatformConfiguration.AndroidSpecific; namespace Xamarin.Forms.Platform.Android { public class FormsApplicationActivity : Activity, IDeviceInfoProvider, IStartActivityForResult { public delegate bool BackButtonPressedEventHandler(object sender, EventArgs e); readonly ConcurrentDictionary> _activityResultCallbacks = new ConcurrentDictionary>(); Application _application; Platform _canvas; AndroidApplicationLifecycleState _currentState; LinearLayout _layout; int _nextActivityResultCallbackKey; AndroidApplicationLifecycleState _previousState; protected FormsApplicationActivity() { _previousState = AndroidApplicationLifecycleState.Uninitialized; _currentState = AndroidApplicationLifecycleState.Uninitialized; } 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 static event BackButtonPressedEventHandler BackPressed; public override void OnBackPressed() { if (BackPressed != null && BackPressed(this, EventArgs.Empty)) return; base.OnBackPressed(); } public override void OnConfigurationChanged(Configuration newConfig) { base.OnConfigurationChanged(newConfig); EventHandler handler = ConfigurationChanged; if (handler != null) handler(this, new EventArgs()); } // FIXME: THIS SHOULD NOT BE MANDATORY // or // This should be specified in an interface and formalized, perhaps even provide a stock AndroidActivity users // can derive from to avoid having to do any work. public override bool OnOptionsItemSelected(IMenuItem item) { if (item.ItemId == global::Android.Resource.Id.Home) _canvas.SendHomeClicked(); return base.OnOptionsItemSelected(item); } public override bool OnPrepareOptionsMenu(IMenu menu) { _canvas.PrepareMenu(menu); return base.OnPrepareOptionsMenu(menu); } [Obsolete("Please use protected LoadApplication (Application app) instead")] public void SetPage(Page page) { var application = new DefaultApplication { MainPage = page }; LoadApplication(application); } protected void LoadApplication(Application application) { if (application == null) throw new ArgumentNullException("application"); (application as IApplicationController)?.SetAppIndexingProvider(new AndroidAppIndexProvider(this)); _application = application; Xamarin.Forms.Application.Current = application; SetSoftInputMode(); application.PropertyChanged += AppOnPropertyChanged; 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 void OnCreate(Bundle savedInstanceState) { Window.RequestFeature(WindowFeatures.IndeterminateProgress); base.OnCreate(savedInstanceState); _layout = new LinearLayout(BaseContext); SetContentView(_layout); Xamarin.Forms.Application.ClearCurrent(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnCreate; OnStateChanged(); } protected override void OnDestroy() { // may never be called base.OnDestroy(); MessagingCenter.Unsubscribe(this, Page.AlertSignalName); MessagingCenter.Unsubscribe(this, Page.BusySetSignalName); MessagingCenter.Unsubscribe(this, Page.ActionSheetSignalName); if (_canvas != null) ((IDisposable)_canvas).Dispose(); } protected override 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; OnStateChanged(); } protected override void OnRestart() { base.OnRestart(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnRestart; OnStateChanged(); } protected override void OnResume() { // counterpart to OnPause base.OnResume(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnResume; OnStateChanged(); } protected override void OnStart() { base.OnStart(); _previousState = _currentState; _currentState = AndroidApplicationLifecycleState.OnStart; OnStateChanged(); } // Scenarios that stop and restart you 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 recieves a phone call while using your app on his or her phone protected override 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; OnStateChanged(); } void AppOnPropertyChanged(object sender, PropertyChangedEventArgs args) { if (args.PropertyName == "MainPage") InternalSetPage(_application.MainPage); if (args.PropertyName == PlatformConfiguration.AndroidSpecific.Application.WindowSoftInputModeAdjustProperty.PropertyName) SetSoftInputMode(); } void InternalSetPage(Page page) { if (!Forms.IsInitialized) throw new InvalidOperationException("Call Forms.Init (Activity, Bundle) before this"); if (_canvas != null) { _canvas.SetPage(page); return; } var busyCount = 0; MessagingCenter.Subscribe(this, Page.BusySetSignalName, (Page sender, bool enabled) => { busyCount = Math.Max(0, enabled ? busyCount + 1 : busyCount - 1); UpdateProgressBarVisibility(busyCount > 0); }); UpdateProgressBarVisibility(busyCount > 0); MessagingCenter.Subscribe(this, Page.AlertSignalName, (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(); }); MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, (Page sender, ActionSheetArguments arguments) => { var builder = new AlertDialog.Builder(this); builder.SetTitle(arguments.Title); string[] items = arguments.Buttons.ToArray(); builder.SetItems(items, (sender2, args) => { arguments.Result.TrySetResult(items[args.Which]); }); if (arguments.Cancel != null) builder.SetPositiveButton(arguments.Cancel, delegate { arguments.Result.TrySetResult(arguments.Cancel); }); if (arguments.Destruction != null) builder.SetNegativeButton(arguments.Destruction, delegate { 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 += (sender3, e) => { arguments.SetResult(null); }; dialog.Show(); }); _canvas = new Platform(this); if (_application != null) _application.Platform = _canvas; _canvas.SetPage(page); _layout.AddView(_canvas.GetViewGroup()); } async void 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 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); } 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 { } } }