// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// App.xaml.h
// Declaration of the App class.
//
using CalculatorApp.ViewModel.Common;
using CalculatorApp.ViewModel.Common.Automation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using Windows.Storage;
using Windows.UI.Core;
using Windows.UI.StartScreen;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace CalculatorApp
{
namespace ApplicationResourceKeys
{
static public partial class Globals
{
public static readonly string AppMinWindowHeight = "AppMinWindowHeight";
public static readonly string AppMinWindowWidth = "AppMinWindowWidth";
}
}
///
/// Provides application-specific behavior to supplement the default Application class.
///
sealed partial class App
{
///
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
///
public App()
{
InitializeComponent();
m_preLaunched = false;
RegisterDependencyProperties();
// TODO: MSFT 14645325: Set this directly from XAML.
// Currently this is bugged so the property is only respected from code-behind.
HighContrastAdjustment = ApplicationHighContrastAdjustment.None;
Suspending += OnSuspending;
#if DEBUG
DebugSettings.IsBindingTracingEnabled = true;
DebugSettings.BindingFailed += (sender, args) =>
{
if (Debugger.IsAttached)
{
string errorMessage = args.Message;
Debugger.Break();
}
};
#endif
}
///
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
///
/// Details about the launch request and process.
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
if (args.PrelaunchActivated)
{
// If the app got pre-launch activated, then save that state in a flag
m_preLaunched = true;
}
NavCategory.InitializeCategoryManifest(args.User);
OnAppLaunch(args, args.Arguments);
}
protected override void OnActivated(IActivatedEventArgs args)
{
if (args.Kind == ActivationKind.Protocol)
{
// We currently don't pass the uri as an argument,
// and handle any protocol launch as a normal app launch.
OnAppLaunch(args, null);
}
}
internal void RemoveWindow(WindowFrameService frameService)
{
// Shell does not allow killing the main window.
if (m_mainViewId != frameService.GetViewId())
{
_ = HandleViewReleaseAndRemoveWindowFromMap(frameService);
}
}
internal void RemoveSecondaryWindow(WindowFrameService frameService)
{
// Shell does not allow killing the main window.
if (m_mainViewId != frameService.GetViewId())
{
RemoveWindowFromMap(frameService.GetViewId());
}
}
private static Frame CreateFrame()
{
var frame = new Frame();
frame.FlowDirection = LocalizationService.GetInstance().GetFlowDirection();
return frame;
}
private static void SetMinWindowSizeAndThemeAndActivate(Frame rootFrame, Size minWindowSize)
{
// SetPreferredMinSize should always be called before Window.Activate
ApplicationView appView = ApplicationView.GetForCurrentView();
appView.SetPreferredMinSize(minWindowSize);
// Place the frame in the current Window
Window.Current.Content = rootFrame;
CalculatorApp.Utils.ThemeHelper.InitializeAppTheme();
Window.Current.Activate();
}
private void OnAppLaunch(IActivatedEventArgs args, string argument)
{
// Uncomment the following lines to display frame-rate and per-frame CPU usage info.
//#if _DEBUG
// if (IsDebuggerPresent())
// {
// DebugSettings->EnableFrameRateCounter = true;
// }
//#endif
args.SplashScreen.Dismissed += DismissedEventHandler;
var rootFrame = (Window.Current.Content as Frame);
WeakReference weak = new WeakReference(this);
float minWindowWidth = (float)((double)Resources[ApplicationResourceKeys.Globals.AppMinWindowWidth]);
float minWindowHeight = (float)((double)Resources[ApplicationResourceKeys.Globals.AppMinWindowHeight]);
Size minWindowSize = SizeHelper.FromDimensions(minWindowWidth, minWindowHeight);
ApplicationView appView = ApplicationView.GetForCurrentView();
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
// For very first launch, set the size of the calc as size of the default standard mode
if (!localSettings.Values.ContainsKey("VeryFirstLaunch"))
{
localSettings.Values["VeryFirstLaunch"] = false;
appView.SetPreferredMinSize(minWindowSize);
appView.TryResizeView(minWindowSize);
}
else
{
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.Auto;
}
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
if (!Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons")) // PC Family
{
// Disable the system view activation policy during the first launch of the app
// only for PC family devices and not for phone family devices
try
{
ApplicationViewSwitcher.DisableSystemViewActivationPolicy();
}
catch (Exception)
{
// Log that DisableSystemViewActionPolicy didn't work
}
}
// Create a Frame to act as the navigation context
rootFrame = App.CreateFrame();
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
if (!rootFrame.Navigate(typeof(MainPage), argument))
{
// We couldn't navigate to the main page, kill the app so we have a good
// stack to debug
throw new SystemException();
}
SetMinWindowSizeAndThemeAndActivate(rootFrame, minWindowSize);
m_mainViewId = ApplicationView.GetForCurrentView().Id;
AddWindowToMap(WindowFrameService.CreateNewWindowFrameService(rootFrame, false, weak));
}
else
{
// For first launch, LaunchStart is logged in constructor, this is for subsequent launches.
// !Phone check is required because even in continuum mode user interaction mode is Mouse not Touch
if ((UIViewSettings.GetForCurrentView().UserInteractionMode == UserInteractionMode.Mouse)
&& (!Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons")))
{
// If the pre-launch hasn't happened then allow for the new window/view creation
if (!m_preLaunched)
{
var newCoreAppView = CoreApplication.CreateNewView();
_ = newCoreAppView.Dispatcher.RunAsync(
CoreDispatcherPriority.Normal, async () =>
{
var that = weak.Target as App;
if (that != null)
{
var newRootFrame = App.CreateFrame();
SetMinWindowSizeAndThemeAndActivate(newRootFrame, minWindowSize);
if (!newRootFrame.Navigate(typeof(MainPage), argument))
{
// We couldn't navigate to the main page, kill the app so we have a good
// stack to debug
throw new SystemException();
}
var frameService = WindowFrameService.CreateNewWindowFrameService(newRootFrame, true, weak);
that.AddWindowToMap(frameService);
var dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
// CSHARP_MIGRATION_ANNOTATION:
// class SafeFrameWindowCreation is being interpreted into a IDisposable class
// in order to enhance its RAII capability that was written in C++/CX
using (var safeFrameServiceCreation = new SafeFrameWindowCreation(frameService, that))
{
int newWindowId = ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread());
ActivationViewSwitcher activationViewSwitcher = null;
var activateEventArgs = (args as IViewSwitcherProvider);
if (activateEventArgs != null)
{
activationViewSwitcher = activateEventArgs.ViewSwitcher;
}
if (activationViewSwitcher != null)
{
_ = activationViewSwitcher.ShowAsStandaloneAsync(newWindowId, ViewSizePreference.Default);
safeFrameServiceCreation.SetOperationSuccess(true);
}
else
{
var activatedEventArgs = (args as IApplicationViewActivatedEventArgs);
if ((activatedEventArgs != null) && (activatedEventArgs.CurrentlyShownApplicationViewId != 0))
{
// CSHARP_MIGRATION_ANNOTATION:
// here we don't use ContinueWith() to interpret origin code because we would like to
// pursue the design of class SafeFrameWindowCreate whichi was using RAII to ensure
// some states get handled properly when its instance is being destructed.
//
// To achieve that, SafeFrameWindowCreate has been reinterpreted using IDisposable
// pattern, which forces we use below way to keep async works being controlled within
// a same code block.
var viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(
frameService.GetViewId(),
ViewSizePreference.Default,
activatedEventArgs.CurrentlyShownApplicationViewId,
ViewSizePreference.Default);
// SafeFrameServiceCreation is used to automatically remove the frame
// from the list of frames if something goes bad.
safeFrameServiceCreation.SetOperationSuccess(viewShown);
}
}
}
}
});
}
else
{
ActivationViewSwitcher activationViewSwitcher = null;
var activateEventArgs = (args as IViewSwitcherProvider);
if (activateEventArgs != null)
{
activationViewSwitcher = activateEventArgs.ViewSwitcher;
}
if (activationViewSwitcher != null)
{
_ = activationViewSwitcher.ShowAsStandaloneAsync(
ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()), ViewSizePreference.Default);
}
else
{
TraceLogger.GetInstance().LogError(ViewMode.None, "App.OnAppLaunch", "Null_ActivationViewSwitcher");
}
}
// Set the preLaunched flag to false
m_preLaunched = false;
}
else // for touch devices
{
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
if (!rootFrame.Navigate(typeof(MainPage), argument))
{
// We couldn't navigate to the main page,
// kill the app so we have a good stack to debug
throw new SystemException();
}
}
if (ApplicationView.GetForCurrentView().ViewMode != ApplicationViewMode.CompactOverlay)
{
if (!Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
{
// for tablet mode: since system view activation policy is disabled so do ShowAsStandaloneAsync if activationViewSwitcher exists in
// activationArgs
ActivationViewSwitcher activationViewSwitcher = null;
var activateEventArgs = (args as IViewSwitcherProvider);
if (activateEventArgs != null)
{
activationViewSwitcher = activateEventArgs.ViewSwitcher;
}
if (activationViewSwitcher != null)
{
var viewId = (args as IApplicationViewActivatedEventArgs).CurrentlyShownApplicationViewId;
if (viewId != 0)
{
_ = activationViewSwitcher.ShowAsStandaloneAsync(viewId);
}
}
}
// Ensure the current window is active
Window.Current.Activate();
}
}
}
}
private void DismissedEventHandler(SplashScreen sender, object e)
{
_ = SetupJumpList();
}
private void RegisterDependencyProperties()
{
NarratorNotifier.RegisterDependencyProperties();
}
private void OnSuspending(object sender, SuspendingEventArgs args)
{
TraceLogger.GetInstance().LogButtonUsage();
}
private sealed class SafeFrameWindowCreation : IDisposable
{
public SafeFrameWindowCreation(WindowFrameService frameService, App parent)
{
m_frameService = frameService;
m_frameOpenedInWindow = false;
m_parent = parent;
}
public void SetOperationSuccess(bool success)
{
m_frameOpenedInWindow = success;
}
public void Dispose()
{
if (!m_frameOpenedInWindow)
{
// Close the window as the navigation to the window didn't succeed
// and this is not visible to the user.
m_parent.RemoveWindowFromMap(m_frameService.GetViewId());
}
GC.SuppressFinalize(this);
}
~SafeFrameWindowCreation()
{
Dispose();
}
private WindowFrameService m_frameService;
private bool m_frameOpenedInWindow;
private App m_parent;
};
private async Task SetupJumpList()
{
try
{
var calculatorOptions = NavCategoryGroup.CreateCalculatorCategory();
var jumpList = await JumpList.LoadCurrentAsync();
jumpList.SystemGroupKind = JumpListSystemGroupKind.None;
jumpList.Items.Clear();
foreach (NavCategory option in calculatorOptions.Categories)
{
if (!option.IsEnabled)
{
continue;
}
ViewMode mode = option.Mode;
var item = JumpListItem.CreateWithArguments(((int)mode).ToString(), "ms-resource:///Resources/" + NavCategory.GetNameResourceKey(mode));
item.Description = "ms-resource:///Resources/" + NavCategory.GetNameResourceKey(mode);
item.Logo = new Uri("ms-appx:///Assets/" + mode.ToString() + ".png");
jumpList.Items.Add(item);
}
await jumpList.SaveAsync();
}
catch
{
}
}
private async Task HandleViewReleaseAndRemoveWindowFromMap(WindowFrameService frameService)
{
WeakReference weak = new WeakReference(this);
// Unregister the event handler of the Main Page
var frame = (Window.Current.Content as Frame);
var mainPage = (frame.Content as MainPage);
mainPage.UnregisterEventHandlers();
await frameService.HandleViewRelease();
await Task.Run(() =>
{
var that = weak.Target as App;
that.RemoveWindowFromMap(frameService.GetViewId());
}).ConfigureAwait(false /* task_continuation_context::use_arbitrary() */);
}
private void AddWindowToMap(WindowFrameService frameService)
{
m_windowsMapLock.EnterWriteLock();
try
{
m_secondaryWindows[frameService.GetViewId()] = frameService;
TraceLogger.GetInstance().UpdateWindowCount(Convert.ToUInt64(m_secondaryWindows.Count));
}
finally
{
m_windowsMapLock.ExitWriteLock();
}
}
private WindowFrameService GetWindowFromMap(int viewId)
{
m_windowsMapLock.EnterReadLock();
try
{
if (m_secondaryWindows.TryGetValue(viewId, out var windowMapEntry))
{
return windowMapEntry;
}
else
{
return null;
}
}
finally
{
m_windowsMapLock.ExitReadLock();
}
}
private void RemoveWindowFromMap(int viewId)
{
m_windowsMapLock.EnterWriteLock();
try
{
bool removed = m_secondaryWindows.Remove(viewId);
Debug.Assert(removed != false, "Window does not exist in the list");
}
finally
{
m_windowsMapLock.ExitWriteLock();
}
}
private readonly ReaderWriterLockSlim m_windowsMapLock = new ReaderWriterLockSlim();
private Dictionary m_secondaryWindows = new Dictionary();
private int m_mainViewId;
private bool m_preLaunched;
}
}