Pull to refresh

Android Calendar API

Reading time5 min
Views43K

В этой статье речь пойдет об использовании недокументированного API для работы со стандартным календарем Android. Статей на русском по данной теме не нашел, да и на английском лишь несколько постов с устаревшими данными. Кто заинтересовался – под кат.

Зачем!?


Я столкнулся с такой задачей, когда писал приложение для составления своего университетского расписания. Удобно иметь свое расписание отдельно, да еще и стандартный календарь не поддерживает повторение событий через одну неделю, что необходимо для двухнедельного (чет./нечет.) расписания.
Идеей фикс была функция приложения, которая позволит “заполнить” введенным расписанием Android календарь. Плюсы очевидны: синхронизация с Google Calendar от google (простите за тавтологию), встроенные виджеты календаря (очень уж хорош этот виджет от HTC Sense) и гора виджетов от сторонних производителей, которые хоть покажут следующее событие, хоть загруженность недели, и т.д. Тут и понадобилась работа с календарем Android.

Использовать недокументированное API — это ПЛОХО! Пнятненько?



Неужели чтобы решить эту очевидную задачу необходимо использовать недокументированное API? Ответ – нет. Самый правильный метод использовать API Google Calendar, что я вам и советую сделать в своих разработках.

Но “правильный” метод налагает ряд ограничений:
• Нельзя использовать в отсутствии соединения с Интернет;
• Необходима синхронизация после заполнения календаря;
• Данные (а их не мало при заполнении целого года) идут до сервера а потом при синхронизации идут обратно, что, очевидно, увеличивает трафик в два раза.

По моему мнению, намного удобней использовать офлайн версию календаря, которую при любом удобном случае можно синхронизировать с гуглокалендарем. К сожалению google не позаботился о разработчиках под Android и не опубликовал официального API, но к нашей радости исходные коды Android открыты и умные люди уже давно нашли волшебные URI контент провайдеров.

Просим у пользователя разрешения работать с календарем



Парадокс. API нет, а разрешения в документации есть. Вам осталось только добавить их в ваш AndroidManifest.xml.

<uses-permission android:name="android.permission.READ_CALENDAR"> </uses-permission>
<uses-permission android:name="android.permission.WRITE_CALENDAR"> </uses-permission>

Унифицированный Идентификатор Ресурса или по-русски – URI


Для пользования календарем Android мы должны обратиться к его контент провайдеру по определенному URI. К нашему огорчению этот URI различается в разных версиях ОС и вполне может измениться в следующей (издержки использования недокументированного API). Но проблема решаема.
Для Froyo[2.2] и с большой вероятностью для следующих версий — content://com.android.calendar/calendars.
Для версий <2.2 — content://calendar/calendars.
Как видим, все не так плохо. Можно определять версию системы и использовать соответствующий URI, но я пошел по другому пути. Смотрю есть ли что по URI для <2.2 и если там нет никого, то использую оставшийся.

// Calendar URI before 2.2
Uri calendars = Uri.parse("content://calendar/calendars");
Cursor managedCursor = this.managedQuery(calendars, new String[] { "_id", "name" }, null, null, null);
if(managedCursor == null || managedCursor.getCount() == 0)
{
	// 2.2 (Froyo)
}
else
{
	// < 2.2
}

Будем считать, что URI получили.

Получаем список календарей


Для пользователей Google Calendar пояснять не нужно, а для остальных поясню. Общий Календарь состоит из одного или нескольких более мелких календарей, которые могут различаться по цвету маркеров. Обычно это что-то вроде “личный”, “работа”, “учеба” и т.д. Создавать такие календари можно только на сайте гуглокалендаря, в самом андроиде этого стандартными средствами сделать нельзя.
Для добавления событий нам необходимо знать ID календаря. Для выполнения этой задачи используем конструкцию из предыдущего пункта.

String[] projection = new String[] { "_id", "name" };
Cursor managedCursor = this.managedQuery(URI, projection, null, null, null);
if (managedCursor != null && managedCursor.moveToFirst()) 
        {
        	String calName;
        	String calID;        	
        	int nameColumn = managedCursor.getColumnIndex("name");
        	int idColumn = managedCursor.getColumnIndex("_id");
        	do 
        	{
        		calName = managedCursor.getString(nameColumn);
        		calID = managedCursor.getString(idColumn);
        		if (calName != null) // … UI     			 
           	} while (managedCursor.moveToNext());
            managedCursor.close();
        }

Теперь полученный набор имен можно вывести в интерфейс и позволить пользователю выбрать.

Добавляем событие


Тут работа с календарем происходит, как и с любым другим контент провайдером. Об этом можно почитать в документации.

ContentValues event = new ContentValues();
event.put("calendar_id", calID);  // ID календаря мы получили ранее
event.put("title", title); // Название события
event.put("description", desc); // Описание события
event.put("eventLocation", loc); // Место проведения
event.put("dtstart", start); // время начала
event.put("dtend", end); // время окончания
eventsURI=Uri.parse(URI + “/events"); 
this.getContentResolver().insert(eventsURI, event);

Поговорим о полях события. Поле описания я советую использовать для индикации события. Используя это поле, потом можно будет удалить скопом все события созданные вашей программой. Поля названия, описания и места проведения являются строковыми. Отдельно стоит упомянуть про поля времени. Эти поля имею тип long, а значения этих полей могут быть сгенерированы, например, следующей функцией.

public static long pickDate(int year, int month, int day, int hour, int minute) 
{
    	Calendar rightNow = Calendar.getInstance();
    	int timeShift = rightNow.get(Calendar.ZONE_OFFSET);    	
    	return Date.UTC(year - 1900, month, day, hour - (timeShift / 3600000), minute, 0);
}

С ней, я думаю, разберетесь сами, отмечу только, что в ней учитывается текущая временная зона.

Удаляем событие


Для удаления сообщения по ID необходимо добавить его к URI.

//eventsURI = URI + events/
uri = ContentUris.withAppendedId(eventsURI, EVENT_ID);
this.getContentResolver().delete(uri, null, null);	

Для удаления по другим полям можно курсором вытащить все события, как мы делали это с календарями, а затем по ID удалить выборочно.

Заключение


В заключение еще раз напомню, что используете вы это на свой страх и риск. В ходе разработки я один раз не так написал URI и мой телефон каждый раз при синхронизации начал выдавать ошибку (исправил стиранием информации из календаря совсем).
Еще одна неприятность – в эмуляторе отсутствует календарь, потому на нем все вышесказанное повторить не удастся.
И совсем в заключение. Не могу не воспользоваться возможностью попиарить свое приложение, о котором шла речь в статье. Вы сможете найти его в маркете под названием uTimetable.




И совсем совсем в заключение хотел бы спросить у хабрастудентов: Какого формата у вас в ВУЗе расписание (одна/две недели) и как эти недели называются если их две — четная/нечетная или по другому? Также интересуют зарубежные вузы.
Tags:
Hubs:
Total votes 66: ↑58 and ↓8+50
Comments50

Articles