Scheduling Background Agents in Windows Phone 7.1 Mango


Scheduled Tasks & Background Agents allows apps to run in background in Windows Phone while the app is not running in foreground. There are two types of Scheduled Tasks referenced called Periodic Tasks & ResourceIntensive Tasks which helps to implement background agent scaling.

  • Periodic Tasks: Periodic agents run for a small amount of time on a regular recurring interval. Typical scenarios for this type of task include uploading the device’s location and performing small amounts of data synchronization.
  • Resource-Intensive Tasks: This tasks works for long period of time when the phone meets a set of requirements relating to processor activity, power source, and network connection. A typical scenario for this type of task is synchronizing large amounts of data to the phone while it is not being actively used by the user.
  • The Background  Agent LifeCycle for Windows Phone:  The app can have at least of one type of Scheduled tasks either Periodic Tasks or Scheduled Tasks or even both. So the schedule on which the agent runs depends on which type of task it is registered as.

There are some constraints features on both of Periodic Tasks & Resource-Intensive Tasks . For details Click here.

  • Implement Background Tasks for Windows Phone 7.1 Mango : In order to implement it you need to a Windows Phone application project in VS 2010 SP1 from Silverlight for Windows Phone template.

  • Add a new Schedule Task agent Project to add resources for Schedule agent tasks in Windows Phone app.

  • In order to access the Schedule Task apps in Foreground app , we need to add reference of ScheduleTaskAgent1 Project in ScheduledTask project by clicking on add reference-> Project dialog.

  • Now, add the following code in ScheduleAgent.cs file in the ScheduleTaskAgent1 project.

#define

DEBUG_AGENT

using  System.Windows;

using Microsoft.Phone.Scheduler;

using  Microsoft.Phone.Scheduler;

using  Microsoft.Phone.Shell;

using  System;

namespace ScheduledTaskAgent1

{

public class ScheduledAgent : ScheduledTaskAgent

{

private static volatile bool _classInitialized;

///<remarks>

/// ScheduledAgent constructor, initializes the UnhandledException handler

///</remarks>

public ScheduledAgent()

{

if (!_classInitialized)

{

_classInitialized = true;

// Subscribe to the managed exception handler

Deployment.Current.Dispatcher.BeginInvoke(delegate

{

Application.Current.UnhandledException += ScheduledAgent_UnhandledException;

});

}

}

/// Code to execute on Unhandled Exceptions

private void ScheduledAgent_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)

{

if (System.Diagnostics.Debugger.IsAttached)

{

// An unhandled exception has occurred; break into the debugger

System.Diagnostics.

Debugger.Break();

}

}

///<summary>

/// Agent that runs a scheduled task

///</summary>

///<param name=”task”>

/// The invoked task

///</param>

///<remarks>

/// This method is called when a periodic or resource intensive task is invoked

///</remarks>

protected overridevoid OnInvoke(ScheduledTask task)

{

//TODO: Add code to perform your task in background

string toastMessage = “Hello WP7”;

if (task isPeriodicTask)

{

toastMessage = “Periodic task running.”;

}

else

{

toastMessage = “Resource-intensive task running.”;

}

// The toast will not be shown if the foreground application is running.

ShellToast toast = newShellToast();

toast.Title = “Background Agent Sample”;

toast.Content = toastMessage;

toast.Show();

#if

DEBUG_AGENT

ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(60));

#endif

// Call NotifyComplete to let the system know is done working .

NotifyComplete();

}

}

}

  • So, Next to build the Forgeound App Project , so move to MainPage.xaml in ScheduleTask Project of Windows Phone.

MainPage.Xaml:  Modify the code inside the Grid name called ContentPanel.

<Grid x:Name=”ContentPanel” Grid.Row=”1″ Margin=”12,0,12,0″>

<StackPanel>

<StackPanel Orientation=”Vertical” Name=”PeriodicStackPanel” Margin=”0,0,0,40″>

<TextBlock Text=”Periodic Agent” Style=”{StaticResource PhoneTextTitle2Style}” />

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”name:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding Name}” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”is enabled” VerticalAlignment=”Center” Style=”{StaticResource PhoneTextAccentStyle}” />

<CheckBox Name=”PeriodicCheckBox” IsChecked=”{Binding IsEnabled}” Checked=”PeriodicCheckBox_Checked” Unchecked=”PeriodicCheckBox_Unchecked” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”is scheduled:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding IsScheduled}” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”last scheduled time:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding LastScheduledTime}” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”expiration time:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding ExpirationTime}” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”last exit reason:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding LastExitReason}” />

</StackPanel>

</StackPanel>

<StackPanel Orientation=”Vertical” Name=”ResourceIntensiveStackPanel” Margin=”0,0,0,40″>

<TextBlock Text=”Resource-intensive Agent” Style=”{StaticResource PhoneTextTitle2Style}” />

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”name:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding Name}” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”is enabled” VerticalAlignment=”Center” Style=”{StaticResource PhoneTextAccentStyle}” />

<CheckBox Name=”ResourceIntensiveCheckBox” IsChecked=”{Binding IsEnabled}” Checked=”ResourceIntensiveCheckBox_Checked” Unchecked=”ResourceIntensiveCheckBox_Unchecked” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”is scheduled:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding IsScheduled}” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”last scheduled time:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding LastScheduledTime}” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”expiration time:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding ExpirationTime}” />

</StackPanel>

<StackPanel Orientation=”Horizontal”>

<TextBlock Text=”last exit reason:” Style=”{StaticResource PhoneTextAccentStyle}” />

<TextBlock Text=”{Binding LastExitReason}” />

</StackPanel>

</StackPanel>

</StackPanel>

</Grid>

</Grid>

  • Add the following code in MainPage.xaml.cs:

MainPage.xaml.cs:

#define DEBUG_AGENT

using System;

using  System.Collections.Generic;

using System.Linq;

using  System.Net;

using  System.Windows;

using  System.Windows.Controls;

using  System.Windows.Documents;

using  System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using  System.Windows.Shapes;

using  Microsoft.Phone.Controls;

using  Microsoft.Phone.Scheduler;

namespace ScheduledTask

{

public partial class MainPage : PhoneApplicationPage

{

PeriodicTask periodicTask;

ResourceIntensiveTask resourceIntensiveTask;

string periodicTaskName = “PeriodicAgent”;

string resourceIntensiveTaskName = “ResourceIntensiveAgent”;

public bool agentsAreEnabled = true;

// Constructor

public MainPage()

{

InitializeComponent();

}

private void StartPeriodicAgent()

{

agentsAreEnabled = true;

periodicTask = ScheduledActionService.Find(periodicTaskName) asPeriodicTask;

if (periodicTask != null)

{

RemoveAgent(periodicTaskName);

}

periodicTask = newPeriodicTask(periodicTaskName);

periodicTask = newPeriodicTask(periodicTaskName);

periodicTask.Description = “This demonstrates a periodic task.”;

// Place the call to add in a try block in case the user has disabled agents.

try

{

ScheduledActionService.Add(periodicTask);

PeriodicStackPanel.DataContext = periodicTask;

// If debugging is enabled , use LaunchForTest to launch the agent in one minutes

#if

(DEBUG_AGENT)

ScheduledActionService.LaunchForTest(periodicTaskName, TimeSpan.FromSeconds(60));

#endif

}

catch (InvalidOperationException exception)

{

if (exception.Message.Contains(“BNS Error: The action is disabled”))

{

MessageBox.Show(“Bakcground agents for this application have been disabled by the user.”);

agentsAreEnabled = false;

PeriodicCheckBox.IsChecked = false;

}

if (exception.Message.Contains(“BNS Error: The maximum number of ScheduledActions of this type have already been added.”))

{

// No user action required.

}

PeriodicCheckBox.IsChecked = false;

}

catch (SchedulerServiceException)

{

PeriodicCheckBox.IsChecked =false;

}

}

private void StartResourceIntensiveAgent()

{

agentsAreEnabled = true;

resourceIntensiveTask =  ScheduledActionService.Find(resourceIntensiveTaskName) asResourceIntensiveTask;

if (resourceIntensiveTask != null)

{

RemoveAgent(resourceIntensiveTaskName);

}

resourceIntensiveTask = newResourceIntensiveTask(resourceIntensiveTaskName);

try

{

ScheduledActionService.Add(resourceIntensiveTask);

ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask;

#if

(DEBUG_AGENT)

ScheduledActionService.LaunchForTest(resourceIntensiveTaskName, TimeSpan.FromSeconds(60));

#endif

}

catch (InvalidOperationException exception)

{

if (exception.Message.Contains(“BNS Error: The action is disabled”))

{

MessageBox.Show(“Background agents for this application have been disabled by the user.”);

agentsAreEnabled = false;

}

ResourceIntensiveCheckBox.IsChecked = false;

}

catch (SchedulerServiceException)

{

ResourceIntensiveCheckBox.IsChecked = false;

}

}

bool ignoreCheckBoxEvents = false;

private void PeriodicCheckBox_Checked(object sender, RoutedEventArgs e)

{

if (ignoreCheckBoxEvents)

return;

StartPeriodicAgent();

}

private void PeriodicCheckBox_Unchecked(object sender, RoutedEventArgs e)

{

if (ignoreCheckBoxEvents)

     return;

RemoveAgent(periodicTaskName);

}

private void ResourceIntensiveCheckBox_Checked(object sender, RoutedEventArgs e)

{

if (ignoreCheckBoxEvents)

return;

RemoveAgent(resourceIntensiveTaskName);

}

private void ResourceIntensiveCheckBox_Unchecked(object sender, RoutedEventArgs e)

{

if (ignoreCheckBoxEvents)

return;

RemoveAgent(resourceIntensiveTaskName);

}

private void RemoveAgent(string name)

{

    try

{

ScheduledActionService.Remove(name);

}

catch (Exception)

{

}

}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

{

ignoreCheckBoxEvents = true;

periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;

     if (periodicTask != null)

{

PeriodicStackPanel.DataContext = periodicTask;

}

resourceIntensiveTask = ScheduledActionService.Find(resourceIntensiveTaskName) as ResourceIntensiveTask;

   if (resourceIntensiveTask != null)

{

ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask;

}

ignoreCheckBoxEvents = false;

}

