czwartek, 18 września 2014

Pojedynek z Androidem - odc.8 Android numer 4 (kalendarz)

Oficjalne API do kalendarza to była kolejna istotna nowość Android 4.0.  Windows Phone 7.5 miał zdaje się po raz pierwszy obsługę takich rzeczy, też za pomocą zapytań, aczkolwiek jakoś bardziej wygodnych. Windows i Windows Phone 8.1 funcjonalności odnośnie kalendarza mają podobne, czyli dodawanie, edycja, usuwanie, listowanie itp. Też możemy wykonywać operacje za pomocą systemowego UI.  Możliwość definiowania własnych kalendarzy również została wprowadzona.   Wracając do Androida to jego intencje dla kalendarza to jakby odpowiednik systemowych tasków. Content provider z operacjami na tabelkach to znacznie bardziej złożone rozwiązanie, ale ujdzie. Nie rozumiem tylko po co obnażanie wnętrzności i danie możliwości znalezienia eventów, które ktoś usunął, ale nie zostały jeszcze fizycznie wyczyszczone.

 

Przed Android 4.0 nie było oficjalnego API  - przez standardowy content provider trzeba było używać niskopoziomowych danych

Android 4.0

  • Klasa CalendarContract dla ułatwienia dostępu
  • Wiele często używanych operacji za pomocą intencji
  • Bezpośrednie programowanie danych kalendarza nadal wymaga content providera (CalendarContract upraszcza korzystanie z niego)

CalendarContract

centralny punkt dla wszystkich stałych kalendarza

  • definiuje uri identyfikujące dane powiązane z kalendarzem
    • używane przez klucz-wartość intencji
    • nazwy tabel dla content providera
  • definiuje nazwy dla elementów powiązanych z kalendarzem
    • identyfikuje extras intencji
    • identyfikuje kolumny tabel

Intencje kalendarza

  • zapewniają najprostszy dostęp do kalendarza
  • zalety w stosunku do korzystania z content providera kalendarza
    • prezentują użytkownikowi standardowy interfejs kalendarza  (startActivity z odpowiednim intent)
    • nie wymagają specjalnych pozwoleń
    • nie trzeba znać szczegółów storage
  • kilka ograniczeń
    • wymagają interakcji użytkownika do zakończenia akcji
    • nie dostarczają informacji z powrotem do naszej aplikacji

Otwieranie kalendarza z podanym czasem i datą

  • akcja:  Intent.ACTION_VIEW
  • dane:  CalendarContract.CONTENT_URI (dodajemy time w ms od północy 1 stycznia 1970)
    • content://com.android.calendar/time/1334168996092

Calendar dateToShow = Calendar.getInstance();

dateToShow.set(2014, Calendar.SEPTEMBER, 17, 8, 00);

 

long ms = dateToShow.getTimeInMillis();

 

Uri.Builder uriBuilder = CalendarContract.CONTENT_URI.buildUpon();

uriBuilder.appendPath(“time”);

ContentUris.appendId(uriBuilder,  ms);

Uri uri = uriBuilder.build();

 

Intent intent = new Intent(Intent.ACTION_VIEW);

intent.setData(uri);

 

startActivity(intent);

Przeglądanie i modyfikacja eventów z intencjami

  • dane:  CalendarContract.Events.CONTENT_URI  (dołączamy event id)
    • content://com.android.calendar/events/125
  • oglądanie istniejącego eventu:  akcja = Intent.ACTION_VIEW
  • edycja istniejącego eventu:  akcja = Intent.ACTION_EDIT

Uri uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI,  eventID);

Intent intent = new Intent(Intent.ACTION_EDIT).setData(uri);

startActivity(intent);

 

Tworzenie nowych eventów

Calendar begin = Calendar.getInstance();

begin.set(2014,  Calendar.SEPTEMBER,  17,  8, 0);

Calendar end = Calendar.getInstance();

end.set(2014,  Calendar.NOVEMBER, 20, 20, 0);

 

Intent intent = new Intent(Intent.ACTION_INSERT).setData(CalendarContract.Events.CONTENT_URI);

intent.putExtra(CalendarContract.Events.TITLE,  title);

intent.putExtra(CalendarContract.Events.EVENT_LOCATION,  location);

intent.putExtra(CalendarContract.Events.DESCRIPTION,  description);

intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,  begin.getTimeInMillis());

intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME,  end.getTimeInMillis());

startActivity(intent);

Calendar content provider

Najbardziej bezpośrednia kontrola nad danymi kalendarza

  • zalety w stosunku do intencji
    • dostęp do wszystkich danych kalendarza
    • dostęp do programów i modyfikacja danych bez interakcji z użytkownikiem
  • wyzwania
    • deklaracja przez naszą aplikację specyficznych dla kalendarza uprawnień

Uprawnienia content providera kalendarza

  • odczytywanie zawartości:   “android.permission.READ_CALENDAR”
  • zapisywanie zawartości:   “android.permission.WRITE_CALENDAR”  (prawo do zapisu nie oznacza prawa do odczytu)

