The Fake Call app makes your phone appear to receive an incoming call at a time you specify. You can use this as an excuse to get out of a bad date or an otherwise-unpleasant situation. Simply pretend to answer the fake call, make up some plausible story about needing to leave as a result of the phone call, and leave!
This app has three pages:
- The main page (for your eyes only), which enables you to customize when the fake call will occur and who is fake-calling you—in case the victim of your scheme catches a glimpse of your screen.
- The incoming-call page, which stays blank until it’s time for the incoming call to appear.
- The call-in-progress page, which mimics the active phone call once you press the “answer” button.
Unlike the preceding app, this intentionally does not use the page transition animation when navigating from one page to another, because that would interfere with the illusion that the real Phone app is being used. In this app, multiple pages are used just as a nice way to structure the code rather than as a metaphor that benefits users.
The Main Page
Fake Call’s main page, pictured in Figure 9.1
The User Interface
Listing 9.1 contains the XAML for the main page.
[code]
<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.MainPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:shell=”clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
xmlns:toolkit=”clr-namespace:Microsoft.Phone.Controls;
➥assembly=Microsoft.Phone.Controls.Toolkit”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”PortraitOrLandscape”
shell:SystemTray.IsVisible=”True”>
<!– Add some items to the page’s resource dictionary –>
<phone:PhoneApplicationPage.Resources>
<!– A margin for the standard header –>
<Thickness x:Key=”PhoneTitlePanelMargin”>24,16,0,12</Thickness>
<!– The tweaked application title style –>
<Style x:Key=”PhoneTextTitle0Style” TargetType=”TextBlock”>
<Setter Property=”FontFamily”
Value=”{StaticResource PhoneFontFamilySemiBold}”/>
<Setter Property=”Foreground”
Value=”{StaticResource PhoneForegroundBrush}”/>
<Setter Property=”FontSize” Value=”{StaticResource PhoneFontSizeMedium}”/>
<Setter Property=”Margin” Value=”-1,0,0,0”/>
</Style>
<!– The tweaked page title style –>
<Style x:Key=”PhoneTextTitle1Style” TargetType=”TextBlock”>
<Setter Property=”FontFamily”
Value=”{StaticResource PhoneFontFamilySemiLight}”/>
<Setter Property=”Foreground”
Value=”{StaticResource PhoneForegroundBrush}”/>
<Setter Property=”FontSize”
Value=”{StaticResource PhoneFontSizeExtraExtraLarge}”/>
<Setter Property=”Margin” Value=”-3,-10,0,0”/>
</Style>
<!– A style for text blocks that act as a label for a text box –>
<Style x:Key=”LabelStyle” TargetType=”TextBlock”>
<Setter Property=”Foreground” Value=”{StaticResource PhoneSubtleBrush}”/>
<Setter Property=”Margin” Value=”24,17,24,-5”/>
</Style>
</phone:PhoneApplicationPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<!– The standard header, with some tweaks –>
<StackPanel Grid.Row=”0” Margin=”{StaticResource PhoneTitlePanelMargin}”>
<TextBlock Text=”FAKE CALL” Style=”{StaticResource PhoneTextTitle0Style}”/>
<TextBlock Text=”call settings”
Style=”{StaticResource PhoneTextTitle1Style}”/>
</StackPanel>
<!– Scrollable pane (for the sake of the landscape orientation) –>
<ScrollViewer Grid.Row=”1”>
<!– A button and three text block / text box pairs stacked vertically –>
<StackPanel>
<Button Content=”wait for call” HorizontalAlignment=”Left”
Margin=”12,13,0,18” Click=”WaitForCallButton_Click”/>
<TextBlock Text=”Time to receive call”
Style=”{StaticResource LabelStyle}”/>
<toolkit:TimePicker x:Name=”TimePicker” local:Tilt.IsEnabled=”True”
ValueChanged=”TimePicker_ValueChanged”
Margin=”{StaticResource PhoneHorizontalMargin}”/>
<TextBlock Text=”Incoming phone number”
Style=”{StaticResource LabelStyle}”/>
<!– Use the Number input scope so we also get parentheses and a dash –>
<TextBox x:Name=”PhoneNumberTextBox” InputScope=”Number”
Margin=”{StaticResource PhoneHorizontalMargin}”
GotFocus=”TextBox_GotFocus” KeyDown=”TextBox_KeyDown”/>
<TextBlock Text=”Carrier name” Style=”{StaticResource LabelStyle}”/>
<TextBox x:Name=”CarrierTextBox” InputScope=”PersonalFullName”
Margin=”{StaticResource PhoneHorizontalMargin}”
GotFocus=”TextBox_GotFocus” KeyDown=”TextBox_KeyDown”/>
</StackPanel>
</ScrollViewer>
</Grid>
</phone:PhoneApplicationPage>
[/code]
Notes:
- This page supports all orientations for the sake of text entry.
- This page appropriately uses a time picker rather than a simple text box for choosing the call time. Although it initially looks like a text box, it presents a rich fullpage interface when tapped that matches the phone’s built-in apps, as pictured in Figure 9.2. The time picker ships with the Silverlight for Windows Phone Toolkit, so you must reference the toolkit’s assembly and namespace to use it.
- The PhoneNumberTextBox text box uses the Number input scope rather than TelephoneNumber because its text is displayed as-is on subsequent pages. This enables the user to type parentheses and a dash to get the optimal display. The Number input scope includes these keys, but TelephoneNumber does not.
- The most important aspect of this listing is its use of resources and styles, covered next.
The time picker requires two specific images to be included in your project!
As with the date picker, you must include two images from the Silverlight for Windows Phone Toolkit in your project (and mark them with a Build Action of Content).These images are ApplicationBar.Check.png and ApplicationBar.Cancel.png, and they must be placed in a folder called Toolkit.Content in the root of your project.
Resources
Windows Phone apps can have two types of resources. One type, often called binary resources, consists of things like image files, audio/video files, and font files. Sometimes these are embedded into your DLL (when the Build Action is set to Resource) and sometimes these are placed as loose files inside your .xap file (when the Build Action is set to Content). The choice of how to package binary resources is sometimes dictated by their consumer (such as the application bar requiring loose files), and sometimes dictated by how you wish to localize the resources in order support multiple languages and regions. As you’ve seen in previous chapters, various elements support referencing binary resources via URIs.
Defining XAML Resources
The XAML files in this app, such as MainPage.xaml in Listing 9.1, make use of the other type of resources. They are often called XAML resources because they are typically defined in XAML, but this book generally refers to them as simply resources, using the term binary resources to refer to the other kind.
A resource is an arbitrary object stored in an element’s Resources property. Such objects can be anything—a number, a brush, or a complex object such as a style. The first resource defined in Listing 9.1 is a thickness, the data type used by margins and padding:
[code]<Thickness x:Key=”PhoneTitlePanelMargin”>24,16,0,12</Thickness>[/code]
The point of defining a resource is to separate and consolidate styling information, much like using Cascading Style Sheets (CSS) to control colors and styles in a webpage rather than hard-coding them on individual elements. The Resources property is a dictionary (usually called a resource dictionary), so adding a child element is equivalent to adding a new key/value pair to the dictionary in C#. The value is the element, and the key is the string specified via the x:Key attribute. Everything placed in a resource dictionary must have a key.
Referencing XAML Resources
Resources are referenced with the familiar StaticResource syntax. Each resource is often referenced by more than one element, although the thickness resource with the PhoneTitlePanelMargin key happens to be referenced only once:
[code]
<StackPanel Grid.Row=”0” Margin=”{StaticResource PhoneTitlePanelMargin}”>
…
</StackPanel>
[/code]
The identifier specified along with StaticResource is the key for the desired resource. Because the object with the PhoneTitlePanelMargin key is the right data type for the Margin property, setting it this way is valid and behaves exactly the same as setting it directly:
[code]
<StackPanel Grid.Row=”0” Margin=”24,16,0,12”>
…
</StackPanel>
[/code]
A Hierarchy of Resource Dictionaries
All visual elements have their own resource dictionary (e.g. a Resources property), not just a page. And the StaticResource mechanism doesn’t simply look in the current page’s resource dictionary for the matching key. It starts by checking the current element’s resource dictionary, and then it checks the parent element, its parent, and so on until it reaches the root element (the frame containing the current page). After that, it checks a resource dictionary defined on the Application object.
You can see the application-wide resource dictionary in any project, initially empty, in the Visual Studio-generated App.xaml file:
[code]
<Application …>
<!– Add a style to the application-wide resource dictionary –>
<Application.Resources>
</Application.Resources>
…
</Application>
[/code]
This means that you can define a toplevel set of resources and override them at arbitrary points in the tree of elements (similar to data contexts). Although each individual resource dictionary requires unique keys, the same key can be used in multiple dictionaries. The one “closest” to the element referencing the resource wins.
The reason why we’ve been able to use StaticResource to reference phone theme resources such as PhoneForegroundBrush and PhoneBackgroundBrush is that these resources are automatically injected into the app when it starts. You can see the resource dictionary for each combination of theme and accent color in ThemeResources.xaml files under %ProgramFiles%Microsoft SDKsWindows Phonev7.0Design.
If you reference a resource in C#, you need to retrieve it from the exact dictionary containing it, for example:
[code]
// Get a resource from the page’s resource dictionary
this.someElement.Margin = (Thickness)this.Resources[“PhoneTitlePanelMargin”];
[/code]
Or as done in the Ruler app:
[code]
// Get a resource from the application-wide resource dictionary
this.LayoutRoot.Background =
Application.Current.Resources[“PhoneChromeBrush”] as Brush;
[/code]
C# code doesn’t have an automatic mechanism to find a resource in multiple locations, like what happens in XAML.
Styles
A style is a pretty simple entity. It collects several property values that could otherwise be set individually. It does this with a collection of setters that each names a property and its value, as with the following style from Listing 9.1:
[code]
<!– A style for text blocks that act as a label for a text box –>
<Style x:Key=”LabelStyle” TargetType=”TextBlock”>
<Setter Property=”Foreground” Value=”{StaticResource PhoneSubtleBrush}”/>
<Setter Property=”Margin” Value=”24,17,24,-5”/>
</Style>
[/code]
Visual elements have a Style property that can be directly set to an instance of a style, but the point of using a style is to define it as a resource instead. That way, you get the flexibility of sharing and overriding styles. Styles can be applied just like any other resource:
[code]
<TextBlock Text=”Carrier name” Style=”{StaticResource LabelStyle}”/>
[/code]
Notice that styles stored as resources often contain their own references to other resources. A resource can reference earlier resources from the same resource dictionary, or from a parent resource dictionary (such as the application-wide resource dictionary containing PhoneSubtleBrush in this example).
Note that style setters are for properties only. You cannot attach event handlers as part of a style.
The Code-Behind
Listing 9.2 contains the code-behind for the main page.
[code]
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Restore saved text box contents when entering this page
this.PhoneNumberTextBox.Text = Settings.PhoneNumber.Value;
this.CarrierTextBox.Text = Settings.Carrier.Value;
// Restore the time if it’s in the future, otherwise use the current time
if (Settings.CallTime.Value > DateTime.Now)
this.TimePicker.Value = Settings.CallTime.Value;
else
this.TimePicker.Value = DateTime.Now;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Persist contents when leaving this page for any reason
Settings.CallTime.Value = this.TimePicker.Value ?? DateTime.Now;
Settings.PhoneNumber.Value = this.PhoneNumberTextBox.Text;
Settings.Carrier.Value = this.CarrierTextBox.Text;
}
void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
// Select all text so it can be cleared with one keystroke
(sender as TextBox).SelectAll();
}
void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
// Cycle through the text boxes
if (sender == this.PhoneNumberTextBox)
this.CarrierTextBox.Focus();
else if (sender == this.CarrierTextBox)
this.TimePicker.Focus(); // Cycle back to the beginning
e.Handled = true;
}
}
void WaitForCallButton_Click(object sender, RoutedEventArgs e)
{
// Go to the next page
this.NavigationService.Navigate(new Uri(“/IncomingCallPage.xaml”,
UriKind.Relative));
}
void TimePicker_ValueChanged(object sender, DateTimeValueChangedEventArgs e)
{
// To prevent getting clobbered on way back in
Settings.CallTime.Value = e.NewDateTime ?? DateTime.Now;
}
}
}
[/code]
Notes:
- Because the three settings used by this app need to be accessed on three different pages, they are defined as public fields on a separate class called Settings:[code]
public static class Settings
{
public static readonly Setting<DateTime> CallTime =
new Setting<DateTime>(“CallTime”, DateTime.Now);
public static readonly Setting<string> PhoneNumber =
new Setting<string>(“PhoneNumber”, “”);
public static readonly Setting<string> Carrier =
new Setting<string>(“Carrier”, “AT&T”);
}
[/code]This pattern is used throughout the rest of the book for settings that need to be shared across multiple pages.
- The operation of the time picker causes a small wrinkle with our saving/restoring of settings in OnNavigatedFrom/OnNavigatedTo. Because changing the time picker’s value involves navigating to and from a different page, the following line in OnNavigatedTo wipes out the value that the user just chose, replacing it with the previously-saved value:[code] Settings.CallTime.Value = this.TimePicker.Value ?? DateTime.Now; [/code]
To work around this, Listing 9.2 handles the time picker’s ValueChanged event (with the TimePicker_ValueChanged handler) and saves the new value instantly. This way, when OnNavigatedTo is called moments later, the assignment is a no-op.
- The Value property on the time picker is a nullable DateTime, to support a state of no time being chosen. Unlike the date picker, however, a time picker’s initial value is null. Fake Call chooses to avoid handling this state by initializing it to DateTime.Now instead.
The Incoming-Call Page
The incoming-call page has two modes, shown in Figure 9.3 with ShowGridLines=”True” temporarily set on the page’s innermost grid. While waiting for the fake call to arrive, the screen is black to simulate it being off. When the call arrives, the screen mimics the incoming-call experience from the real Phone app. To help the user get used to how the app works, the “waiting mode” also shows a countdown to the call, but this can be hidden by tapping the screen. When using this app for real, the user should hide the countdown to avoid exposing the secret.
The User Interface
Listing 9.3 contains the XAML file for the incoming-call page.
LISTING 9.3 IncomingCall.xaml—The User Interface for Fake Call’s Incoming Call Page
[code]
<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.IncomingCallPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”Portrait”>
<!– Add one item to the page’s resource dictionary –>
<phone:PhoneApplicationPage.Resources>
<!– A style for the two text blocks in WaitingForCallPanel –>
<Style x:Key=”WaitingTextStyle” TargetType=”TextBlock”>
<Setter Property=”Foreground” Value=”#99FFFFFF”/>
<Setter Property=”FontFamily”
Value=”{StaticResource PhoneFontFamilySemiBold}”/>
<Setter Property=”FontSize” Value=”23”/>
</Style>
</phone:PhoneApplicationPage.Resources>
<!– A 1×1 grid holds two overlapping stack panels –>
<Grid>
<!– The initial panel that can show a countdown to the phone call –>
<StackPanel x:Name=”WaitingForCallPanel” Background=”Black”>
<TextBlock x:Name=”CountdownTextBlock” Margin=”11,120,0,0”
Style=”{StaticResource WaitingTextStyle}”/>
<TextBlock x:Name=”TapToHideTextBlock” Margin=”11,0,0,0”
Text=”(tap screen to hide)”
Style=”{StaticResource WaitingTextStyle}”/>
</StackPanel>
<!– The fake “incoming call” user interface –>
<StackPanel x:Name=”IncomingCallPanel” Visibility=”Collapsed”
Background=”{StaticResource PhoneChromeBrush}”>
<!– These two text blocks form the fake status bar –>
<TextBlock x:Name=”CurrentTimeTextBlock”
Style=”{StaticResource StatusBarTextStyle}”/>
<TextBlock x:Name=”CarrierTextBlock”
Style=”{StaticResource StatusBarTextStyle}”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<Grid Margin=”12,1,12,0” Height=”566”>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!– “INCOMING CALL” –>
<TextBlock Grid.ColumnSpan=”2” Text=”INCOMING CALL” Margin=”11,0,0,-10”
VerticalAlignment=”Bottom” FontSize=”23”
FontFamily=”{StaticResource PhoneFontFamilySemiBold}”
Foreground=”{StaticResource PhoneAccentBrush}”/>
<!– The phone number –>
<TextBlock x:Name=”PhoneNumberTextBlock” Grid.Row=”1” Grid.ColumnSpan=”2”
Margin=”6,22,0,51” TextWrapping=”Wrap” FontSize=”85”
LineHeight=”91” LineStackingStrategy=”BlockLineHeight”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}” />
<!– The answer/ignore buttons –>
<Button Grid.Row=”2” Content=”answer” Click=”AnswerButton_Click”
local:Tilt.IsEnabled=”True”/>
<Button Grid.Column=”1” Grid.Row=”2” Content=”ignore”
Click=”IgnoreButton_Click” local:Tilt.IsEnabled=”True”/>
</Grid>
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>
[/code]
Notes:
- Unlike the main page, this page (and the next one) is portrait-only to match the behavior of the phone app.
- This page (and the next one) uses a fake status bar. This enables it to blend in with the PhoneChromeBrush background. Although the real Phone app looks this way, third-party apps cannot change the color of the real status bar.
- The WaitingTextStyle style is defined in this page’s resource dictionary, but the StatusBarTextStyle style is defined in the application-level resource dictionary inside App.xaml so it can be shared by two pages:[code]
<Application …>
<!– Add a style to the application-wide resource dictionary –>
<Application.Resources>
<Style x:Key=”StatusBarTextStyle” TargetType=”TextBlock”>
<Setter Property=”HorizontalAlignment” Value=”Right”/>
<Setter Property=”Margin” Value=”0,0,13,4”/>
</Style>
</Application.Resources>
…
</Application>
[/code] - PhoneNumberTextBlock wraps its text by setting its TextWrapping property to Wrap. This alone doesn’t exactly match the real Phone app, because the wrapped lines are too far apart. Therefore, it uses an explicit LineHeight setting to better match the tight wrapping, as shown in Figure 9.4.
- The layout of this page keeps the answer and ignore buttons in a fixed position even as the length of the phone number text changes. Figure 9.5 shows the appearance of this page when the phone number is left blank, and when the user gets creative with long, custom text.
The Code-Behind
Listing 9.4 contains the code-behind for the incoming-call page.
LISTING 9.4 IncomingCall.xaml.cs—The Code-Behind for Fake Call’s Incoming Call Page
[code]
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Threading;
using Microsoft.Devices;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class IncomingCallPage : PhoneApplicationPage
{
// A timer that ticks once per second to keep track of the time
DispatcherTimer timer = new DispatcherTimer {
Interval = TimeSpan.FromSeconds(1)
};
// A variable-interval timer that performs the call vibration pattern
DispatcherTimer vibrationTimer = new DispatcherTimer();
// Bookkeeping for the vibration pattern used to simulate ringing
bool isRinging;
int vibrationStep;
public IncomingCallPage()
{
InitializeComponent();
this.timer.Tick += Timer_Tick;
this.vibrationTimer.Tick += VibrationTimer_Tick;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (this.WaitingForCallPanel.Visibility == Visibility.Visible)
{
// Hide the contents of WaitingForCallPanel when the screen is tapped
this.CountdownTextBlock.Visibility = Visibility.Collapsed;
this.TapToHideTextBlock.Visibility = Visibility.Collapsed;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Respect the current settings
this.CarrierTextBlock.Text = Settings.Carrier.Value;
this.PhoneNumberTextBlock.Text = Settings.PhoneNumber.Value;
// Start the main timer
this.timer.Start();
Timer_Tick(null, null); // Force initial update
// While on this page, don’t allow the screen to auto-lock
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Disabled;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Stop the main timer
this.timer.Stop();
// Stop ringing (if ringing)
StopRinging();
// Restore the ability for the screen to auto-lock when on other pages
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Enabled;
}
void StartRinging()
{
// Show IncomingCallPanel and start the vibration pattern
isRinging = true;
this.WaitingForCallPanel.Visibility = Visibility.Collapsed;
this.IncomingCallPanel.Visibility = Visibility.Visible;
this.vibrationStep = 0;
this.vibrationTimer.Start();
}
void StopRinging()
{
// Hide IncomingCallPanel, stop the vibration, and set the next call time
// to two minutes from now
isRinging = false;
this.WaitingForCallPanel.Visibility = Visibility.Visible;
this.IncomingCallPanel.Visibility = Visibility.Collapsed;
this.vibrationTimer.Stop();
Settings.CallTime.Value = DateTime.Now + TimeSpan.FromMinutes(2);
Timer_Tick(null, null); // Force an update now
}
void Timer_Tick(object sender, EventArgs e)
{
// Show the current time on the fake status bar
this.CurrentTimeTextBlock.Text = DateTime.Now.ToString(“h:mm”);
TimeSpan delta = Settings.CallTime.Value – DateTime.Now;
if (delta > TimeSpan.Zero)
{
// It’s not time to ring yet. Update the countdown in case it is visible.
this.CountdownTextBlock.Text = “COUNTDOWN: “ + (int)delta.TotalHours + “:”
+ delta.Minutes.ToString(“00”) + “:”
+ Math.Ceiling(delta.Seconds).ToString(“00”);
}
else if (!this.isRinging)
{
// It’s time to ring
StartRinging();
}
}
void AnswerButton_Click(object sender, RoutedEventArgs e)
{
// Go to the next page
this.NavigationService.Navigate(new Uri(“/CallInProgressPage.xaml”,
UriKind.Relative));
}
void IgnoreButton_Click(object sender, RoutedEventArgs e)
{
StopRinging();
}
void VibrationTimer_Tick(object sender, EventArgs e)
{
// Make a short-long-short-long pattern of vibration
switch (this.vibrationStep % 4) // Cycle from 0 – 3
{
case 0:
case 2:
VibrateController.Default.Start(TimeSpan.FromSeconds(.1)); // short
// Leave space between this short and the next long
this.vibrationTimer.Interval = TimeSpan.FromSeconds(.4);
break;
case 1:
VibrateController.Default.Start(TimeSpan.FromSeconds(.4)); // long
// Leave more space between this long and the next short
this.vibrationTimer.Interval = TimeSpan.FromSeconds(.8);
break;
case 3:
VibrateController.Default.Start(TimeSpan.FromSeconds(.4)); // long
// Leave even more space after every other long
this.vibrationTimer.Interval = TimeSpan.FromSeconds(2.1);
break;
}
this.vibrationStep++;
// Stop ringing after 20 seconds (6 full cycles of the 4-part pattern)
if (this.vibrationStep == 24)
StopRinging();
}
}
}
[/code]
Notes:
- The implementation of OnNavigatedTo and OnNavigatedFrom ensures that the app’s UserIdleDetectionMode property is set to Disabled while this page is active. As explained in the preceding chapter, this prevents the screen from automatically locking while the user waits for the fake phone call to arrive, as this would interfere with its operation. Unlike ApplicationIdleDetectionMode, UserIdleDetectionMode can be changed at any time. Running under the screen lock with ApplicationIdleDetectionMode is not useful for Fake Call, because even though it enables the phone to pretend-ring while it is locked, the user would still need to press the Power button, swipe upward, and enter their password (if the phone is password-protected) before seeing the incoming-call screen!
- The processing of the carrier and phone number settings could have been done in the constructor rather than in OnNavigatedTo, but then the main page would have had to override its OnNavigatingFrom method and set these values there rather than OnNavigatedFrom to prevent this page from getting stale values. This page is constructed between the previous page’s OnNavigatingFrom and OnNavigatedFrom events.
- Every time the phone stops ringing (either because the user tapped “ignore” or because they answered the fake call), the CallTime setting is changed to 2 minutes from the current time. This will simulate the person calling again every 2 minutes for as long as you keep the app open.
- The time on the fake status bar is formatted with an “h:mm” string, ensuring that the hour and minutes are displayed without the corresponding AM or PM. This matches the phone’s real status bar except when it has the 24-hour clock setting turned on.
- Pieces of the countdown string use the “00” format string to ensure that two digits are displayed, even if one is a padded 0.
- The “ringing” is done with a pattern of vibration that mimics the sound of a real incoming call when the ringer is set to vibrate.
The Call-In-Progress Page
The final page—the call-in-progress page—is pictured in Figure 9.6 with ShowGridLines=”True” marked on its second grid.
The User Interface
Listing 9.5 contains this page’s XAML file.
LISTING 9.5 CallInProgress.xaml—The User Interface for Fake Call’s Call-In-Progress Page
[code]
<phone:PhoneApplicationPage
x:Class=”WindowsPhoneApp.CallInProgressPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:phone=”clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone”
xmlns:local=”clr-namespace:WindowsPhoneApp”
xmlns:sys=”clr-namespace:System;assembly=mscorlib”
FontFamily=”{StaticResource PhoneFontFamilyNormal}”
FontSize=”{StaticResource PhoneFontSizeNormal}”
Foreground=”{StaticResource PhoneForegroundBrush}”
SupportedOrientations=”Portrait”>
<!– Add two items to the page’s resource dictionary –>
<phone:PhoneApplicationPage.Resources>
<Thickness x:Key=”ButtonMargin”>0,41,0,12</Thickness>
<sys:Int32 x:Key=”SmallButtonWidth”>88</sys:Int32>
</phone:PhoneApplicationPage.Resources>
<StackPanel Background=”{StaticResource PhoneChromeBrush}”
VerticalAlignment=”Top”>
<Grid>
<!– The fake signal-strength bars for the fake status bar –>
<Canvas>
<!– Add two styles to this canvas’s resource dictionary –>
<Canvas.Resources>
<Style x:Key=”BarOnStyle” TargetType=”Line”>
<Setter Property=”Stroke”
Value=”{StaticResource PhoneForegroundBrush}”/>
<Setter Property=”StrokeThickness” Value=”5”/>
<Setter Property=”Y2” Value=”26”/>
</Style>
<Style x:Key=”BarOffStyle” BasedOn=”{StaticResource BarOnStyle}”
TargetType=”Line”>
<Setter Property=”Stroke” Value=”{StaticResource PhoneSubtleBrush}”/>
<Setter Property=”Opacity” Value=”.3”/>
</Style>
</Canvas.Resources>
<Line Style=”{StaticResource BarOnStyle}”
X1=”16” X2=”16” Y1=”22”/>
<Line Style=”{StaticResource BarOnStyle}”
X1=”22” X2=”22” Y1=”18”/>
<Line Style=”{StaticResource BarOffStyle}”
X1=”28” X2=”28” Y1=”15”/>
<Line Style=”{StaticResource BarOffStyle}”
X1=”34” X2=”34” Y1=”11”/>
<Line Style=”{StaticResource BarOffStyle}”
X1=”40” X2=”40” Y1=”7”/>
</Canvas>
<!– The current time for the fake status bar–>
<TextBlock x:Name=”CurrentTimeTextBlock”
Style=”{StaticResource StatusBarTextStyle}”/>
</Grid>
<!– The carrier for the fake status bar–>
<TextBlock x:Name=”CarrierTextBlock”
Style=”{StaticResource StatusBarTextStyle}”
Foreground=”{StaticResource PhoneSubtleBrush}”/>
<Grid Margin=”12,1,12,0”>
<Grid.RowDefinitions>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
<RowDefinition Height=”Auto”/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition Width=”Auto”/>
</Grid.ColumnDefinitions>
<TextBlock x:Name=”CallDurationTextBlock” Grid.ColumnSpan=”3” FontSize=”21”
Margin=”12,20,0,0” Foreground=”{StaticResource PhoneAccentBrush}”/>
<TextBlock x:Name=”PhoneNumberTextBlock” Grid.Row=”1” Grid.ColumnSpan=”3”
Margin=”6,-5,0,28” TextWrapping=”Wrap” FontSize=”68”
FontFamily=”{StaticResource PhoneFontFamilySemiLight}” />
<!– The “end call” button –>
<Button Grid.Row=”2” Content=”end call” Click=”EndCallButton_Click”
Margin=”{StaticResource ButtonMargin}” local:Tilt.IsEnabled=”True”
Background=”{StaticResource PhoneAccentBrush}”/>
<!– The fake keypad button –>
<Button Grid.Row=”2” Grid.Column=”1” Margin=”{StaticResource ButtonMargin}”
Width=”{StaticResource SmallButtonWidth}” local:Tilt.IsEnabled=”True”>
<Canvas Width=”16” Height=”18”>
<!– Add a style to this canvas’s resource dictionary –>
<Canvas.Resources>
<Style x:Key=”SquareStyle” TargetType=”Rectangle”>
<Setter Property=”Width” Value=”4”/>
<Setter Property=”Height” Value=”4”/>
<Setter Property=”Fill”
Value=”{StaticResource PhoneForegroundBrush}”/>
</Style>
</Canvas.Resources>
<Rectangle Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”6” Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”12” Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Top=”6” Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”6” Canvas.Top=”6”
Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”12” Canvas.Top=”6”
Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Top=”12” Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”6” Canvas.Top=”12”
Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”12” Canvas.Top=”12”
Style=”{StaticResource SquareStyle}”/>
<Rectangle Canvas.Left=”6” Canvas.Top=”18”
Style=”{StaticResource SquareStyle}”/>
</Canvas>
</Button>
<!– The fake double-arrow button –>
<Button Width=”{StaticResource SmallButtonWidth}” Grid.Row=”2”
Grid.Column=”2” Margin=”{StaticResource ButtonMargin}”
local:Tilt.IsEnabled=”True”>
<Path Fill=”{StaticResource PhoneForegroundBrush}”
Data=”M0,2 14,2 7,11z M0,13 14,13 7,22” />
</Button>
</Grid>
</StackPanel>
</phone:PhoneApplicationPage>
[/code]
Notes:
- The SmallButtonWidth resource is an integer used as the width of the two small buttons. To create an integer element in XAML, the Int32 type is referenced from the System namespace in the mscorlib assembly.
- This page has a second reason to use a fake status bar—so it can show fake signalstrength bars that mimic the real Phone app experience. The fake bars are created with 5 lines in a canvas (the first two always on and the remaining always off). To avoid repeating several property settings, two styles shared by the lines are placed in the canvas’s resource dictionary. There is no need to have the styles in the page’s resource dictionary because they are only used in this specific spot.
- The keypad button contains ten rectangles to produce the appropriate appearance, and the doublearrow button is created with a Path shape. These buttons don’t actually do anything; they are just there to visually match the real Phone app.
- Unlike the incoming-call page, the content grows downward if the user has chosen long text that wraps, as shown in Figure 9.7.
The Code-Behind
Listing 9.6 contains the code-behind for this page.
LISTING 9.6 CallInProgress.xaml.cs—The Code-Behind for Fake Call’s Call-In-Progress Page
[code]
using System;
using System.Windows;
using System.Windows.Navigation;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class CallInProgressPage : PhoneApplicationPage
{
// A timer that ticks once per second to keep track of the time
DispatcherTimer timer = new DispatcherTimer {
Interval = TimeSpan.FromSeconds(1) };
TimeSpan callDuration;
public CallInProgressPage()
{
InitializeComponent();
this.timer.Tick += Timer_Tick;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Respect the current settings
this.CarrierTextBlock.Text = Settings.Carrier.Value;
this.PhoneNumberTextBlock.Text = Settings.PhoneNumber.Value;
// Start at -1 seconds because Timer_Tick is about to increase it by 1
this.callDuration = TimeSpan.Zero – TimeSpan.FromSeconds(1);
// Start the main timer
this.timer.Start();
Timer_Tick(null, null); // Force an update now
// While on this page, don’t allow the screen to auto-lock
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Disabled;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Stop the main timer
this.timer.Stop();
// Set the next call time to two minutes from now
Settings.CallTime.Value = DateTime.Now + TimeSpan.FromMinutes(2);
// Restore the ability for the screen to auto-lock when on other pages
PhoneApplicationService.Current.UserIdleDetectionMode =
IdleDetectionMode.Enabled;
}
void Timer_Tick(object sender, EventArgs e)
{
// Show the current time on the fake status bar
this.CurrentTimeTextBlock.Text = DateTime.Now.ToString(“h:mm”);
// Update the call duration display
this.callDuration = this.callDuration.Add(TimeSpan.FromSeconds(1));
this.CallDurationTextBlock.Text = (int)this.callDuration.TotalMinutes +
“:” + this.callDuration.Seconds.ToString(“00”);
}
void EndCallButton_Click(object sender, RoutedEventArgs e)
{
// Go back to the incoming call page, which will wait for the next call
if (this.NavigationService.CanGoBack)
this.NavigationService.GoBack();
}
}
}
[/code]
The code-behind for this page is similar to the code-behind for the previous page, just a bit simpler. Ideally, the app would just exit when the user taps the “end call” button, but there’s no good way to accomplish this. Therefore, after the user taps “end call,” they can either press the Power button to lock their phone or the Start button to leave this app—if they don’t want to be fake-called again in 2 minutes.
Exiting an App Programmatically
To make the app easier to quickly exit, you could merge all of Fake Call’s functionality onto one page to avoid filling the back stack. However, this would make the code messier, and the user would still need to press the hardware Back button to exit the app.
Throwing an unhandled exception would force the app to exit, but this has two problems. If you do this, settings won’t be properly persisted to disk (because this is internally done during a graceful app shutdown). Also, such behavior would not get past the marketplace certification process. So don’t ever attempt to force an app to exit this way!
Another approach would be to call XNA’s Game.Exit method from the Microsoft.Xna. Framework.Game assembly.However, calling anything from this assembly will cause your app to fail marketplace certification.You might be able to get away with using .NET reflection to call Game.Exit in a way that the certification process doesn’t detect, but I wouldn’t recommend it.
The Finished Product