  • Now the Debug the app in Windows Phone 7.1 Mango emulator & check the background app agent running as Schedule Tasks.

  •    The Background apps schedule time with Periodic Agent on Windows Phone.

  • Enable Background schedule to receive toast notification as foreground process from background activity apps. Check to see the Toast notification tile on Mango screen as foreground process.

Create, Update & Delete(CUD) Operations of SQL Server / SQL Azure Data through OData WCF Services from Windows Phone 7.1 Mango


In previous posts, I discussed about the consumption of SQL Server/ SQL Azure data in Windows Phone 7.1 Mango device through the use of OData WCF services. OData Services  support not only HTTP GET operations but also supports for HTTP POST, PUT & DELETE by which we can perform CRUD (Create, Read, Update, Delete) operations from SQL Server/SQL Azure/ Oracle / SharePoint 2010 / SSRS 2008 / SAP Netweaver BI data etc.

  • In this post, Let’s discuss how to add new data by HTTP POST through XML ATOMPUB format from Windows Phone 7.1 client to the SQL Server database.
  • Created a new .XAML page called ‘Add.xaml’ which will capture data from Phone UI & save it to the SQL Server / SQL Azure database.

  • Lets check the Source Code for the Create operation:

<phone:PhoneApplicationPage

x:Class=”WP7PanoramaOData.Add”

xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation&#8221;

xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml&#8221;

xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”

xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”

xmlns:d=”http://schemas.microsoft.com/expression/blend/2008&#8243;

xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006&#8243;

FontFamily=”{StaticResource PhoneFontFamilyNormal}”

FontSize=”{StaticResource PhoneFontSizeNormal}”

Foreground=”{StaticResource PhoneForegroundBrush}”

SupportedOrientations=”PortraitOrLandscape” Orientation=”Portrait”

mc:Ignorable=”d” d:DesignHeight=”768″ d:DesignWidth=”480″

shell:SystemTray.IsVisible=”True”>

<!–LayoutRoot is the root grid where all page content is placed–>

<Grid x:Name=”LayoutRoot”>

<Grid.Background>

<ImageBrush ImageSource=”images/Back.png”/>

</Grid.Background>

<Grid.RowDefinitions>

<RowDefinition Height=”Auto”/>

<RowDefinition Height=”*”/>

</Grid.RowDefinitions>

<!–TitlePanel contains the name of the application and page title–>

<StackPanel x:Name=”TitlePanel” Grid.Row=”0″ Margin=”12,17,0,28″>

<TextBlock x:Name=”ApplicationTitle” Text=”MY APPLICATION” Style=”{StaticResource PhoneTextNormalStyle}”/>

<TextBlock x:Name=”PageTitle” Text=”Customer Data” Margin=”9,-7,0,0″ Style=”{StaticResource PhoneTextTitle1Style}”/>

</StackPanel>

<!–ContentPanel – place additional content here–>

<Grid x:Name=”ContentPanel” Grid.Row=”1″ Margin=”12,-15,12,0″>

<StackPanel Orientation=”Vertical”>

<TextBlock Text=”First Name:” FontSize=”16″ Margin=”5″/>

<TextBox Name=”txtFirstName” />

<TextBlock Text=”Last Name:” FontSize=”16″ Margin=”5″ />

<TextBox Name=”txtLastName”/>

<TextBlock Text=”Address:” FontSize=”16″ Margin=”5″/>

<TextBox Name=”txtAddress” />

<TextBlock Text=”City:” FontSize=”16″ Margin=”5″ />

<TextBox Name=”txtCity”/>

<TextBlock Text=”Zip:” FontSize=”16″ Margin=”5″/>

<TextBox Name=”txtZip” />

<TextBlock Text=”State:” FontSize=”16″ Margin=”5″ />

<TextBox Name=”txtState”/>

</StackPanel>

</Grid>

</Grid>

<!–Sample code showing usage of ApplicationBar–>

<phone:PhoneApplicationPage.ApplicationBar>

<shell:ApplicationBar IsVisible=”True” IsMenuEnabled=”True” Opacity=”.2″>

<shell:ApplicationBarIconButton x:Name=”btnSave” IconUri=”images/Save.png” Text=”Save” Click=”btnSave_Click”/>

<shell:ApplicationBarIconButton x:Name=”btnCancel” IconUri=”images/Cancel.png” Text=”Cancel” Click=”btnCancel_Click”/>

</shell:ApplicationBar>

</phone:PhoneApplicationPage.ApplicationBar>

</phone:PhoneApplicationPage>