Główne klasy CalendarContract

  • CalendarContract.Calendars - wszystkie kalendarze
  • CalendarContract.Events - wszystkie eventy
  • CalendarContract.Instances - czas rozpoczęcia i zakończenia każdego wydarzenia
  • CalendarContract.Attendees - osoby uczestniczące w każdym wydarzeniu
  • CalendarContract.Reminders - przypominanie o eventach

Calendars –> Events –> Instances,  Attendees,  Reminders

  • Calendars:  CalendarContract.Calendars.CONTENT_URI
  • Events: CalendarContract.Events.CONTENT_URI,   CalendarContract.Events.CALENDAR_ID
  • Instances: CalendarContract.Instances.CONTENT_URI,  CalendarContract.Instances.EVENT_ID
  • Attendees:  CalendarContract.Attendees.CONTENT_URI,  CalendarContract.Attendees.EVENT_ID
  • Reminders:  CalendarContract.Reminders.CONTENT_URI,   CalendarContract.Reminders.EVENT_ID

Tabela kalendarzy

Lista wszystkich kalendarzy na urządzeniu

  • unikalne ID dla każdego kalendarza:   CONTENT_URI & _ID
  • możliwość wyszukiwania za pomocą ContentResolver.query
  • wyszukiwanie konkretnego kalendarza wymaga kombinacji trzech pól
    • CalendarContract.Calendars.ACCOUNT_NAME   xxx@gmail.com
    • CalendarContract.Calendars.ACCOUNT_TYPE
      • “com.google”  - konta synchronizowane przez Google Calendar
      • CalendarContract.Calendars.ACCOUNT_TYPE_LOCAL - dla kalendarzy niesynchronizowanych
    • CalendarContract.Calendars.NAME - nazwa kalendarza, kiedy został utworzony

long calendarID = 1;

Uri uri = ContentUris.withAppendedId(CalendarContract.CONTENT_URI, calendarID);

Listowanie kalendarzy

String[] returnColumns = new String[]  {

       CalendarContract.Calendars._ID,

       CalendarContract.Calendars.ACCOUNT_NAME,

       CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,

       CalendarContract.Calendars.ACCOUNT_TYPE

};

 

Cursor cursor = null;

ContentResolver cr = getContentResolver();

 

cursor = cr.query(CalendarContract.Calendars.CONTENT_URI,  returnColumns,  null, null, null);

 

while (cursor.moveToNext())  {

        long calID = cursor.getLong(0);

        String displayName = cursor.getString(1);

        String accountName = cursor.getString(2);

        String accountType = cursor.getString(3);

        …

}

       cursor.close();

Znalezienie kalendarza

String[] returnColumns = new String[] {

         CalendarContract.Calendars._ID

};

 

String selection = “((“  + CalendarContract.Calendars.ACCOUNT_NAME + “  =  ?)  AND  ”  +

              “(“   + CalendarContract.Calendars.ACCOUNT_TYPE  + “  =  ?)   AND  ”  +

              “(“   + CalendarContract.Calendars.CALENDAR_DISPLAY_NAME   +  “  = ?))”;

String[]  selectionArgs = {accountName,  accountType,  displayName};

 

Cursor cursor = null;

ContentResolver cr = getContentResolver();

 

cursor = cr.query(CalendarContract.Calendars.CONTENT_URI,  returnColumns,  selection,  selectionArgs, null);

 

long calendarID = –1;

 

if (cursor.moveToNext())

          calendarID = cursor.getLong(0);

 

return calendarID;

Tabela zdarzeń

Lista wszystkich zdarzeń dla wszystkich kalendarzy

  • Identyfikacja zdarzeń
    • bezpośredni dostęp przez unikalne _ID
    • powiązanie z kalendarzem za pomocą kolumny CALENDAR_ID
  • Wyszukiwanie przez ContentResolver.query
  • Ważne kolumny
    • TITLE
    • EVENT_LOCATION
    • DESCRIPTION
    • DTSTART:  czas rozpoczęcia  (ms od północy 1 stycznia 1970)
    • DTEND: czas zakończenia  (-//-)
    • EVENT_TIMEZONE:  nazwa strefy czasowej miejsca, gdzie odbywa się zdarzenie np. US/Eastern  (lista wszystkich dostępnych – TimeZone.getAvailableIDs())

Dodawanie nowych zdarzeń

Dodawanie za pomocą ContentResolver.insert

  • Wartości dostarczone w tablicy ContentValues
  • Wymagane kolumny
    • CALENDER_ID
    • DTSTART
    • DTEND  (dla niecyklicznych)
    • EVENT_TIMEZONE
  • zwracane jest URI nowego zdarzenia (można wyciągnąć id zdarzenia przy pomocy ContentUris.parseId)

Modyfikacja i usuwanie zdarzeń

  • Modyfikacja za pomocą ContentResolver.update
    • idetyfikacja zdarzeń do update’u przy użyciu event URI lub query
    • specyfikacja kolumn do zmiany i ich wartości przy użyciu klasy ContentValues
  • Usuwamy za pomocą ContentResolver.delete
    • identyfikacja zdarzeń do zmiany przy użyciu event URI lub query
    • skasowane zdarzenia mogą nie być natychmiast usunięte
      • zdarzenie nie jest usuwane dopóki urządzenie nie zsynchronizuje się z serwerem
      • należy sprawdzić, czy kolumna DELETED ma wartość 0, aby być pewnym że zdarzenie jest nadal poprawne

Aktualizacja zdarzenia

ContentResolver cr = getContentResolver();

 

Uri uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID);

 

ContentValues values = new ContentValues();

values.put(CalendarContract.Events.EVENT_LOCATION,  location);

values.put(CalendarContract.Events.DESCRIPTION,  description);

 

int rows = 0;

       rows = cr.update(uri,  values,  null, null);

Usuwanie zdarzenia

ContentResolver cr = getContentResolver();

 

Uri uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI,  eventID);

 

int rows = 0;

      rows = cr.delete(uri, null, null);

Sprawdzenie czy event został naprawdę usunięty

String[] EVENT_PROJECTION = new String[] {

        CalendarContract.Events._ID,

        CalendarContract.Events.TITLE,

        CalendarContract.Events.DELETED

};

 

String queryFilter = CalendarContract.Events.DELETED  +  “  =  ?”;

String[]  queryFilterValues = {“0”};

 

Cursor cur = null;

ContentResolver cr = getContentResolver();

Uri uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI,  eventID);

cur  = cr.query(uri,  EVENT_PROJECTION,  queryFilter,  queryFilterValues,  null);

 

if (cur.moveToNext())  {

          long returnedEventID = cur.getLong(0);

          String title = cur.getString(1);

          int isDeleted = cur.getInt(2);

          …

}

Uczestnicy eventów

Tabela Attendees

  • Uczestnika dodajemy do wydarzenia przy użyciu ContentResolver.insert (dla każdego uczestnika musimy podać prawidłowe event id)
  • Ważne kolumny
    • ATTENDEE_NAME
    • ATTENDEE_EMAIL
    • ATTENDEE_RELATIONSHIP (relacja do użytkownika,  stałe Attendees.RELATIONSHIP_xxx:  Attendee, Organizer, Performer, Speaker)
    • ATTENDEE_TYPE (stałe Attendees.TYPE_xxx: Optional, Required, Resource,  dozwolone typy Calendars.ALLOWED_ATTENDEE_TYPES)
    • ATTENDEE_STATUS  (stałe Attendees.STATUS_xxx:  Accepted, Declined, Invited, Tentative)

Przypominanie o wydarzeniach

  • Dodajemy przypomnienie do spotkania używając ContentResolver.insert
    • trzeba podać poprawne event id
    • limit na kalendarz:  Calendars.MAX_REMINDERS
  • Ważne kolumny
    • METHOD: typ (stałe Reminders.METHOD_xxx:  Alert, Default, Email, SMS)
    • MINUTES:  minuty do rozpoczęcia alarmu

Zdarzenia cykliczne

Informacje o cykliczności w tabeli Instances

  • Kolumny dla dat/czasu cyklów eventów
  • Zapytania zawsze muszą mieć podany przedział czasu (dołączamy czas początkowy i końcowy w CONTENT_URI)
  • Widok na wystąpienia eventu
    • Tabela może być tylko odpytywana
    • Nie można utworzyć powtórzeń

Uri.Builder uriBuilder = CalendarContract.Instances.CONTENT_URI.buildUpon();

 

ContentUris.appendedId(uriBuilder, startDate.getTimeInMillis());

ContentUris.appendedId(uriBuilder, endDate.getTimeInMillis());

 

return uriBuilder.build();

Tworzenie cyklicznych eventów

Cykliczność jest cechą zdarzenia

ContentResolver cr = getContentResolver();

ContentValues values = new ContentValues();

values.put(CalendarContract.Events.CALENDAR_ID, calendarID);

values.put(CalendarContract.Events.TITLE, title);

values.put(CalendarContract.Events.DESCRIPTION, description);

values.put(CalendarContract.Events.EVENT_LOCATION, location);

values.put(CalendarContract.Events.DTSTART, startDate.getTimeInMillis());

values.put(CalendarContract.Events.EVENT_TIMEZONE, “US/Eastern”);

values.put(CalendarContract.Events.DURATION, duration);  //”PT1H”

values.put(CalendarContract.Events.RRULE, rRule);  //”FREQ=WEEKLY;COUNT=4”

 

Uri uri = cr.insert(CalendarContract.Events.CONTENT_URI, values);

long eventID = ContentUris.parseId(uri); 

Brak komentarzy: