×
Namespaces

Variants
Actions
(Difference between revisions)

How to create a Company Hub app for Windows Phone

From Nokia Developer Wiki
Jump to: navigation, search
GuruuMeditation (Talk | contribs)
(GuruuMeditation -)
GuruuMeditation (Talk | contribs)
(GuruuMeditation -)
Line 1: Line 1:
 
[[Category:Draft]][[Category:Windows Phone 8]]
 
[[Category:Draft]][[Category:Windows Phone 8]]
 
+
{{Abstract|This article explains how to develop a Company Hub for Windows Phone.
 
+
It will not cover MDM (Mobile Devices Management) systems like Intune.
{{Abstract|This article explains how to ... }} ''Replace the abstract text with a short paragraph (or sentence) describing what the topic covers.''
+
}}  
  
  
Line 27: Line 27:
 
== Introduction ==
 
== Introduction ==
  
This is an article I started almost one year ago. After some change of priority and some procrastination, it is time to deliver…
+
These are the steps covered in this Wiki :
 
+
I will no talk about MDM like InTune. It is all hand made here. Old school.
+
 
+
[[File:HubExample.png|200px]]
+
 
+
== Steps ==
+
Here are the steps to create a company hub and side-load apps :
+
  
 
* Register as company
 
* Register as company
Line 43: Line 36:
 
* Create app discovery service
 
* Create app discovery service
 
* Send the certificate and the company app to users
 
* Send the certificate and the company app to users
 +
 +
There is, at the end, the source code of a Company Hub sample :
 +
 +
[[File:HubExample.png|200px]]
  
 
== Register as company ==
 
== Register as company ==
  
When you create your developer account, you should create a company one, not an individual. Once the company verification is done, you’ll get a Symantec ID :
+
When you create your developer account, you should create a company one, not an individual. Once the company verification is done, you’ll get a Symantec ID : <br />
[[File:CompanyHub1.jpg|500px]]
+
 
 +
[[File:CompanyHub1.jpg|300px]]
  
 
== Get a Symantec Certificate ==
 
== Get a Symantec Certificate ==
Line 69: Line 67:
  
 
This is the PacketInfo class I use. It gives all the information I need about available apps (ID, name, description, URL, icon,…) and has also some ViewModel-like properties (I want to make the things simple) :
 
This is the PacketInfo class I use. It gives all the information I need about available apps (ID, name, description, URL, icon,…) and has also some ViewModel-like properties (I want to make the things simple) :
 
+
<code csharp>
[code csharp]
+
    [Serializable]
[Serializable]
+
 
     [DataContract]
 
     [DataContract]
 
     public class PackageInfo
 
     public class PackageInfo
Line 77: Line 74:
 
         [DataMember]
 
         [DataMember]
 
         public Guid ID { get; set; }
 
         public Guid ID { get; set; }
 
 
         [DataMember]
 
         [DataMember]
 
         public string Name { get; set; }
 
         public string Name { get; set; }
 
 
         [DataMember]
 
         [DataMember]
 
         public string Description { get; set; }
 
         public string Description { get; set; }
 
 
         [DataMember]
 
         [DataMember]
 
         public string Thumbnail { get; set; }
 
         public string Thumbnail { get; set; }
 
 
         [DataMember]
 
         [DataMember]
 
         public string XAPPath { get; set; }
 
         public string XAPPath { get; set; }
 
 
         [DataMember]
 
         [DataMember]
 
         public object Icon { get; set; }
 
         public object Icon { get; set; }
Line 95: Line 87:
 
         [DataMember]
 
         [DataMember]
 
         public bool Downloading { get; set; }
 
         public bool Downloading { get; set; }
 
 
         [DataMember]
 
         [DataMember]
 
         public uint Progress { get; set; }
 
         public uint Progress { get; set; }
 
 
         [DataMember]
 
         [DataMember]
 
         public string ProgressString { get; set; }
 
         public string ProgressString { get; set; }
 
 
         [DataMember]
 
         [DataMember]
 
         public object Package { get; set; }
 
         public object Package { get; set; }
 
     }
 
     }
[/code]
+
</code>
 
+
 
This is the part where I load the two lists : the available app list and the already installed app list :
 
This is the part where I load the two lists : the available app list and the already installed app list :
  
[code csharp]
+
<code csharp>
 
  // Clear the ObservableCollections
 
  // Clear the ObservableCollections
 
InstalledPackages.Clear();
 
InstalledPackages.Clear();
AvailablePackages.Clear();
+
AvailablePackages.Clear();[/br]
+
 
// Get all packages from web service  
 
// Get all packages from web service  
 
var allpackages = await GetPackageListAsync();
 
var allpackages = await GetPackageListAsync();
Line 151: Line 138:
 
                 AvailablePackages.Add(package);
 
                 AvailablePackages.Add(package);
 
}
 
}
[/code]
+
</code>
  
 
I get the available app list from my webservice. I get the installed app list with the FindPackagessForCurrentPublisher method.
 
I get the available app list from my webservice. I get the installed app list with the FindPackagessForCurrentPublisher method.
Line 161: Line 148:
 
This is what my available package list look like in XAML :
 
This is what my available package list look like in XAML :
  
[cpde xaml]
+
<code xml>
 
  <phone:LongListSelector Margin="0,-38,-22,2"
 
  <phone:LongListSelector Margin="0,-38,-22,2"
 
                                         ItemsSource="{Binding AvailablePackages}">
 
                                         ItemsSource="{Binding AvailablePackages}">
Line 210: Line 197:
 
                     </phone:LongListSelector.ItemTemplate>
 
                     </phone:LongListSelector.ItemTemplate>
 
                 </phone:LongListSelector>
 
                 </phone:LongListSelector>
[/code]
+
</code>
  
 
Just a simple LongListSelector. When you tap on an item, the app is installed :
 
Just a simple LongListSelector. When you tap on an item, the app is installed :
  
[code csharp]
+
<code csharp>
  // Install app
+
        // Install app
 
         private void Package_OnTap(object sender, GestureEventArgs e)
 
         private void Package_OnTap(object sender, GestureEventArgs e)
 
         {
 
         {
Line 249: Line 236:
  
 
         }
 
         }
[/code]
+
</code>
  
 
I use  '''InstallationManager.AddPackageAsync''' to add the package. It returns a IAsyncOperationWithProgress<PackageInstallResult, UInt32>, so I can follow the progress thanks to the Progress event. I use the Completed delegate to warn user it is installed or if there was an error.
 
I use  '''InstallationManager.AddPackageAsync''' to add the package. It returns a IAsyncOperationWithProgress<PackageInstallResult, UInt32>, so I can follow the progress thanks to the Progress event. I use the Completed delegate to warn user it is installed or if there was an error.
Line 259: Line 246:
 
To launch an app is straightforward:
 
To launch an app is straightforward:
  
[code csharp]
+
<code csharp>
 
// Lauch app
 
// Lauch app
 
private void LaunchApp_OnTap(object sender, GestureEventArgs e)
 
private void LaunchApp_OnTap(object sender, GestureEventArgs e)
Line 269: Line 256:
 
   package.Launch("");
 
   package.Launch("");
 
}
 
}
[/code]
+
</code>
  
 
== Develop company apps ==
 
== Develop company apps ==
Line 281: Line 268:
 
To do so, you have to install the certificate you got from Symantec and export it as PFX (example here : http://msdn.microsoft.com/en-us/library/gg432987.aspx). Then you can generate the AET (Application Enrollment Token) that you will have to send to the users. They have to install it on their phone in order to use your apps. There is an AET generator with the Phone SDK. To launch :
 
To do so, you have to install the certificate you got from Symantec and export it as PFX (example here : http://msdn.microsoft.com/en-us/library/gg432987.aspx). Then you can generate the AET (Application Enrollment Token) that you will have to send to the users. They have to install it on their phone in order to use your apps. There is an AET generator with the Phone SDK. To launch :
  
[code]
+
<code>
 
%ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v8.0\Tools\AETGenerator\AETGenerator.exe PFXFile Password
 
%ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v8.0\Tools\AETGenerator\AETGenerator.exe PFXFile Password
[/code]
+
</code>
  
 
It will generate 3 files. It is the AET.aetx that you send to users. The AET.aet is for MDM systems. The other is raw AET in XML. More info here : http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj735576(v=vs.105).aspx)
 
It will generate 3 files. It is the AET.aetx that you send to users. The AET.aet is for MDM systems. The other is raw AET in XML. More info here : http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj735576(v=vs.105).aspx)
Line 297: Line 284:
 
Here is it, for one of the example company app :
 
Here is it, for one of the example company app :
  
[code]
+
<code>
 
del "$(TargetDir)\AriesApp_new.xap"
 
del "$(TargetDir)\AriesApp_new.xap"
 
powershell.exe  -ExecutionPolicy ByPass -File "%ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v8.0\Tools\
 
powershell.exe  -ExecutionPolicy ByPass -File "%ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v8.0\Tools\
Line 303: Line 290:
 
         –pfxfilename "$(SolutionDir)\Keys\mycert.pfx" -password mypassword
 
         –pfxfilename "$(SolutionDir)\Keys\mycert.pfx" -password mypassword
 
copy /y "$(TargetDir)\AriesApp_new.xap" "$(SolutionDir)\WcfService1\XAP\AriesApp.xap"
 
copy /y "$(TargetDir)\AriesApp_new.xap" "$(SolutionDir)\WcfService1\XAP\AriesApp.xap"
[/code]
+
</code>
  
 
I delete the older version of the signed xap, I execute the script (my keys are stored in a Keys directory) and finally I copy the xap on my service website, where it can be downloaded by the users.
 
I delete the older version of the signed xap, I execute the script (my keys are stored in a Keys directory) and finally I copy the xap on my service website, where it can be downloaded by the users.
Line 315: Line 302:
 
I made a very simple one, it just read, deserialize an XML file and send it :
 
I made a very simple one, it just read, deserialize an XML file and send it :
  
[code csharp]
+
<code csharp>
 
  public class EntrepriseService : IEntrepriseService
 
  public class EntrepriseService : IEntrepriseService
 
     {
 
     {
Line 334: Line 321:
 
         }
 
         }
 
     }
 
     }
[/code]
+
</code>
  
 
The XML is like this :
 
The XML is like this :
  
[code]
+
<code xml>
 
<?xml version="1.0" encoding="utf-8" ?>
 
<?xml version="1.0" encoding="utf-8" ?>
 
<ArrayOfPackageInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 
<ArrayOfPackageInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
Line 356: Line 343:
 
   </PackageInfo>
 
   </PackageInfo>
 
</ArrayOfPackageInfo>
 
</ArrayOfPackageInfo>
[/code]
+
</code>
  
 
== Send the certificate and the company app to users ==
 
== Send the certificate and the company app to users ==

Revision as of 21:11, 6 October 2013

This article explains how to develop a Company Hub for Windows Phone. It will not cover MDM (Mobile Devices Management) systems like Intune.


SignpostIcon XAML 40.png
WP Metro Icon WP8.png
SignpostIcon Code 52.png
Article Metadata
Code ExampleTested with
SDK: Windows Phone 8.0 SDK
Devices(s): Nokia Lumia 920
Compatibility
Platform(s):
Windows Phone 8
Platform Security
Signing Required: Symantec Certificate
Article
Created: User:Guruumeditation (06 Oct 2013)
Last edited: GuruuMeditation (06 Oct 2013)

Contents

Introduction

These are the steps covered in this Wiki :

  • Register as company
  • Get a Symantec Certificate
  • Develop company hub
  • Develop company apps
  • Sign hub and apps
  • Create app discovery service
  • Send the certificate and the company app to users

There is, at the end, the source code of a Company Hub sample :

HubExample.png

Register as company

When you create your developer account, you should create a company one, not an individual. Once the company verification is done, you’ll get a Symantec ID :

CompanyHub1.jpg

Get a Symantec Certificate

Once you have your Symantec ID, you have to buy a $299/year certificate here : https://products.websecurity.symantec.com/orders/enrollment/microsoftCert.do

Develop company hub