  • Paste the code in Add.xaml.cs:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

using Microsoft.Phone.Controls;

using WP7PanoramaOData.CustomersModel;

using System.Data.Services.Client;

namespace WP7PanoramaOData

{

public partial class Add : PhoneApplicationPage

{

public  CustomersEntities ctx = newCustomersEntities(new Uri(http://10.12.1.223/ODataSQLWP7/CustomerService.svc/&#8221;, UriKind.Absolute));

     public Add()

{

InitializeComponent();

}

//   Load data for the ViewModel Items

 private void btnSave_Click(object sender, EventArgs e)

{

// Instantiate the Client

var newCustomer = newCustomerInfo();

newCustomer.FirstName = this.txtFirstName.Text.Trim();

newCustomer.LastName = this.txtLastName.Text.Trim();

newCustomer.Address = this.txtAddress.Text.Trim();

newCustomer.City = this.txtCity.Text.Trim();

newCustomer.Zip = this.txtZip.Text.Trim();

newCustomer.State = this.txtState.Text.Trim();

ctx.AddObject(“CustomerInfoes”, newCustomer);

ctx.BeginSaveChanges(insertUserInDB_Completed, ctx);

}

   private void insertUserInDB_Completed(IAsyncResult result)

{

ctx.EndSaveChanges(result);

}

   private  void btnCancel_Click(object sender, EventArgs e)

{

     this.NavigationService.Navigate(newUri(“/MainPage.xaml”, UriKind.Relative));

}

}

}

  • Lets check after addition of new data , the Windows Phone 7.1 UI:

  • Lets check the steps of updating the existing data :

  • Lets update the list with new data & perform HTTP PUT operation for updating data of Astoria services.

  •  After updating existing data, you can refresh the Main list to get updated data. Same update happens in database too.

