Reading from the SD card for Windows Phone 8

August 19, 2014

Applies to: Windows Phone 8 and Windows Phone Silverlight 8.1 only

Windows Phone apps can read specific file types from the SD card using the Microsoft.Phone.Storage APIs. To read a file, your app must register for a file association in the app manifest file and declare what file types (extensions) it can handle. Because a file association is required, your app can also be launched to handle files that are not on the SD card. For more info about file associations, see Auto-launching apps using file and URI associations for Windows Phone 8.

Code featured in this topic is from the Route Mapper Sample. The sample demonstrates the primary steps for handling a file association and reading from the SD card.

NoteNote:

The Route Mapper Sample reads routes from GPX files and displays them on a map control. It can read GPX files from the SD card and from file associations. A single page, RoutePage.xaml, is used to handle both cases. To read a GPX file from the SD card, MainPage.xaml sends RoutePage.xaml the path to the file via the URI that launches the page. For more info, see of the sample.

This topic contains the following sections.

In the app manifest file, WMAppManifest.xml, specify the ID_CAP_REMOVEABLE_STORAGE capability to access the SD card.

<Capability Name="ID_CAP_REMOVABLE_STORAGE" />

To handle a specific file type, register for a file association extension. Extensions are specified in WMAppManifest.xml. Just after the Tokens element, inside the Extensions element, the file association extension is specified with the following FileTypeAssociation element.

<FileTypeAssociation TaskID="_default" Name="GPX" NavUriFragment="fileToken=%s">
  <Logos>
    <Logo Size="small" IsRelative="true">Assets/Route_Mapper_Logo33x33.png</Logo>
    <Logo Size="medium" IsRelative="true">Assets/Route_Mapper_Logo69x69.png</Logo>
    <Logo Size="large" IsRelative="true">Assets/Route_Mapper_Logo176x176.png</Logo>
  </Logos>
  <SupportedFileTypes>
    <FileType ContentType="application/gpx">.gpx</FileType>
  </SupportedFileTypes>
</FileTypeAssociation>

The Windows Phone Manifest Designer doesn’t support the Extensions element. For more info about editing extensions, see How to modify the app manifest file for Windows Phone 8.

Because a file association is required to read a specific file type from the SD card, your app can be launched to handle files that aren’t on the SD card. To handle those scenarios, add a custom URI mapper to pass the file association’s token to the correct page. This code is from the CustomURIMapper.cs file of the sample.


using System;
using System.Windows.Navigation;
using Windows.Phone.Storage.SharedAccess;

namespace sdkRouteMapperWP8CS
{
    class CustomURIMapper : UriMapperBase
    {
        private string tempUri;

        public override Uri MapUri(Uri uri)
        {
            tempUri = uri.ToString();

            // File association launch
            // Example launch URI: /FileTypeAssociation?fileToken=89819279-4fe0-4531-9f57-d633f0949a19
            if (tempUri.Contains("/FileTypeAssociation"))
            {
                // Get the file ID (after "fileToken=").
                int fileIDIndex = tempUri.IndexOf("fileToken=") + 10;
                string fileID = tempUri.Substring(fileIDIndex);

                // Map the file association launch to route page.
                return new Uri("/RoutePage.xaml?fileToken=" + fileID, UriKind.Relative);
            }

            // Otherwise perform normal launch.
            return uri;
        }
    }
}


In this example, when a file association launches the app, the URI mapper maps the incoming URI to a page named RoutePage.xaml and passes the fileToken value to the destination page so that the page can open the file. If the app launches for any other reason, the mapper returns the incoming URI in its original state.

The following code shows how the URI mapper is assigned to the App object in the InitializePhoneApplication method of the App.xaml.cs file.


private void InitializePhoneApplication()
{
    if (phoneApplicationInitialized)
        return;

    // Create the frame but don't set it as RootVisual yet; this allows the splash
    // screen to remain active until the application is ready to render.
    RootFrame = new PhoneApplicationFrame();
    RootFrame.Navigated += CompleteInitializePhoneApplication;

    // Assign the URI-mapper class to the application frame.
    RootFrame.UriMapper = new CustomURIMapper();


    // Handle navigation failures
    RootFrame.NavigationFailed += RootFrame_NavigationFailed;

    // Handle reset requests for clearing the backstack
    RootFrame.Navigated += CheckForResetNavigation;

    // Ensure we don't initialize again
    phoneApplicationInitialized = true;
}


When the page is launched, the page can access all of the parameters in the URI (that launched the page) using the QueryString property of the page’s NavigationContext object. The following example shows how the value of the fileToken parameter is extracted from the URI and assigned to a variable for use later. This code is from the RoutePage.xaml.cs file of the sample.


// Assign the path or token value, depending on how the page was launched.
protected override async void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    // Route is from a file association.
    if (NavigationContext.QueryString.ContainsKey("fileToken"))
    {
        _fileToken = NavigationContext.QueryString["fileToken"];
        await ProcessExternalGPXFile(_fileToken);
    }
    // Route is from the SD card.
    else if (NavigationContext.QueryString.ContainsKey("sdFilePath"))
    {
        _sdFilePath = NavigationContext.QueryString["sdFilePath"];
        await ProcessSDGPXFile(_sdFilePath);
    }
}


Paths to files on the SD card are passed to RoutePage.xaml from MainPage.xaml using a parameter named sdFilePath. That code is covered in step 6.

A file association includes a token in the app launch URI, fileToken, that can be used to access the file that is being launched. As shown in the previous step, copy the token from the URI mapper to the page so that your page can access the file. Use CopySharedFileAsync to copy the file to your app’s local folder before using it. If you want to access the name of the file prior to copying it, you can call the GetSharedFileName method from the page or the URI mapper. In RoutePage.xaml of the sample, the ProcessExternalGPXFile method uses the token to copy the file to the app’s local folder.


// Process a route from a file association.
public async Task ProcessExternalGPXFile(string fileToken)
{
    // Create or open the routes folder.
    IStorageFolder routesFolder = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFolderAsync(ROUTES_FOLDER_NAME, CreationCollisionOption.OpenIfExists);

    // Get the full file name of the route (.GPX file) from the file association.
    string incomingRouteFilename = Windows.Phone.Storage.SharedAccess.SharedStorageAccessManager.GetSharedFileName(fileToken);

    // Copy the route (.GPX file) to the Routes folder.
    IStorageFile routeFile = await Windows.Phone.Storage.SharedAccess.SharedStorageAccessManager.CopySharedFileAsync((StorageFolder)routesFolder, incomingRouteFilename, NameCollisionOption.GenerateUniqueName, fileToken);

    // Create a stream for the route.
    var routeStream = await routeFile.OpenReadAsync();

    // Read the route data.
    ReadGPXFile(routeStream.AsStream());
}


The ReadGPXFile method shown in this example processes the route data and draws it on a map control. For more info about using maps in your app, see Maps and navigation for Windows Phone 8.

The Route Mapper Sample breaks up the SD card functionality into two discrete parts: scanning the SD card and opening a file from a Path. In the following code, the app scans the SD card for GPX files and saves their file paths to an ObservableCollection<T> named Routes. In MainPage.xaml, Routes is bound to a ListBox named gpxFilesListBox so that the app can display the names of GPX files that are present on the SD card.


// Connect to the current SD card.
ExternalStorageDevice _sdCard = (await ExternalStorage.GetExternalStorageDevicesAsync()).FirstOrDefault();

// If the SD card is present, add GPX files to the Routes collection.
if (_sdCard != null)
{
    try
    {
        // Look for a folder on the SD card named Routes.
        ExternalStorageFolder routesFolder = await _sdCard.GetFolderAsync("Routes");

        // Get all files from the Routes folder.
        IEnumerable<ExternalStorageFile> routeFiles = await routesFolder.GetFilesAsync();

        // Add each GPX file to the Routes collection.
        foreach (ExternalStorageFile esf in routeFiles)
        {
            if (esf.Path.EndsWith(".gpx"))
            {
                Routes.Add(esf);
            }
        }
    }
    catch (FileNotFoundException)
    {
        // No Routes folder is present.
        MessageBox.Show("The Routes folder is missing on your SD card. Add a Routes folder containing at least one .GPX file and try again.");
    }
}
else 
{
    // No SD card is present.
    MessageBox.Show("The SD card is mssing. Insert an SD card that has a Routes folder containing at least one .GPX file and try again.");
}


When the user taps on a GPX file displayed in gpxFilesListBox, MainPage.xaml launches RoutePage.xaml and passes the file path as a URI parameter. Because gpxFilesListBox is bound to Routes, and Routes is of type ExternalStorageFile, the sender parameter returned by the gpxFilesListBox_SelectionChanged_1 event hander can be used to access the SD card file path. The Path is accessed when the URI is created, as shown below.


// When a different route is selected, launch the RoutePage and send the file path with the URI.
private void gpxFilesListBox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
    ListBox lb = (ListBox)sender;

    if (lb.SelectedItem != null)
    {
        ExternalStorageFile esf = (ExternalStorageFile)lb.SelectedItem;
        NavigationService.Navigate(new Uri("/RoutePage.xaml?sdFilePath=" + esf.Path, UriKind.Relative));
    }
}


Finally, on RoutePage.xaml, the ProcessSDGPXFile method uses the path to get the file from the SD card and open a stream to it for the ReadGPXFiles method.


// Process a route from the SD card.
private async Task ProcessSDGPXFile(string _sdFilePath)
{
    // Connect to the current SD card.
    ExternalStorageDevice sdCard = (await ExternalStorage.GetExternalStorageDevicesAsync()).FirstOrDefault();

    // If the SD card is present, get the route from the SD card.
    if (sdCard != null)
    {
        try
        {
            // Get the route (.GPX file) from the SD card.
            ExternalStorageFile file = await sdCard.GetFileAsync(_sdFilePath);

            // Create a stream for the route.
            Stream s = await file.OpenForReadAsync();

            // Read the route data.
            ReadGPXFile(s);
        }
        catch (FileNotFoundException)
        {
            // The route is not present on the SD card.
            MessageBox.Show("That route is missing on your SD card.");
        }
    }
    else
    {
        // No SD card is present.
        MessageBox.Show("The SD card is mssing. Insert an SD card that has a Routes folder containing at least one .GPX file and try again.");
    }
}


For more info about how ReadGPXFiles displays the route on a map control, download the Route Mapper Sample.

Show:
© 2014 Microsoft