The company hub is just like a regular application. you can do anything you want. Of course, it would be quite useless if there isn’t a company app discovery page where you can see which company apps are available to download and a page where you can launch the already installed apps. A company news or user company profile page wouldn’t hurt either.

There are 5 methods specially there for company hub :


I’ll give a simple example (sources given at the end) of a company hub with a 2 tabs panorama. One tab show already installed app. You can launch them with a tap. The other tab is a list of available company apps. Tap to install.

This is the PacketInfo class I use. It gives all the information I need about available apps (ID, name, description, URL, icon,…) and has also some ViewModel-like properties (I want to make the things simple) :

    [Serializable]
[DataContract]
public class PackageInfo
{
[DataMember]
public Guid ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public string Thumbnail { get; set; }
[DataMember]
public string XAPPath { get; set; }
[DataMember]
public object Icon { get; set; }
// To simplify the app, let's make this class a kind of VM with those properties :
[DataMember]
public bool Downloading { get; set; }
[DataMember]
public uint Progress { get; set; }
[DataMember]
public string ProgressString { get; set; }
[DataMember]
public object Package { get; set; }
}

This is the part where I load the two lists : the available app list and the already installed app list :

 // Clear the ObservableCollections
InstalledPackages.Clear();
AvailablePackages.Clear();[/br]
// Get all packages from web service
var allpackages = await GetPackageListAsync();
// get all package installed with same PublisherID as the Company hub
var installed = InstallationManager.FindPackagesForCurrentPublisher().ToList();
// Company Hub ProductID. Use to remove it from the installed list we just got
var hubid = "{27ed8894-9f3b-4bef-89c6-75e25bb6520a}";
 
foreach (var app in installed.Where(d => !d.Id.ProductId.Equals(hubid,StringComparison.OrdinalIgnoreCase)))
{
// get thumbnail token, and copy the file in local folder
var thumbtoken = app.GetThumbnailToken();
var name = SharedStorageAccessManager.GetSharedFileName(thumbtoken);
 
await SharedStorageAccessManager.CopySharedFileAsync(ApplicationData.Current.LocalFolder
, name,NameCollisionOption.ReplaceExisting, thumbtoken);
var file = await ApplicationData.Current.LocalFolder.GetFileAsync(name);
 
var stream = await file.OpenStreamForReadAsync();
 
var bitmap = new BitmapImage();
bitmap.SetSource(stream);
// create a package info and add it to the collection
var packageinfo = new PackageInfo { ID = new Guid(app.Id.ProductId),
Name = app.Id.Name, Icon = bitmap, Package = app };
 
InstalledPackages.Add(packageinfo);
}
 
foreach (var package in allpackages)
{
// if available package is already installed, do nothing
if (installed.Any(d => d.Id.ProductId.Equals(string.Format("{{{0}}}", package.ID.ToString()),
StringComparison.OrdinalIgnoreCase)))
continue;
// otherwise add it the the vailable collection
AvailablePackages.Add(package);
}

I get the available app list from my webservice. I get the installed app list with the FindPackagessForCurrentPublisher method.

For each app installed, I get the thumbnail (icon) and copy it locally. I create a PackageInfo with all the info needed and add it to a list. As the company hub itself will be listed, it is better to remove it from the list

For each packages available, I check if it is already installed ('by comparing product IDs). If not I add. So there will only be in that list the apps that are not installed yet

This is what my available package list look like in XAML :

 <phone:LongListSelector Margin="0,-38,-22,2"
ItemsSource="{Binding AvailablePackages}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid Margin="0,-6,0,12"
d:DataContext="{d:DesignInstance entrepriseService:PackageInfo}"
toolkit:TiltEffect.IsTiltEnabled="True"
Tap="Package_OnTap">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="{Binding Thumbnail}"
Width="80"
Height="80"
Grid.RowSpan="2"
Margin="10"
VerticalAlignment="Top" />
<TextBlock Text="{Binding Name}"
Grid.Column="1"
TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"
FontSize="{StaticResource PhoneFontSizeExtraLarge}" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Text="{Binding Description}"
Visibility="{Binding Downloading,Converter={StaticResource BoolToInvertedVisibilityConverter}}"
Foreground="DarkGray"
TextWrapping="Wrap"
Margin="15,0,0,0" />
<StackPanel Orientation="Vertical"
Grid.Column="1"
Grid.Row="1"
x:Name="IntallStackPanel">
<ProgressBar IsIndeterminate="False"
x:Name="IntallProgressBar"
Value="{Binding Progress}"
Visibility="{Binding Downloading,Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock Text="{Binding ProgressString}" />
</StackPanel>
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>

Just a simple LongListSelector. When you tap on an item, the app is installed :

        // Install app
private void Package_OnTap(object sender, GestureEventArgs e)
{
// Get package info
var package = (sender as Grid).DataContext as PackageInfo;
 
try
{
package.Downloading = true;
// install app package
var result = InstallationManager.AddPackageAsync(package.Name, new Uri(package.XAPPath, UriKind.Absolute));
 
result.Progress += (info, progressInfo) => Dispatcher.BeginInvoke(() =>
{
package.Progress = progressInfo;
package.ProgressString = string.Format("Installing ({0}%)...", progressInfo);
});
 
result.Completed = (info, status) => Dispatcher.BeginInvoke(async () =>
{
MessageBox.Show(info.ErrorCode == null
? string.Format("{0} installed", package.Name)
: string.Format("Error installing : {0}", info.ErrorCode.Message));
package.Downloading = false;
await LoadingPackagesListAsync();
});
 
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
 
}

I use InstallationManager.AddPackageAsync to add the package. It returns a IAsyncOperationWithProgress<PackageInstallResult, UInt32>, so I can follow the progress thanks to the Progress event. I use the Completed delegate to warn user it is installed or if there was an error.

Note 1 : The Progress event will be called 4 times only. At 5% (Phone asking confirmation ot the user), 10% (User accepted), 55% (Download done) and 100% (Installation done)

Note 2 : There is something subtle with Completed : it is a delegate, not an event ! Use = and not +=. If you have weird exception when you’re on that line, it is probably you made the mistake (as I did)

To launch an app is straightforward:

// Lauch app
private void LaunchApp_OnTap(object sender, GestureEventArgs e)
{
var packageInfo = (sender as Grid).DataContext as PackageInfo;
 
var package = packageInfo.Package as Package;
 
package.Launch("");
}

Develop company apps

Nothing special to say. It is just like regular app.

Sign hub and apps

Before you deploy everything, you need to sign the app.

To do so, you have to install the certificate you got from Symantec and export it as PFX (example here : http://msdn.microsoft.com/en-us/library/gg432987.aspx). Then you can generate the AET (Application Enrollment Token) that you will have to send to the users. They have to install it on their phone in order to use your apps. There is an AET generator with the Phone SDK. To launch :

%ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v8.0\Tools\AETGenerator\AETGenerator.exe PFXFile Password

It will generate 3 files. It is the AET.aetx that you send to users. The AET.aet is for MDM systems. The other is raw AET in XML. More info here : http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj735576(v=vs.105).aspx)

Now to sign your apps, there are several ways as explained here : http://msdn.microsoft.com/en-us/library/windowsphone/develop/dn168929(v=vs.105).aspx

The easiest is to use the BuildMDILXap.ps1 that is in the %ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v8.0\Tools\MDILXAPCompile as it does all the steps for you.

I added it in post build that way :

CompanyHub2.jpg

Here is it, for one of the example company app :

del "$(TargetDir)\AriesApp_new.xap"
powershell.exe -ExecutionPolicy ByPass -File "%ProgramFiles(x86)%\Microsoft SDKs\Windows Phone\v8.0\Tools\
MDILXAPCompile\BuildMDILXap.ps1" -xapfilename "$(TargetDir)\AriesApp.xap"
–pfxfilename "$(SolutionDir)\Keys\mycert.pfx" -password mypassword
copy /y "$(TargetDir)\AriesApp_new.xap" "$(SolutionDir)\WcfService1\XAP\AriesApp.xap"

I delete the older version of the signed xap, I execute the script (my keys are stored in a Keys directory) and finally I copy the xap on my service website, where it can be downloaded by the users.

Note : Don’t forget to set your publisher ID in the manifest of the apps. The publisher ID can be generated that way : take your Symantec ID (Example : 541255) . Convert it to hex (0x84247) and create a GUID out of it : 00084247-0000-0000-000000000000)

Create app discovery service

It is not necessarily a service. It can be XML file,etc…

I made a very simple one, it just read, deserialize an XML file and send it :

 public class EntrepriseService : IEntrepriseService
{
public List<PackageInfo> GetEntreprisePackages()
{
// just read the XML file, deserialize it and then send
var appPath = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath;
 
PackageInfo[] packages;
 
using (var sr = new StreamReader(appPath + "/Packages.xml"))
{
var dcs = new XmlSerializer(typeof(PackageInfo[]), new[] { typeof(PackageInfo) });
packages = dcs.Deserialize(sr.BaseStream) as PackageInfo[];
}
 
return packages.ToList();
}
}

The XML is like this :

<?xml version="1.0" encoding="utf-8" ?>
<ArrayOfPackageInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PackageInfo>
<ID>{16823216-c5b1-4613-a50c-9fb9bbddc571}</ID>
<Name>Aries</Name>
<Description>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In aliquam convallis elit, ac aliquam neque vulputate eget. Sed consequat non nibh quis pharetra. </Description>
<Thumbnail>http://192.168.0.110:19475/Thumb/Astro-Aries.png</Thumbnail>
<XAPPath>http://192.168.0.110:19475/XAP/AriesApp.XAP</XAPPath>
</PackageInfo>
<PackageInfo>
<ID>{52957e14-91ef-485d-9fb4-e69086facb6e}</ID>
<Name>Taurus</Name>
<Description>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In aliquam convallis elit, ac aliquam neque vulputate eget. Sed consequat non nibh quis pharetra. </Description>
<Thumbnail>http://192.168.0.110:19475/Thumb/Astro-Taurus.png</Thumbnail>
<XAPPath>http://192.168.0.110:19475/XAP/TaurusApp.XAP</XAPPath>
</PackageInfo>
</ArrayOfPackageInfo>

Send the certificate and the company app to users

Send the keys and the app to the user by email. Or have them download it from a website. Anyway, do it securely.

To install the AET, just double click on it.

Conclusion

Well, side loading company app is way easier than with Windows 8. In the sense you don’t need extra license, or specific version of Windows (Entreprise),... It is a complain I heard a lot from companies who are interested in Windows side loading. I hope for Windows 9, the system will get inspired by the WP team one.

HubExample.png

File:MyCompanyHub.zip

The "platform categories" will be displayed here in preview only - Copy paste relevant categories into text here

Version Hint

Windows Phone: [[Category:Windows Phone]]
[[Category:Windows Phone 7.5]]
[[Category:Windows Phone 8]]

Nokia Asha: [[Category:Nokia Asha]]
[[Category:Nokia Asha Platform 1.0]]

Series 40: [[Category:Series 40]]
[[Category:Series 40 1st Edition]] [[Category:Series 40 2nd Edition]]
[[Category:Series 40 3rd Edition (initial release)]] [[Category:Series 40 3rd Edition FP1]] [[Category:Series 40 3rd Edition FP2]]
[[Category:Series 40 5th Edition (initial release)]] [[Category:Series 40 5th Edition FP1]]
[[Category:Series 40 6th Edition (initial release)]] [[Category:Series 40 6th Edition FP1]] [[Category:Series 40 Developer Platform 1.0]] [[Category:Series 40 Developer Platform 1.1]] [[Category:Series 40 Developer Platform 2.0]]

Symbian: [[Category:Symbian]]
[[Category:S60 1st Edition]] [[Category:S60 2nd Edition (initial release)]] [[Category:S60 2nd Edition FP1]] [[Category:S60 2nd Edition FP2]] [[Category:S60 2nd Edition FP3]]
[[Category:S60 3rd Edition (initial release)]] [[Category:S60 3rd Edition FP1]] [[Category:S60 3rd Edition FP2]]
[[Category:S60 5th Edition]]
[[Category:Symbian^3]] [[Category:Symbian Anna]] [[Category:Nokia Belle]]

Add categories below using category selector.

131 page views in the last 30 days.