  • Source code to perform Update Operation with OData WCF Services for SQL Server / SQL Azure database for Windows Phone 7.1 Mango:

<phone:PhoneApplicationPage

x:Class=”WP7PanoramaOData.Edit”

xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation&#8221;

xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml&#8221;

xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”

xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”

xmlns:d=”http://schemas.microsoft.com/expression/blend/2008&#8243;

xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006&#8243;

FontFamily=”{StaticResource PhoneFontFamilyNormal}”

FontSize=”{StaticResource PhoneFontSizeNormal}”

Foreground=”{StaticResource PhoneForegroundBrush}”

SupportedOrientations=”Portrait” Orientation=”Portrait”

mc:Ignorable=”d” d:DesignHeight=”768″ d:DesignWidth=”480″

shell:SystemTray.IsVisible=”True”>

<!–LayoutRoot is the root grid where all page content is placed–>

<Grid x:Name=”LayoutRoot”>

<Grid.Background>

<ImageBrush ImageSource=”images/Back.png”/>

</Grid.Background>

<Grid.RowDefinitions>

<RowDefinition Height=”Auto”/>

<RowDefinition Height=”*”/>

</Grid.RowDefinitions>

<!–TitlePanel contains the name of the application and page title–>

<StackPanel x:Name=”TitlePanel” Grid.Row=”0″ Margin=”12,17,0,28″>

<TextBlock x:Name=”ApplicationTitle” Text=”MY APPLICATION” Style=”{StaticResource PhoneTextNormalStyle}”/>

<TextBlock x:Name=”PageTitle” Text=”Edit Data” Margin=”9,-7,0,0″ Style=”{StaticResource PhoneTextTitle1Style}”/>

</StackPanel>

<!–ContentPanel – place additional content here–>

<Grid x:Name=”ContentPanel” Grid.Row=”1″ Margin=”12,-15,12,0″>

<StackPanel Orientation=”Vertical”>

<TextBlock Text=”First Name:” FontSize=”16″ Margin=”5″/>

<TextBox Name=”txtFirstName” />

<TextBlock Text=”Last Name:” FontSize=”16″ Margin=”5″ />

<TextBox Name=”txtLastName”/>

<TextBlock Text=”Address:” FontSize=”16″ Margin=”5″/>

<TextBox Name=”txtAddress” />

<TextBlock Text=”City:” FontSize=”16″ Margin=”5″ />

<TextBox Name=”txtCity”/>

<TextBlock Text=”Zip:” FontSize=”16″ Margin=”5″/>

<TextBox Name=”txtZip” />

<TextBlock Text=”State:” FontSize=”16″ Margin=”5″ />

<TextBox Name=”txtState”/>

</StackPanel>

</Grid>

</Grid>

<!–Sample code showing usage of ApplicationBar–>

<phone:PhoneApplicationPage.ApplicationBar>

<shell:ApplicationBar IsVisible=”True” IsMenuEnabled=”True” Opacity=”.2″>

<shell:ApplicationBarIconButton x:Name=”btnSave” IconUri=”images/Save.png” Text=”Save” Click=”btnSave_Click”/>

<shell:ApplicationBarIconButton x:Name=”btnCancel” IconUri=”images/Cancel.png” Text=”Cancel” Click=”btnCancel_Click”/>

</shell:ApplicationBar>

</phone:PhoneApplicationPage.ApplicationBar>

</phone:PhoneApplicationPage>

  • Edit.xaml.cs :

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using  System.Windows.Shapes;

using Microsoft.Phone.Controls;

using WP7PanoramaOData.CustomersModel;

using System.Data.Services.Client;

namespace WP7PanoramaOData

{

public partialclassEdit : PhoneApplicationPage

{

public CustomersEntities ctx = newCustomersEntities(newUri(http://10.12.1.223/ODataSQLWP7/CustomerService.svc/&#8221;, UriKind.Absolute));

public Edit()

{

InitializeComponent();

}

privatevoid btnSave_Click(object sender, EventArgs e)

{

var qry = ctx.CreateQuery<CustomerInfo>(“CustomerInfoes”).AddQueryOption(“$filter”, “FirstName eq” + “\'” + “Maria” + “\'”);

qry.BeginExecute(r =>

{

var query = r.AsyncState asDataServiceQuery<CustomerInfo>;

               try

{

            Deployment.Current.Dispatcher.BeginInvoke(() =>

{

                var result = query.EndExecute(r).First();

result.FirstName = this.txtFirstName.Text;

result.LastName = this.txtLastName.Text.Trim();

result.Address =  this.txtAddress.Text.Trim();

result.City = this.txtCity.Text.Trim();

result.Zip = this.txtZip.Text.Trim();

result.State = this.txtState.Text.Trim();

ctx.UpdateObject(result);

ctx.BeginSaveChanges(changeUserInDB_Completed, ctx);

});

}

      catch (Exception ex)

{

MessageBox.Show(ex.ToString());

}

}, qry);

}

private void changeUserInDB_Completed(IAsyncResult result)

{

ctx.EndSaveChanges(result);

}

private void btnCancel_Click(object sender, EventArgs e)

{

this.NavigationService.Navigate(newUri(“/MainPage.xaml”, UriKind.Relative));

}

}

}

 

%d bloggers like this: