Как синхронизировать календарь google с android
Чтобы вы могли просматривать приемы на телефоне, сначала директор или ответственный добавляет адреса электронной почты Google в карточки сотрудников и выдает доступ. Управление сотрудниками
После выдачи доступа на вашу почту придет письмо. Чтобы настроить календарь выполните действия:
- На компьютере зайдите в почту и последовательно перейдите в каждое письмо «Пользователь . предоставил вам доступ к календарю».
- В каждом письме нажмите «Добавить этот календарь».

- Вас «перебросит» в Google Календарь. Появится всплывающее окно «Добавить календарь *Фамилия И. О.*» → нажмите «Добавить».
Рекомендуем переходить по ссылке с компьютера, потому что первичную настройку нужно делать именно через Календарь в браузере, а не через приложение.

4. В Google Календаре слева вы увидите фамилии добавленных сотрудников. Рядом с каждой должна стоять галочка.

Переходим к настройке календарей на телефоне. Как добавить учетную запись Google на iPhone
- Откройте мобильное приложение «Google Календарь». Внешний вид и меню приложения может немного отличаться в зависимости от операционной системы телефона — iOS или Android — и ее версии. Скачать Google Календарь на iPhone

- Перейдите в «Настройки».

- Если учетных записей в Google несколько, найдите ту, которая прописана в вашей карточке в IDENT. Нажмите на «Еще» (если сотрудник, расписание которого нужно видеть, будет только один, то сразу на его фамилию).

- Далее последовательно нажмите на фамилию каждого сотрудника, который должен отображаться в календаре и включите синхронизацию (если она уже включена, то выключите и включите заново).
![]() |
![]() |
Готово. Расписание синхронизировано.
Как синхронизировать календари на компьютере и смартфоне

Проще всего синхронизировать свое расписание на компьютере и смартфоне с помощью календаря Google. К нему вы можете легко получить доступ не только через телефон, но и через браузер. Как это работает, рассказываем в статье.


Как настроить приложение календаря на компьютере
Для синхронизации календаря вам понадобится учетная запись Google. Кроме того, вам нужно будет соответствующим образом настроить свой Android-смартфон:
Если у вас еще нет учетной записи Google, вы можете создать ее бесплатно здесь. Если вы зашли на этот сайт первый раз, вы получите от Google небольшую инструкцию по работе с системой. Здесь вы можете ввести информацию о мероприятиях и встречах, которые хотите синхронизировать.

Проверьте общий доступ к календарю. Для этого перейдите в раздел «Настройки», нажав на значок шестеренки в правом верхнем углу. Затем откройте раздел «Настройки моих календарей» в меню слева и щелкните по календарю, который хотите синхронизировать. Убедитесь, что в пункте «Доступ для отдельных пользователей» указан адрес электронной почты, с которым вы вошли в систему.
Как синхронизировать календари на ПК и смартфоне
Скачайте приложение «Google Календарь» на свой смартфон, если оно еще не установлено.
- Откройте настройки телефона и найдите раздел, отвечающий за учетные записи.
- Кликните по строке с аккаунтом Google — вы перейдете в настройки синхронизации.
- Активируйте ползунок напротив календаря, чтобы синхронизировать его со своей учетной записью Google.

Отныне все события, которые вы создаете в календаре на смартфоне, станут сохраняться в Google-календаре и, соответственно, будут доступны на всех устройствах: как на компьютере, так и на телефоне или планшете.
Читайте также:
- Да будет свет: как выбрать датчик движения для освещения дома?=«content_internal_link»>
- 7 способов быстро очистить микроволновку от жира=«content_internal_link»>
Как синхронизировать календари и заметки на смартфоне и компьютере
Чтобы получить доступ к заметкам и событиям календаря не только со смартфона, но и с компьютера, нужно настроить их синхронизацию – тогда ваши записи будут отображаться сразу на всех устройствах. Редакция ZOOM.CNews рассказывает, как сделать это разными способами: с помощью встроенных инструментов мобильных систем и дополнительных приложений.
Автор Любовь Баскова

Как синхронизировать календари и заметки на смартфоне и компьютере
Дата публикации: 03.11.2021
Как синхронизировать календарь
Самый популярный способ планирования рабочих и личных дел – Google Календарь. Рассмотрим, как сделать события, которые вы в нем создаете, доступными на всех устройствах.

В первую очередь для работы с календарем вам нужна учетная запись Google. Также понадобится установить соответствующее приложение на смартфон, если его еще нет. На компьютере в настройках Google Календаря надо открыть общий доступ для вашего аккаунта:
- Зайдите на сайт calendar.google.com и нажмите не шестеренку в правом верхнем углу.
- Слева в разделе «Настройки моих календарей» выберите нужный календарь.
- В пункте «Доступ для отдельных пользователей» должен быть указан ваш e-mail.
Как настроить Google Календарь на iPhone и iPad
На iPhone и iPad есть возможность синхронизировать встроенный календарь и Google Календарь. Для этого нужно добавить аккаунт Google в раздел «Календарь» в настройках системы:
- Зайдите в «Настройки» и откройте «Календарь», затем – «Учетные записи».
- Если среди учетных записей нет Google, тапните на пункт «Новая учетная запись» и выберите из предложенных вариантов Google.
- На открывшемся сайте google.com войдите в свой аккаунт – теперь он появится в учетных записях на смартфоне.
- Выберите вашу учетную запись Google и активируйте пункт «Календари».

Теперь события, которые вы внесете в Google Календарь с другого устройства, отобразятся во встроенном календаре вашего iPhone или iPad.
Это работает и в обратную сторону, то есть события из iOS-планировщика можно сохранить в Google Календаре:
- Войдите в календарь на устройстве и нажмите кнопку «Плюс» в правом верхнем углу экрана.
- Пролистайте описание события до пункта «Календарь» и нажмите на него.
- В открывшемся разделе поставьте галочку напротив вашего адреса электронной почты Gmail – событие сохранится в Google Календаре.

Как настроить Google Календарь на Android-смартфоне
На Android-устройствах Google Календарь используется по умолчанию. Но на всякий случай проверьте, что синхронизация включена.
- В настройках смартфона нужно найти пункт «Пользователи и аккаунты» (или с похожим названием в зависимости от оболочки вашего устройства).
- Далее выбрать ваш аккаунт Google, чтобы перейти в параметры синхронизации.
- Здесь необходимо удостовериться, что напротив пункта «Google Календарь» стоит галочка, как показано на скриншоте. То же самое касается и заметок (Google Keep).

Как синхронизировать заметки
В заметки мы вносим важные напоминания, списки покупок или рабочие задачи, потому желателен доступ к ним со всех устройств. Ниже — несколько простых способов синхронизации заметок для разных операционных систем.
Встроенные заметки на iOS
Встроенные заметки на iPhone и iPad можно синхронизировать с ПК несколькими путями.
1. Через iCloud
Самый простой способ читать и редактировать заметки со всех устройств – это загрузить их в iCloud, если вы пользуетесь этим сервисом.
Для начала удостоверьтесь, что у вас настроена синхронизация с iCloud:
- Войдите в «Настройки» и откройте ваш профиль, затем – iCloud.
- В этом разделе сделайте активным пункт «Заметки».
Теперь все заметки по умолчанию будут сохраняться в iCloud, также они появятся в соответствующей папке на компьютере Mac и всех iOS-устройствах, на которые вы вошли под своим Apple ID.

Зайти в свои заметки на ПК с другой операционной системой вы можете через сайт icloud.com или приложение: iCloud для Windows или iCloud Snap для Linux. Записи можно редактировать, удалять и добавлять новые – все изменения отобразятся в папке iCloud в приложении «Заметки» на смартфоне.
2. Синхронизация через Gmail
Встроенные заметки на iOS можно синхронизировать так же, как и календарь – через учетную запись Google. Для этого войдите в «Настройки» и откройте раздел «Заметки», после чего по такой же схеме, как для Google Календаря, активируйте соответствующий пункт.

После этого в «Заметках» появится папка Gmail, в которую нужно сохранять записи для синхронизации. Все заметки из нее будут отображаться в почте Gmail в отдельной папке Notes. Вы сможете просматривать их из своего почтового аккаунта в браузере на ПК или другом устройстве, а вот добавлять и редактировать получится только со смартфона.
Обратите внимание, что синхронизируются лишь заметки, созданные после подключения аккаунта Google. Все, что вы написали ранее, будет сохранено на устройстве и в iCloud.
Встроенные заметки в Android
На большинстве Android-устройств используется сервис Google Keep (Google Заметки). Исключение составляют гаджеты Samsung, где по умолчанию работает приложение Samsung Notes, которое синхронизируется через сервис OneNote от Microsoft, и смартфоны HUAWEI и HONOR без Google-сервисов — доступ к заметкам в них можно получить через HUAWEI Cloud из браузера.

Вы можете открыть Google Keep с компьютера через браузер на сайте keep.google.com. На всех устройствах можно редактировать, добавлять и удалять новые записи. Кстати, приложение Google Keep можно использовать и на iOS — оно есть в App Store.
Синхронизация заметок через сторонние приложения
Также вы можете установить одно из приложений для создания записей. В них предусмотрена синхронизация и есть настройки общего доступа к заметкам.
Что касается файловых менеджеров, то неверно считать, что они позволяют синхронизировать заметки: такие утилиты, как ES Проводник, FX File Explorer, Total Commander для Android и File Explorer, Documents или File Master для iOS позволяют сохранять записи для переноса или просмотра на других устройствах, но именно синхронизации в них нет.
Evernote
Evernote, несмотря на появление платной версии, все еще остается одним из самых популярных приложений для заметок, так как в нем много инструментов для организации работы в целом: задачи, поиск с помощью меток, функция копирования веб-страниц и возможность прикрепить к записям PDF-файлы, чеки, изображения и др. Программа работает на iOS, MacOS, Android и Windows.

Но синхронизировать заметки в бесплатной версии можно только на двух устройствах, на которые установлено приложение Evernote, например, ноутбук и смартфон. Если вам нужна синхронизация еще и с планшетом – придется завести платный аккаунт (от 219 рублей в месяц) либо редактировать заметки из браузера.
OneNote
Заметки OneNote экосистемы Microsoft также можно использовать для синхронизации записей на разных гаджетах. Для этого установите приложение OneNote (есть для ОС Windows, MacOS, Android, iOS) или откройте сервис в браузере.

Для хранения записных книжек используется OneDrive, так что понадобится аккаунт и в этом сервисе. Если у вас установлена Windows 10, то сохранение происходит автоматически. Для более ранних версий и других ОС придется вручную импортировать записные книжки в OneDrive.
- Откройте записки на компьютере, нажмите «Файл» – «Общий доступ».
- Выберите OneDrive в качестве расположения для записной книжки или кликните «Добавить место» и войдите в OneDrive.
- Введите имя для записной книжки и нажмите «Переместить записную книжку», после чего заметки перенесутся с локального устройства (компьютера) в OneDrive.
- В iOS при запуске приложения OneNote все записи будут по умолчанию синхронизироваться с OneDrive.
- На Android-устройстве нужно открыть приложение OneNote и в левом нижнем углу тапнуть на пункт «Записные книжки», после чего в разделе «Дополнительные параметры» выбрать «Синхронизировать все».
Теперь при входе в учетную запись в браузере или в приложение OneNote на ПК вы увидите заметки, созданные на смартфоне. Их можно отредактировать или добавить новые – все изменения отобразятся на мобильном устройстве.
SimpleNote
Менеджер заметок SimpleNote работает на Android, Windows, Linux, iOS, MacOs – записи синхронизируются на всех устройствах и доступны для просмотра и редактирования. К заметкам можно добавить теги, по которым их удобно искать и объединять в тематические блоки.

Также SimpleNote поддерживает разметку Markdown, позволяющую с помощью простых обозначений добавлять форматирование, которое сохраняется при экспорте файла в другие программы. То есть запись с заголовками, курсивом и иными элементами, внесенными при помощи Markdown, можно перенести, например, в Word или PDF, не теряя форматирования.
Яндекс.Заметки
Яндекс.Заметки входит в состав сервиса Яндекс.Диск, который работает на iOS, Android, Windows, MacOS и Linux. Также доступ к заметкам можно получить из браузера и из Яндекс.Почты. Вот как это сделать:
- Установите на устройство Яндекс.Почту или Яндекс.Диск и войдите в свой аккаунт.
- В левом нижнем углу экрана нажмите на значок «Еще» – откроются сервисы Яндекса.
- Откройте вкладку «Заметки» и добавьте новую запись.

Все записи будут автоматически сохраняться на Диске, их можно добавлять, редактировать и удалять – изменения появятся на всех гаджетах. Но если редактировать одну и ту же запись с нескольких девайсов одновременно, то сервис создаст ее копию. В ней сохранятся изменения с одного устройства, а в оригинале – с другого.
Dropbox Paper
Для синхронизации заметок через Dropbox понадобится утилита Paper, в которой можно открывать, удалять, создавать и редактировать тексты на смартфоне, в приложении на ПК или через браузер в аккаунте Dropbox. Синхронизация происходит автоматически на всех устройствах, где вы зашли в учетную запись.

У заметок в Paper широкие возможности форматирования: можно добавлять таблицы, иллюстрации и файлы мультимедиа, писать код. Также в приложении есть инструменты для совместной работы: любую заметку можно открыть и редактировать нескольким пользователям, написать к ней комментарий и отметить в нем коллегу, чтобы привлечь его внимание.
Android Studio. Kotlin. Подключение Google календаря через Content Provider
Безысходность и отчаяние я испытывал много дней подряд, пытаясь «подключить» Google календарь к своему приложению. Так долго и так тяжело, как тогда, я не буксовал ни над одной фичей. Я сделал это! Прошло более двух месяцев, пока я и мои почти 200 активных пользователей не протестировали этот функционал в полной мере. Теперь я готов поделиться своим опытом, ибо в сети на русском языке (да и на английском тоже) я не нашел удовлетворяющее меня описание того, как работать с Google календарем через Content Provider.
Постановка задачи
Я работаю над приложением «Учет клиентов для самозанятых». Мне необходимо было реализовать в нем возможность планирования встреч с клиентами. Я сразу остановил свой выбор на Google календаре, т.к. в своей профессиональной деятельности активно им пользуюсь. Более того, мне была важна возможность общего доступа к календарю и его синхронизация с облачным сервисом Google. Супруга у меня тоже самозанятая и нам очень удобно планировать нашу семейную жизнь, поделившись друг с другом доступами для чтения к календарю каждого.
Самое простое решение, когда необходим Google календарь — воспользоваться Интентом, с помощью которого можно запускать стандартное приложение Календарь, чтобы добавлять новые события. Мне это решение не подходило по нескольким причинам. Во-первых, помимо добавления событий, мне так же нужно их читать и редактировать. Во-вторых, мое приложение работало со своей локальной Базой Данных, содержащей конфиденциальную информацию (самозанятые коллеги психологи меня поймут), которую не хотелось бы выносить за пределы приложения. И тогда необходимо решать, как локальные данные синхронизировать с данными Google календаря. Вызовом стандартного приложения Календаря не обойтись.
Приложение должно само добавлять, редактировать и читать данные из Google календаря. Синхронизировать его с облаком, если это необходимо. Для этого Google предлагает воспользоваться контент провайдером (Content Provider). Излагать подробно теорию не буду, ибо я не профи, а любитель. Прошу заранее прощение за возможные неточности в изложении и косяки в коде. По мере необходимости буду давать ссылки на статьи, из которых сам черпал информацию.
Решение задачи
Я так понимаю, что доступ к Google календарю в смартфоне похож на доступ к Базе Данных. Точнее, сам Календарь и все, что в нем есть по сути хранится в таблицах, как данные в БД. При помощи Контент провайдера (Content Provider) можно осуществлять запросы в БД Календаря: сохранять, изменять или удалять данные — события, календари, напоминания. Подробнее можете ознакомиться с работой Контент провайдера по ссылке: https://developer.android.com/guide/topics/providers/content-provider-basics
1. Получаем доступ к Календарю
Как во многом, что находится вне приложения, сначала необходимо получить к этому доступ. Получение доступа состоит из двух частей:
1.1. Указываем в манифесте необходимость доступа
Я на всякий случай запрашиваю доступ и на чтение, и на запись, хотя первое, возможно, делать и не обязательно.
1.2. Просим пользователя разрешить доступ
Все функции, отвечающие за интеграцию приложения с Google календарем, я упаковал в отдельный класс CalManager, однако доступ у пользователя запрашиваю из MainActivity, т.к. возвращаемый пользователем ответ (ActivityResultCallback), я так понимаю, принимать и обрабатывать возможно только в ней.
private val calendarPermission = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) < map ->if ( map[Manifest.permission.WRITE_CALENDAR] == true && map[Manifest.permission.READ_CALENDAR] == true ) initCalendar() else showAskWhyDialog() > private fun initCalendar()
Для получения доступа я использую RequestMultiplePermissions контракт, который пришел на замену устаревшего onRequestPermissionsResult. Подробнее о нем можно прочесть здесь: https://developer.android.com/training/permissions/requesting
Если кратко, то сначала инициализирую переменную calendarPermission, с помощью которой регистрирую запрос разрешений к Календарю и определяю Callback, который выполнится, когда пользователь отреагирует на просьбу дать доступ. Если разрешения получены, то запускаю функцию initCalendar(), иначе спрашиваю у пользователя «В чем дело, неужели сложно разрешить мне элементарную вещь? :)» — showAskWhyDialog()
Мое приложение без доступа к Календарю не работает, поэтому в функции initCalendar() проверяю доступ и опять его запрашиваю. Функция calManager.checkPermission(), описанная в классе CalManager, выглядит следующим образом:
fun checkPermission(): Boolean
Не спрашивайте меня, почему я запрашиваю разрешения на чтение и запись в Календаре, а проверяю только запись. 🙂
1.3. Настойчиво просим дать доступ к Календарю
Почему-то мой аппарат при попытке приложения запросить доступ повторно, если ранее пользователь отклонил запрос, игнорирует его, как-будто он поставил галочку «Больше не показывать», хотя ее нет в диалоговом окне. Поэтому, если пользователь сразу не дал доступ, то в диалоговом окне я доходчиво ему объясняю, в чем он не прав и направляю его в настройки смартфона, чтобы он дал доступ вручную.
private fun showAskWhyDialog() < val builder = AlertDialog.Builder(this) builder.setTitle(getString(R.string.cal_permission_deny_title)) .setMessage(R.string.cal_permission_deny_message) .setCancelable(false) .setPositiveButton(getText(R.string.cal_permission_deny_yes)) < dialog, id ->// открываем настройки приложения, чтобы пользователь дал разрешение вручную val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) val uri = Uri.fromParts("package", this.packageName, null) intent.data = uri getPermissionManually.launch(intent) > .setNegativeButton(getText(R.string.cal_permission_deny_no)) < dialog, id ->finish() > val dlg = builder.create() dlg.show() >
А так выглядит переменная getPermissionManually, отвечающая за callBack:
private val getPermissionManually = registerForActivityResult( ActivityResultContracts.StartActivityForResult() )
Бесконечный цикл — пока не дашь доступ, дальше не пройдешь! No pasaran! 🙂
2. Выбираем или создаем календарь, в котором будем сохранять события
Далее я буду описывать функции, содержащиеся в классе CalManager, отвечающие в моем приложении за работу Календаря. В первую очередь необходимо выбрать или создать календарь.
2.1. Получаем список доступных на смартфоне календарей
class ListCalendars < var id : Long = 0 var name = "" var accountName = "" var accountType = "" >fun getCalendars(): ArrayList < val calList = ArrayList() if (checkPermission()) < val projection = arrayOf( Calendars._ID, Calendars.NAME, Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE ) val selection = "$= $" val cursor: Cursor? = context.contentResolver.query( Calendars.CONTENT_URI, projection, selection, null, Calendars._ID + " ASC" ) if (cursor != null) while (cursor.moveToNext()) < val calendar = ListCalendars() calendar.id = cursor.getLong(0) calendar.name = cursor.getStringOrNull(1) ?: "" calendar.accountName = cursor.getString(2) calendar.accountType = cursor.getString(3) calList.add(calendar) >cursor?.close() > return calList >
В каждой функции, на всякий случай, я проверяю, есть ли доступ к календарю. Не уверен, что это необходимо, но возможен такой вариант, когда пользователь даст доступ, а потом его попросит обратно, тогда приложение «вылетит» с ошибкой. Случай маловероятный, но лучше перестраховаться.
Как видите, получение списка календарей один в один похоже на запрос к Базе Данных. В переменной projection указываем нужные нам имена полей таблицы со списком календарей. В selection — описываем условие, указав только те календари, в которых пользователь считается полноправным владельцем. Потом осуществляем запрос (contentResolver.query) и берем результат из переменной cursor, сохраняя для каждого календаря его имя, аккаунт, тип аккаунта и id. На выходе имеем список календарей calList.
2.2. Указываем календарь, в зависимости от размера списка
fun setCalendar(calList: ArrayList) < // определяем календарь when (calList.size) < 1 -> < setCalendarId(calList[0].id) accountType = calList[0].accountType accountName = calList[0].accountName setCalendarVisibilityAndSync() Toast.makeText(context, context.resources.getString(R.string.cal_set_lonely), Toast.LENGTH_LONG).show() >0 -> < val newCalUri = createCalendar() if (newCalUri != null) < setCalendarId(ContentUris.parseId(newCalUri)) accountName = "customer_accounting" accountType = CalendarContract.ACCOUNT_TYPE_LOCAL Toast.makeText(context, context.resources.getString(R.string.cal_create_success), Toast.LENGTH_LONG).show() >else Toast.makeText(context, context.resources.getString(R.string.cal_create_error), Toast.LENGTH_LONG).show() > else -> < var isLocalCalendarExist = false calList.forEach < if (it.accountName == "customer_accounting") isLocalCalendarExist = true >if (!isLocalCalendarExist) createCalendar() chooseCalendar(calList) > > >
Если календарь в списке один, то его и выбираем. Если нет ни одного календаря, то создаем новый. Если календарей больше одного, то проверяем, создан ли локальный календарь «customer_accounting», создаем его, если нет и даем пользователю выбрать календарь. Назначение функции setCalendarVisibilityAndSync() я поясню далее.
Тут необходимо сказать, что в приложении можно создавать только локальный календарь. Его нельзя синхронизировать с облаком и просматривать события из него на разных устройствах. Подробнее о том, как работать с Google Календарем через свое приложение можно прочесть здесь: https://developer.android.com/guide/topics/providers/calendar-provider
2.3. Создаем календарь, если это необходимо
fun createCalendar(): Uri? < if (checkPermission()) < val values = ContentValues().apply < put(Calendars.ACCOUNT_NAME, "customer_accounting") put(Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL) put(Calendars.NAME, context.resources.getString(R.string.cal_local_name_calendar)) put(Calendars.CALENDAR_DISPLAY_NAME, context.resources.getString(R.string.cal_local_name_calendar)) put(Calendars.CALENDAR_COLOR, -0x10000) put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER) put(Calendars.OWNER_ACCOUNT, "customer_accounting") put(Calendars.CALENDAR_TIME_ZONE, TimeZone.getDefault().id) put(Calendars.SYNC_EVENTS, 1) put(Calendars.VISIBLE, 1) >val builder: Uri.Builder = Calendars.CONTENT_URI.buildUpon() builder.appendQueryParameter(Calendars.ACCOUNT_NAME, "customer_accounting") builder.appendQueryParameter( Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL ) builder.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") return context.contentResolver.insert(builder.build(), values) > else return null >
Не сразу я понял, что в поле CALENDAR_TIME_ZONE необходимо указывать не название Зоны в строке, а ее id числом. В остальном новый календарь добавляется, как новая строка в таблицу календарей. Единственное, на что стоит обратить внимание, — это переменная builder. Она, я так понимаю, отвечает за построение Uri — пути, по которому находится необходимое место для записи в таблицу календарей. При ее настройке я указываю дополнительный параметр CALLER_IS_SYNCADAPTER — true. Дело в том, что доступ к Календарю можно получить двумя способами: «как приложение» или «как адаптер синхронизации». У второго способа — возможностей больше и. (спойлер) без него у меня не получилось редактировать повторяющиеся события. Но об этом — далее. Подробнее про «адаптер синхронизации» — здесь: https://developer.android.com/guide/topics/providers/calendar-provider#sync-adapter
2.4. Даем пользователю возможность выбрать календарь
private fun chooseCalendar(calList: ArrayList) < // создаем массив названий календарей и заполняем его var calendarNames : Array= emptyArray() var calIndex = 0 calList.forEachIndexed < index, calendar ->val subtitle = if (calendar.accountType == "LOCAL") context.resources.getString(R.string.cal_local_message) else calendar.accountName val name = calendar.name.ifEmpty < context.resources.getString(R.string.cal_without_name) >val title = "$name\n($subtitle)" calendarNames += title if (calendar.id == calendarId) calIndex = index > val builder = AlertDialog.Builder(context) builder.setTitle(context.resources.getString(R.string.cal_choose_cal)) .setCancelable(false) .setSingleChoiceItems(calendarNames, calIndex) < dialog, index ->calIndex = index > .setPositiveButton(context.resources.getText(R.string.OK)) < dialog, id ->setCalendarId(calList[calIndex].id) accountName = calList[calIndex].accountName accountType = calList[calIndex].accountType setCalendarVisibilityAndSync() Toast.makeText(context, "$ $", Toast.LENGTH_LONG).show() > val dlg = builder.create() dlg.show() >
Для предоставления выбора я использую стандартное диалоговое окно AlertDialog. Перед его показом пользователю, создаю массив имен календарей calendarNames и указываю в переменной calIndex — индекс выбранного ранее календаря. Обратите внимание, что при выборе календаря помимо его id я еще в обязательном порядке сохраняю имя календаря, accountName и accountType. Эти поля необходимы для работы «адаптера синхронизации» (наберитесь терпения — об этом чуть дальше).
3. CRUD-операции с событиями Google календаря
Для новичков, типа меня поясню, что CRUD — это Create, Read, Update, Delete. Но в данном случае вместо Create идет Insert. С нее и начнем.
3.1. Добавление нового события в Календарь
suspend fun insertEvent( title: String, _start: Long, duration: Long, _rrule: String = "", until: String = ""): Long? = withContext(Dispatchers.IO) < return@withContext if (checkPermission()) < val timeZone = TimeZone.getDefault().id val rrule = _rrule + until val start = if (duration != 0L) _start else getAllDayStart(_start) val end = if (duration != 0L) start + duration else start + 24 * 60 * 60 * 1000 val event = ContentValues().apply < put(Events.CALENDAR_ID, calendarId) // ID календаря put(Events.TITLE, title) // Название события put(Events.DESCRIPTION, context.resources.getString(R.string.cal_local_name_calendar)) // указание принадлежности приложению put(Events.EVENT_TIMEZONE, timeZone) put(Events.EVENT_LOCATION, "") put(Events.DTSTART, start) // время начала put(Events.STATUS, Events.STATUS_CONFIRMED) if (duration == 0L) put(Events.ALL_DAY, 1) if (rrule.isNotEmpty()) < put(Events.RRULE, rrule) // повторяемость put(Events.DURATION, "P$S") // продолжительность > else < put(Events.DTEND, end) // время окончания >> val eventUri = asSyncAdapter(Events.CONTENT_URI) val uri = context.contentResolver.insert(eventUri, event) syncCalendar() uri?.lastPathSegment?.toLongOrNull() > else null > fun getAllDayStart(_start: Long) : Long
Все операции с Календарем лучше осуществлять в «фоновом» (асинхронном) режиме, чтобы интерфейс приложения не подтормаживал. Google рекомендует для этого использовать AsyncQueryHandler (подробнее см. https://developer.android.com/reference/android/ content/AsyncQueryHandler). Я не стал, уж очень мне показался он чересчур замысловатым. Запускаю функции работы с Календарем при помощи Корутин (https://developer.android.com/kotlin/coroutines) в параллельном потоке. Я привык так работать с Базой Данных.
Повторять то, что уже написано о добавлении событий в статье «Calendar provider overview» (https://developer.android.com/guide/topics/providers/calendar-provider) не буду. Расскажу лишь о тех «граблях», на которые наступал я сам, когда писал и отлаживал код и своих «фишках».
- timeZone — как и при добавлении календаря, так и при добавлении события — id типа Int, а не String, будьте внимательны!
- rrule, который отвечает за правила повторяемости события, в моем случае, удобнее было разбить на две части: непосредственно правило и указание, до каких пор работает повторяемость. Это удобно впоследствии, когда повторяющиеся события будут редактироваться.
- start и end. Мне показалось удобным для тех записей в календаре, которым пользователь не указывает длительность (duration), создавать события целого дня. Тогда необходимо изменять им начало и конец, указывая полночь заданного дня и полночь + 24 часа. Ну и поле Events.ALL_DAY устанавливать в 1.
- Events.DESCRIPTION. Это единственное поле в записи события в календаре, где можно, как я понял, сохранить некий текст, указывающий принадлежность события моему приложению. У меня это — «Учет клиентов» (название приложения).
- rrule.isNotEmpty(). Черным по белому написано: если указываете повторяемость события в rrule, то вместо end — duration! Но кто подробно читает мануал? Точно не я. Будьте внимательны!
Объяснение концовки кода про функции asSyncAdapter() и syncCalendar() пока опускаю. Продолжаю держать интригу. Ибо для меня эта «жара» стоила 2 недель пота и слез! Две недели, Карл.
3.2. Изменение существующего события
suspend fun updateEvent( eventId: Long, title: String, _start: Long, duration: Long, _rrule: String = "", until: String = ""): Boolean = withContext(Dispatchers.IO)< return@withContext if(checkPermission()) < val rrule = _rrule + until val start = if (duration != 0L) _start else getAllDayStart(_start) val end = if (duration != 0L) start + duration else start + 24 * 60 * 60 * 1000 val event = ContentValues().apply < if (title.isNotEmpty()) put(Events.TITLE, title) // Название события - Имя клиента? if (start != 0L) put(Events.DTSTART, start) // время начала put(Events.ALL_DAY, if (duration == 0L) 1 else 0) if (rrule.isNotEmpty()) < put(Events.RRULE, rrule) // повторяемость if (duration != 0L) put(Events.DURATION, "P$S") // продолжительность > else < put(Events.DTEND, end) // время окончания >> val eventUri = Events.CONTENT_URI val row = context.contentResolver.update( eventUri, event, "$ = $eventId", null ) syncCalendar() row == 1 > else false >
Не спрашивайте меня, почему в случае update в отличие от insert я не использую «адаптер синхронизации». Может быть, с ним хуже работало, а может, я его убрал по каким-то для меня самого не ведомым причинам. Не знаю. Мне до сих пор не очень понятно, как он работает, но без него никак. В моем случае — никак! Подробнее расскажу ниже. А пока, вроде бы, пояснять в коде больше нечего. Изменяем событие по единственному условию — «$ = $eventId» посему должна измениться лишь одна строка таблицы, т.е. успешный исход изменений — row == 1 (true).
3.3. Удаление события из Календаря
suspend fun deleteEvent(eventId: Long): Boolean = withContext( Dispatchers.IO) < return@withContext if (checkPermission()) < //val eventUri = asSyncAdapter(Events.CONTENT_URI) val eventUri = Events.CONTENT_URI val row = context.contentResolver.delete( eventUri, "$= $eventId", null ) syncCalendar() row == 1 > else false >
Здесь также, как и в случае update, я не использую «адаптер синхронизации». Знатоки, подскажите в комментариях, как поступать правильно? Где его использовать, а где — нет, я, видимо, определял опытным путем. Про функцию syncCalendar() я напишу ниже.
3.4. Чтение событий Календаря
class ListEvents < var eventId : Long = 0 var newEventId : Long = 0 var title = "" var start : Long = 0 var begin : Long = 0 var end : Long = 0 var duration : Long = 0 var rrule = "" >suspend fun readEventsListOfDay( day: LocalDate): ArrayList = withContext( Dispatchers.IO) < val dataList = ArrayList() if (checkPermission()) < val calDate = Calendar.getInstance() calDate.timeZone = TimeZone.getDefault() calDate.set(day.year, day.monthValue - 1, day.dayOfMonth, 0, 0, 0) val start = calDate.timeInMillis calDate.add(Calendar.HOUR, 24) val end = calDate.timeInMillis val titleCol = CalendarContract.Instances.TITLE val startCol = CalendarContract.Instances.DTSTART val endCol = CalendarContract.Instances.END val idCol = CalendarContract.Instances.EVENT_ID val beginCol = CalendarContract.Instances.BEGIN val rruleCol = CalendarContract.Instances.RRULE val projection = arrayOf(titleCol, startCol, endCol, idCol, beginCol, rruleCol) val selection = "$!= 1 " + // исключаем удаленные события "AND $ = '$' " + // выбираем только те события, которые созданы приложением "AND $ = $calendarId " + "AND $beginCol > $start " val order = "$beginCol ASC" val eventsUriBuilder = CalendarContract.Instances.CONTENT_URI .buildUpon() ContentUris.appendId(eventsUriBuilder, start) ContentUris.appendId(eventsUriBuilder, end) val eventsUri = eventsUriBuilder.build() val cursor = context.contentResolver.query( eventsUri, projection, selection, null, order ) if (cursor != null) while (cursor.moveToNext()) < val item = ListEvents() item.eventId = cursor.getLongOrNull(cursor.getColumnIndex(idCol)) ?: 0 item.title = cursor.getStringOrNull(cursor.getColumnIndex(titleCol)).orEmpty() item.begin = cursor.getLongOrNull(cursor.getColumnIndex(beginCol)) ?: 0 item.start = cursor.getLongOrNull(cursor.getColumnIndex(startCol)) ?: 0 item.end = cursor.getLongOrNull(cursor.getColumnIndex(endCol)) ?: 0 item.rrule = cursor.getStringOrNull(cursor.getColumnIndex(rruleCol)).orEmpty() dataList.add(item) >cursor?.close() > return@withContext dataList >
Здесь уже плавно перехожу к описанию самого сложного для моего понимания — к работе с повторяющимися событиями (reccurent events). С ними я намаялся больше всего! Особенно в свете того, что в моем приложении мне необходимо было синхронизировать работу внутренней Базы Данных и Календаря.
Все события, как одиночные, так и повторяющиеся, записываются в таблицу Events. Для указания повторяемости события, как вы уже поняли, используются поля rrule и duration. А для того, чтобы прочитать события в некотором интервале времени, с учетом их повторяемости, делается запрос в таблицу Instances. В переменной eventsUriBuilder как раз указывается интервал времени (от start до end), из которого выбираются все входящие в него события.
Обратите внимание, что в таблице Instances у событий несколько иные поля. BEGIN и END — начало и конец отдельного события из серии повторяющихся событий или начало и конец одиночного события. EVENT_ID — id исходного события, которое задает серию повторяющихся событий или id одиночного события. DTSTART — начало первого события серии или начало одиночного события. В случае одиночного события DTSTART = BEGIN.
В остальном, как видите, чтение событий из Календаря ничем не отличается от запроса из Базы Данных. В projection даем массив необходимых нам полей. В selection — формулируем условия. Подробнее можете прочесть здесь: https://developer.android.com/guide/topics/ providers/calendar-provider#instances
4. Повторяющиеся события в Календаре
Начинается «моя боль». Добавлять повторяющиеся событие не сложнее, чем одиночные. Как читать данные из таблицы Instances, где отображаются события из серии повторяющихся, входящих в заданный интервал, разобрался довольно быстро. Но редактирование и удаление — боль, боль, боль.
Сначала я пытался указывать исключения в поле EXRULE или EXDATE — ничего хорошего из этого не получалось. Я так и не понял, для чего оно нужно, если не работает! Потом для того, чтобы удалить одно событие из серии повторяющихся или изменить его, я добавлял новое событие в таблицу исключений — эпик фэйл! Работало это крайне плохо. События редактировались или удалялись, но потом появлялись вновь, как будто в смартфоне «кто-то» проводит ревизию и все возвращает на свои места. При этом, если добавить новое повторяющееся событие, подождать приличное время и только потом его редактировать или удалять, то все работает нормально.
После двух недель мытарств и хождения по мукам (гугля все, что удавалось найти в интернете), я понял, что дело в синхронизации. Нельзя редактировать или удалять одно событие из серии повторяющихся, пока они не будут синхронизированы с облаком. Посему я задался этим вопросом и понял, что необходимо подключать приложение к Календарю, «как адаптер синхронизации» и синхронизировать календарь вручную сразу после добавления нового события.
4.1. Адаптер синхронизации и синхронизация Календаря
private fun setCalendarVisibilityAndSync() < val values = ContentValues() values.put(Calendars.SYNC_EVENTS, 1) values.put(Calendars.VISIBLE, 1) val uri = asSyncAdapter(ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId!!)) context.contentResolver.update(uri, values, null, null) >private fun asSyncAdapter(uri: Uri): Uri < return uri.buildUpon() .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") .appendQueryParameter(Calendars.ACCOUNT_NAME, accountName) .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build() >private fun syncCalendar()
Функцию setCalendarVisibilityAndSync() в обязательном порядке запускаю для того Календаря, в который собираюсь сохранять сообщения. Она «включает» Календарю поля SYNC_EVENTS и VISIBLE.
Функция asSyncAdapter() добавляет к Uri параметры ACCOUNT_NAME, ACCOUNT_TYPE и указывает CALLER_IS_SYNCADAPTER — true.
Функция syncCalendar() запускает синхронизацию Календаря. Ее я запускаю каждый раз, сразу после внесения в календарь изменений. Тогда и только тогда не возникает ошибок при редактировании и удалении повторяющихся событий.
4.2. Редактирование или удаление одного из серии повторяющихся событий
suspend fun deleteOneRecurrentEvent(eventId: Long, begin: Long): Long? = withContext( Dispatchers.IO) < return@withContext updateRecurrentEvent(eventId, begin) >private suspend fun updateRecurrentEvent( eventId: Long, begin: Long, newDate: Long = 0L): Long? = withContext(Dispatchers.IO) < return@withContext if (checkPermission()) < val event = ContentValues().apply < put(Events.ORIGINAL_INSTANCE_TIME, begin) if (newDate == 0L) put(Events.STATUS, Events.STATUS_CANCELED) else put(Events.DTSTART, newDate) >val eventUri = ContentUris.withAppendedId(CONTENT_EXCEPTION_URI, eventId) //val eventUri = asSyncAdapter(ContentUris.withAppendedId(CONTENT_EXCEPTION_URI, eventId)) val uri = context.contentResolver.insert(eventUri, event) syncCalendar() uri?.lastPathSegment?.toLongOrNull() > else null >
Для того, чтобы изменить одно событие из серии повторяющихся, необходимо в таблицу исключений (CONTENT_EXCEPTION_URI) добавить новое событие. В нем обязательно необходимо указать поле ORIGINAL_INSTANCE_TIME — начало того события серии, которое хотим изменить. Если я хочу не изменить, а удалить его, то вместо параметра newDate указываю — ноль. Тогда добавляемому событию в таблице исключений, указываю статус — STATUS_CANCELED.
Опытным путем обнаружено, что добавлять новое событие в таблицу исключений лучше не используя «адаптер синхронизации», но в обязательном порядке сразу после добавления необходимо синхронизировать календарь — syncCalendar().
4.3. Удаление всех последующих повторяющихся событий серии
suspend fun deleteAllRecurrentEvents( eventId: Long, begin: Long, start: Long, duration: Long, rrule: String): Boolean = withContext(Dispatchers.IO) < return@withContext if (checkPermission()) < val until = fHelper.dateTimeFormatter( context.resources, begin - 23*60*60*1000, Const.RFC5545 ) updateEvent(eventId, "", start, duration, rrule, until) >else false >
В моем приложении нет возможности изменять все последующий события серии, только удалять. По сути, удаление некоторого события серии заключается в изменении исходного события (updateEvent). Я добавляю в правило повторения ограничение его действия через until. Функция dateTimeFormatter форматирует дату события в форме RFC5545 — YYYYMMDDTHHMMSSZ. Подробнее здесь: https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.2
Итоги
Очень может быть, код у меня получился не идеальный. Однозначно, я не до конца понимаю, как оно работает. Но практика показывает, что все с ним в порядке. Буду рад прочесть ваши комментарии. И надеюсь, этот гайд будет кому-нибудь полезен. Я ничего подобного в сети не нашел.
- google calendar
- kotlin
- sync adapter
- content provider
- recurrent events


