Jakub Florczyk - Blog o programowaniu .NET

Programista praktyczny

Tag: API

Demotywatory na Windows Phone 1.2

Demotywatory na Windows Phone zaktualizowane do wersji 1.2.

Zmiany w wersji 1.2 obejmują drobne poprawki ikon, optymalizacji kodu oraz obsługi błędów. Ale najważniejsza zmiana dotyczy publikacji aplikacji na rynek międzynarodowy. A więc Polacy na całym świecie od kilku dni mogą się cieszyć demotami, niezależnie od lokalizacji jaką macie ustawioną w telefonie.

Demotywatory na Windows Phone

Moja kolejna aplikacja została pomyślnie certyfikowana w marketplace. Tym razem jest to apka tylko na rynek polski. A żeby tradycji stało się zadość jest to oczywiście klient Demotywatory.pl.

Oprogramowanie pozwala na przeglądanie strony głównej, losowanie, dodawanie demotów i ocenianie, czytanie oraz dodawanie komentarzy. Każdego demota można zapisać w albumie telefonu albo wysłać e-mailem. Czyli pełen klient i do tego (jak zwykle w przypadku demotów) zupełnie za darmo.

Poniżej screeny z aplikacji:

Windows Phone 7 Series – API kamery

API kamery w narzędziach CTP dla Windows Phone 7 Series nie zostało oficjalnie opublikowane i jeszcze nie możemy z niego korzystać. Jednakże przeglądając dump-a emulatora WP7S znalazłem plik o nazwie GAC_CaptureApiManaged_v1_0_0_0_cneutral_1.dll (znajduje się w ścieżce \SYS\MediaApps).

Szybki przegląd Reflector-em ukazuje nam klasy od obsługi kamery. Próba podpięcia referencji w projekcie i odpalenie kończy się niepowodzeniem, jednak zainteresowani mogą sobie przejrzeć jak będzie można obsługiwać kamerę.

Obiekty alfabetycznie:

// nazwa mówi za siebie
public class AutoFocusCompletedEventArgs : EventArgs
{
    // Methods
    public AutoFocusCompletedEventArgs(bool succeeded);

    // Properties
    public bool Succeeded { get; internal set; }
}

// bazowa klasa kamery
public abstract class Camera : IDisposable
{
...
}

// j.w. (fajnie że będziemy mieli dostęp do obydwu kamer - spodziewajmy się masy "lusterek" w Marketplace)
public enum CameraSource
{
    PrimaryCamera,
    SelfPortraitCamera
}

// jedna z ciekawszych klas, ponieważ wynika z niej że nie będziemy obsługiwać kamery w Silverlight w taki sposób jak to się robi w wersji pełnej - enumerując urządzenia i "rysując" VideoBrush a uzyskamy dostęp do MediaElement, który będzie wyświetlał kolejne klatki
public class CameraVisualizer : UserControl
{
    // Fields
    private Camera cameraSource;
    private MediaElement displayMediaElement;
    private Binding heightBinding;
    private bool isLoaded;
    private Binding widthBinding;

    // Methods
    [SecuritySafeCritical]
    public CameraVisualizer();
    public void OnLoaded(object sender, EventArgs ea);
    public void SetSource(Camera camera);
}

// tutaj także wszystko jasne
[StructLayout(LayoutKind.Sequential)]
public struct CaptureResolution
{
    public int Width { get; internal set; }
    public int Height { get; internal set; }
}

// klasa dostępności danych
public class ContentReadyEventArgs : EventArgs
{
    // Methods
    public ContentReadyEventArgs(string relativePath);

    // Properties
    public string RelativePath { get; internal set; }
}

// no proszę nawet o fleszu nie zapomnieli
public enum FlashMode
{
    Off,
    On,
    Auto,
    RedEyeReduction
}

// ogólny błąd zgłaszany w przypadku wyjątków kamery
public class OperationFailedEventArgs : EventArgs
{
    // Methods
    public OperationFailedEventArgs(Exception exception);

    // Properties
    public Exception Exception { get; internal set; }
}

// i teraz najciekawsze dwie klasy
public sealed class StillCamera : Camera
{
    // Fields
    private Guid _captureId;
    private bool _isCapturing;
    private object _lock;
    private EventHandler ImageAcquired;
    private EventHandler ImageSavedToDisk;
    private EventHandler ThumbnailSavedToDisk;

    // Events
    public event EventHandler ImageAcquired;
    public event EventHandler ImageSavedToDisk;
    public event EventHandler ThumbnailSavedToDisk;

    // Methods
    public StillCamera();
    public StillCamera(CameraSource source);
    public void CaptureImage();
    internal override void RouteEvent(CaptureEvent eventId);

    // Properties
    public FlashMode FlashMode { get; set; }
}

public sealed class VideoCamera : Camera
{
    // Fields
    private object _lock;
    private Guid _recordingId;
    private EventHandler RecordingStarted;
    private EventHandler RecordingStopped;
    private EventHandler ThumbnailSavedToDisk;

    // Events
    public event EventHandler RecordingStarted;
    public event EventHandler RecordingStopped;
    public event EventHandler ThumbnailSavedToDisk;

    // Methods
    public VideoCamera();
    public VideoCamera(CameraSource source);
    internal override void RouteEvent(CaptureEvent eventId);
    public void StartRecording();
    public void StopRecording();

    // Properties
    public bool IsRecording { get; private set; }
    public bool LampEnabled { get; set; }
}

Wnioski:

  • do osadzenia podglądu kamery w silverlight należy użyć CameraVisualizer który dostarczy nam widok taki jak dostarcza obecny CameraCaptureDialog z Microsoft.WindowsMobile.Forms
  • do pobrania pojedynczej klatki należy użyć StillCamera i podpiąć się pod event ImageSavedToDisk – co ciekawe nie działa to tak jak opisywano to na MIX2010, że uzyskamy dane klatki, tylko musimy je sami odczytać z pamięci urządzenia; odrobinę jest to niespójne skoro do pamięci nie możemy się dostać!
  • do nagrania należy użyć VideoCamera który podobnie jak StillCamera zapisze nam odpowiedni w tym przypadku film na dysku

Wszystko ładnie i pięknie ale brakuje mi dostępu do czystych klatek z kamery. A nie wyobrażam sobie dobrej aplikacji Augmented Reality bez takowego dostępu. Póki co jednak jest to wersja CTP i oby ta biblioteka uległa zmianie.

BlipiNET 2.0

Marek Foss od Blipi.pl zaktualizował dziś API do wersji 2.0 wprowadzając kilka ulepszeń. Co za tym idzie zaktualizowałem także BlipiNET.

Zmiany w najnowszej wersji:

  • klucz API wymagany jest tylko do wyszukiwania, reszta metod go nie używa
  • usunięte zostały metody GetTrackedBy oraz GetRank
  • powyższe metody zastąpiła metoda GetStats która zwraca pełne statystyki użytkownika: liczba obserwujących, zmiana liczby obserwujących, liczba obserwowanych, zmiana liczby obserwowanych, liczba cytowań, liczba wzmianek, ranking i zmiana rankingu

Ponieważ aktualnie pracuje nad zmianami w Pocket Blip już można zobaczyć roboczą wersję widoku profilu użytkownika:

Marek na Blipie zdradził także, że pracuje nad udostępnieniem trendów w API.

SamsungMobileSDKNET

Żeby nie było, iż jestem jednostronny to dla odmiany dziś o firmie Samsung. I o jego w moim mniemaniu cudownym dziecku Samsung Windows Mobile SDK.

Na początek małe wytknięcie pięty achillesowej firmy HTC. Otóż mimo iż produkują świetne telefony, z najlepszymy nakładkami i oprogramowaniem, to ogromnym minusem jest brak jakiegokolwiek SDK dla bibliotek HTC.
Oczywiście podstawową część obsługi telefonu możemy załatwić standardowymi bibliotekami .NET / Tapi/ ExTapi / RIL ale często te najsmaczniejsze kąski siedzą w warstwach do których HTC nas nie chce dopuścić. I do dziś się zastanawiam dlaczego.
O tyle dobrze, że niektórym udało się dostać to obsługi akcelerometru i LED-ów HTC o tyle kamera np. nadal jest jedną wielką zagadką i dostęp do surowych danych a nie ubranych w jakieś okienka Microsoftu.

Z drugiej strony pojawia się firma Samsung i udostępnia pełne SDK do funkcji telefonu. Pisząc pełne, mam na myśli: akcelerometr, kamerę, lampę kamery, radio, led, haptics, czujnik światła, mysz optyczną, orientację 2D i 3D, czujnik zbliżeniowy, procesor audio, slider-a, wyjście TV, wibrację i kółko nawigacji. Jeżeli jeszcze ci szczęka nie opadła, to jest właśnie dobry moment.

Samsung całość udostępnił jako SDK, które można pobrać tutaj. Biblioteki są przygotowane pod C++ z jednym przykładem natywnym.

Jako, że nie mogłem znaleźć wrappera z pełną implementacją, postanowiłem go napisać samemu. Biblioteka dostępna jest na CodePlex SamsungMobileSDKNET. Na obecną chwilę zawiera pełną implementację wersji 2.1. Ale ponieważ nie posiadam telefonu Samsunga, żaden element nie jest przetestowany. Dlatego prośba dla programistów z telefonami Samsunga o przetestowanie poszczególnych elementów. No chyba, że wcześniej z Orange-a wezmę Samsunga Omnia II – bo ostatnio mi proponowali nawet w rozsądnej cenie i sam przetestuje poszczególne elementy.

RilNET – Radio Interface Layer (RIL) .NET wrapper – sprostowanie

A propos artykułu RilNET – Radio Interface Layer (RIL) .NET wrapper należy się drobne sprostowanie działania biblioteki a przede wszystkim obsługi lokalizacji.

Otóż po wielu testach na telefonach firm HP i HTC oraz po przeczytaniu kilku artykułów w sieci wszystkim osobom, które używają RIL-a należy się małe wyjaśnienie o którym nie zdawałem sobie sprawy w momencie pisania wpisu.

Otóż implementacja biblioteki RIL zależy od producenta telefonu. Sprowadza się to do tego, iż część funkcji może nie być w ogóle zaimplementowana!

W praktyce wygląda to tak, iż mój wysłużony HP 614C nie posiadał zaimplementowanej metody udostępniającej informację o komórce sieci  GetCellTowerInfo przez co w artykule błędnie zasugerowałem, że to pewnie wina sieci Orange.
Po zamianie i testach na HP Touch Pro2 wszystko działa poprawnie. Funkcja zwraca informacje o sieci!

Oczywiście można sobie zadać pytanie: no i co z tego? Otóż wbrew pozorom bardzo wiele. Chociażby to, że Google Maps potrafi obsługiwać RIL-a i jest w stanie określić waszą przybliżoną pozycję na podstawie wieży komórkowej.

Podsumowując. Jest to kolejny powód aby kupować telefony poważnych firm. Choć HP kiedyś bardzo się starało w sprawie PocketPC, obecnie daleko im do czołówki i standardów króla HTC.

BlipiNET

W nowej wersji Pocket Blip pracuję nad zmianą dostawcy wyszukiwarki oraz statystykami użytkowników. Wybór dostawcy padł na Blipi Marka Fossa, ponieważ poza wyszukiwarką Marek udostępnia też ranking i licznik obserwujących. Z tego powodu powodu popełniłem bibliotekę dostępową .NET do API. Projekt można znaleźć na CodePlex BlipiNET.

Jak w przypadku innych bibliotek zdecydowałem się na “toporne” metody HttpWebRequest ze względu na brak nowych rozwiązań a’la WCF w wersji Json w Compact Framework.

Przykłady użycia:

// Wyszukiwanie wiadomości
BlipiService bs = new BlipiService("[twój klucz API]");
Message[] messages = bs.Search("jakubflorczyk");
// Ranking (Top100)
BlipiService bs = new BlipiService("[twój klucz API]");
User userRank = bs.GetRank("jakubflorczyk");

Dostępna jest pełna implementacja aktualnego API:
- wyszukiwanie
- pobieranie rankingu użytkownika
- pobieranie licznika obserwujących użytkownika
- pobieranie statystyk Top 10 i 100

FlakerNET 2.0

Zaktualizowałem FlakerNET do ostatnich zmian API wprowadzonych przez twórców Flakera.

Nowe funkcjonalności to m.in.:
- pobieranie obserwowanych
- pobieranie obserwujących
- pobieranie wiadomości obserwowanych tagów
- pobieranie wiadomości przyjaciół
- wyszukiwanie
- itp.

FlakerNET 1.0

Powiedziałem “a” trzeba powiedzieć “b” i po BlipNet popełniłem bibliotekę dostępową do API Flakera. Projekt można znaleźć na CodePlex FlakerNET.

Jak w przypadku BlipNet zdecydowałem się na “toporne” metody HttpWebRequest ze względu na brak nowych rozwiązań a’la WCF w wersji Json w Compact Framework.

Przykłady użycia:

// Flakosfera
Entry[] entires = new FlakerService().GetFlakosphere();
// Dodanie wpisu
new FlakerService("login", "password").AddEntry("Hello world!");

Dostępna jest pełna implementacja aktualnego API, m.in.:
- pobieranie flakosfery
- dodawanie wiadomości z linkami i zdjęciami
- pobieranie użytkowników
- pobierania przyjaciół
- pobieranie ulubionych wpisów
- sprawdzanie autoryzacji
- dodawanie / usuwanie ulubionych
- traker
- itd.

RilNET – Radio Interface Layer (RIL) .NET wrapper

Długo się zanosiłem z wrapperem na RIL-a i zawsze mi brakowało czasu, ale może od początku…

Dla niezorientowanych Radio Interface Layer jest warstwą łączącą hardware telefonu z oprogramowaniem. Pisząc hardware mam na myśli część telefonu Windows Mobile odpowiedzialną za wykonywanie połączeń i transfer danych do stacji komórkowych (Radio). Z punktu widzenia programisty nic poniżej RIL-a już nie ma poza samym sprzętem. Więcej o samym systemie można poczytać na http://msdn.microsoft.com/en-us/library/aa920475.aspx.

Poniżej schemat architektury w Windows Mobile (RIL występuje także w Windows CE):

RIL WM Architecture

Jeżeli czytasz ten tekst pewnie zadasz pytanie “po co to komu?”. Z punktu widzenia CF mamy dostępne wrappery Microsoftu pozwalające na wykonywanie połączeń, wysyłanie SMS-ów, obsługę książki telefonicznej itp. Z punktu widzenia telefonu posiadamy jeszcze Tapi oraz ExTapi. A więc po co?

Otóż tak jak napisałem wcześniej RIL jest najniższą warstwą a więc pozwala na rzeczy na które wyższe warstwy nigdy nie zezwolą. Przy użyciu RIL-a możemy pobrać informację o  wieży komórkowej, możemy zarządzać kartą SIM, możemy wysyłać komendy AT, itp.

RIL pozwala na dwa sposoby odwołań:

  • Podłączenie się do systemu notyfikacji, przez co dostaniemy każde zdarzenie które wygeneruje Radio
  • Wysyłanie zapytań do systemu, przez co możemy uzyskać dowolną informację z Radio

Poniżej prosty przykład użycia:

int hrCo, hrCti, hrEi;

int hr = Ril.Initialize(1, new RILRESULTCALLBACK(RilResultCallback), new RILNOTIFYCALLBACK(RilNotifyCallback), RIL_NCLASS.ALL, 0, out hRil);

// you can bind only to RilResultCallback
// int hr = Ril.Initialize(1, new RILRESULTCALLBACK(RilResultCallback), null, 0, 0, out hRil);
// or only to or RilNotifyCallback
// int hr = Ril.Initialize(1, null, new RILNOTIFYCALLBACK(RilNotifyCallback), RIL_NCLASS.ALL, 0, out hRil);

hrCti = Ril.GetCellTowerInfo(hRil);
hrCo = Ril.GetCurrentOperator(hRil, RIL_OPFORMAT.LONG);
hrEi = Ril.GetEquipmentInfo(hRil);
hr = Ril.Deinitialize(hRil);

// after call some method remember to save returned HRESULT and compare it to hrCmdID in RilResultCallback

private void RilResultCallback(
    uint dwCode,
    int hrCmdID,
    IntPtr lpData,
    uint cbData,
    uint dwParam)
{
    if (hrCo == hrCmdID)
    {
        //Ril.GetCurrentOperator
        RILOPERATORNAMES pOperatorNames = (RILOPERATORNAMES)Marshal.PtrToStructure(lpData, typeof(RILOPERATORNAMES));

        if ((pOperatorNames.dwParams & RIL_PARAM_ON.LONGNAME) == RIL_PARAM_ON.LONGNAME) // check that LongName member is valid
        {
            string longName = Encoding.ASCII.GetString(pOperatorNames.szLongName, 0, pOperatorNames.szLongName.Length).Replace("\0", "");
        }
    }

    if (hrCti == hrCmdID)
    {
        //Ril.GetCellTowerInfo
        RILCELLTOWERINFO pCellTowerInfo = (RILCELLTOWERINFO)Marshal.PtrToStructure(lpData, typeof(RILCELLTOWERINFO));
    }

    if (hrEi == hrCmdID)
    {
        //Ril.GetEquipmentInfo
        RILEQUIPMENTINFO pEquipmentInfo = (RILEQUIPMENTINFO)Marshal.PtrToStructure(lpData, typeof(RILEQUIPMENTINFO));

        if ((pEquipmentInfo.dwParams & RIL_PARAM_EI.MANUFACTURER) == RIL_PARAM_EI.MANUFACTURER)
        {
            string manufacturer = Encoding.ASCII.GetString(pEquipmentInfo.szManufacturer, 0, pEquipmentInfo.szManufacturer.Length).Replace("\0", "");
        }

        if ((pEquipmentInfo.dwParams & RIL_PARAM_EI.MODEL) == RIL_PARAM_EI.MODEL)
        {
            string model = Encoding.ASCII.GetString(pEquipmentInfo.szModel, 0, pEquipmentInfo.szModel.Length).Replace("\0", "");
        }

        if ((pEquipmentInfo.dwParams & RIL_PARAM_EI.REVISION) == RIL_PARAM_EI.REVISION)
        {
            string revision = Encoding.ASCII.GetString(pEquipmentInfo.szRevision, 0, pEquipmentInfo.szRevision.Length).Replace("\0", "");
        }

        if ((pEquipmentInfo.dwParams & RIL_PARAM_EI.SERIALNUMBER) == RIL_PARAM_EI.SERIALNUMBER)
        {
            string serialNumber = Encoding.ASCII.GetString(pEquipmentInfo.szSerialNumber, 0, pEquipmentInfo.szSerialNumber.Length).Replace("\0", "");
        }
    }
}

public void RilNotifyCallback(
    uint dwCode,
    IntPtr lpData,
    uint cbData,
    uint dwParam)
{
    RIL_NCLASS dwClass = ((RIL_NCLASS)dwCode & RIL_NCLASS.ALL);

    Debug.WriteLine("NotifyCallback: " + dwClass.ToString());

    switch ((RIL_NOTIFY_RADIOSTATE)dwCode)
    {
        case RIL_NOTIFY_RADIOSTATE.RADIOEQUIPMENTSTATECHANGED:
        {
            RILEQUIPMENTSTATE pState = (RILEQUIPMENTSTATE)Marshal.PtrToStructure(lpData, typeof(RILEQUIPMENTSTATE));

            Debug.WriteLine(String.Format("Radio Support: {0}; equipment State: {1}; ready State: {2}",
                           pState.dwRadioSupport, pState.dwEqState, pState.dwReadyState));

            break;
        }
        case RIL_NOTIFY_RADIOSTATE.RADIOPRESENCECHANGED:
        {
            RIL_RADIOPRESENCE dwPresence = (RIL_RADIOPRESENCE)Marshal.ReadInt32(lpData);

            switch (dwPresence)
            {
                case RIL_RADIOPRESENCE.NOTPRESENT:
                    Debug.WriteLine("Radio module is not present");
                    break;
                case RIL_RADIOPRESENCE.PRESENT:
                    Debug.WriteLine("Radio module is present");
                    break;
            }

            break;
        }
    }
}

W przypadku gdy podłączamy się do notyfikacji musimy określić jakie notyfikacje nas interesują oraz musimy zdefiniować funkcję która będzie je odbierać. Po odebraniu zdarzenia musimy przy pomocy enumeratorów “rozwiązać” typ zdarzenia oraz zinterpretować dane. System jest tak zorganizowany, że z danymi przychodzi wskaźnik który w większości przypadków wskazuje na strukturę w której dodatkowo są zapisane informacje przy pomocy “flagowego” enumeratora o poprawności poszczególnych pól w tej strukturze.

W przypadku gdy wysyłamy zapytania po odwołaniu do funkcji musimy zapamiętać uchwyt, który funkcja zwraca a następnie w metodzie obsługującej musimy go porównać z uchwytem który uzyskujemy od urządzenia. Po tej operacji wskaźnik możemy skonwertować do odpowiedniego typu danych i podobnie jak poprzednio, odpowiednie pole wskazuje poprawność poszczególnych pól w strukturze.

Dla wszystkich osób zainteresowanych określaniem pozycji według stacji nadawczych mam złe wiadomości. Otóż w mojej sieci Orange, system zwraca błąd o braku wsparcia ze strony sieci. Może to wina miejsca w którym mieszkam a może tak jest wszędzie. Jak przetestuje to w kilku lokalizacjach to udostępnie podsumowanie.

Projekt dostępny jest oczywiście na CodePlex RilNET