项目场景:
在项目开发过程中,需要使用系统日历来辅助提醒。通过向系统日历中写入事件、设置提醒方式,实现到达某个特定的时间自动提醒的功能
解决方案:
1. 请求权限
//Android6.0以上需要动态申请权限
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
2. 创建一个常量类添加日历相关的uri
const val CONSTANT_CALENDAR_URL = "content://com.android.calendar/calendars" // 日历账户
const val CONSTANT_CALENDAR_EVENT_URL = "content://com.android.calendar/events" // 日历事件
const val CONSTANT_CALENDAR_REMINDER_URL = "content://com.android.calendar/reminders" // 日历提醒
3.创建一个日历的工具类
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.graphics.Color
import android.net.Uri
import android.provider.CalendarContract
import android.util.Log
import androidx.core.content.contentValuesOf
import com.list.plan.constant.*
import com.list.plan.entity.PunchEntity
import java.util.*
/**
* 日历提醒工具类
*/
class CalendarReminderUtils {
/**
* 检查是否已经添加了日历账户,如果没有先添加一个日历账户再查询
* 获取账户成功返回账户id,否则返回-1
*/
private fun checkAndAddCalendarAccount(context: Context): Int {
var account = -1
account = getSystemCalendarAccount(context)
return if (account < 0) {
addCalendarAccount(context)
} else account
}
/**
* 检查是否存在现有账户,存在则返回账户id,否则返回-1
* 这里获取现有账户中第一个账号,如果不需要,可以将CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL + " ASC "变成null
*/
private fun getSystemCalendarAccount(context: Context): Int {
var account = -1
var userCursor: Cursor? = null
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
userCursor = context.contentResolver.query(
Uri.parse(CONSTANT_CALENDAR_URL),
null,
null,
null,
CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL + " ASC "
)
}
if (userCursor != null && userCursor.count > 0) {
userCursor.moveToLast()
account = userCursor.getInt(userCursor.getColumnIndex(CalendarContract.Calendars._ID))
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
userCursor?.close()
}
return account
}
/**
* 添加一个日历账号
*/
private fun addCalendarAccount(context: Context): Int {
val values= contentValuesOf(
// 日历名称
CalendarContract.Calendars.NAME to CONSTANT_CALENDARS_NAME,
// 日历账号,为邮箱格式
CalendarContract.Calendars.ACCOUNT_NAME to CONSTANT_CALENDARS_ACCOUNT_NAME,
// 账户类型
CalendarContract.Calendars.ACCOUNT_TYPE to CONSTANT_CALENDARS_ACCOUNT_TYPE,
// 展示给用户的日历名称
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME to CONSTANT_CALENDARS_DISPLAY_NAME,
// 它是一个表示被选中日历是否要被展示的值。
// 0值表示关联这个日历的事件不应该展示出来。
// 而1值则表示关联这个日历的事件应该被展示出来。
// 这个值会影响CalendarContract.instances表中的生成行。
CalendarContract.Calendars.VISIBLE to 1,
// 账户标记颜色
CalendarContract.Calendars.CALENDAR_COLOR to Color.YELLOW,
// 账户级别
CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL to CalendarContract.Calendars.CAL_ACCESS_OWNER,
// 它是一个表示日历是否应该被同步和是否应该把它的事件保存到设备上的值。
// 0值表示不要同步这个日历或者不要把它的事件存储到设备上。
// 1值则表示要同步这个日历的事件并把它的事件储存到设备上。
CalendarContract.Calendars.SYNC_EVENTS to 1,
// 时区
CalendarContract.Calendars.CALENDAR_TIME_ZONE to TimeZone.getDefault().id,
// 账户拥有者
CalendarContract.Calendars.OWNER_ACCOUNT to CONSTANT_CALENDARS_ACCOUNT_NAME,
CalendarContract.Calendars.CAN_ORGANIZER_RESPOND to 0
)
var calendarUri=Uri.parse(CONSTANT_CALENDAR_URL)
calendarUri=calendarUri.buildUpon()
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(
CalendarContract.Calendars.ACCOUNT_NAME,
CONSTANT_CALENDARS_ACCOUNT_NAME
)
.appendQueryParameter(
CalendarContract.Calendars.CALENDAR_COLOR_KEY,
CONSTANT_CALENDARS_ACCOUNT_TYPE
)
.build()
val result=context.contentResolver.insert(calendarUri, values)
return if (result == null) -1 else ContentUris.parseId(result).toInt()
}
/**
* 添加日历事件
* 里面有个日历的重复规则,比较麻烦,可查看https://www.jianshu.com/p/8f8572292c58里面有详细的重复规则
*/
fun addCalendarEvent(
context: Context,
name: String,
startTime: Long?,
endTime: Long?,
planWeek: String,
untilDate: String?
){
val account=checkAndAddCalendarAccount(context)
if (account<0){
return
}
var weekDay=""
//这里是重复规则里的截至日期,暂时没有什么比较好的办法,只能这么写
//例:截至2022年12月12日,20221212T000000Z
val Date="${untilDate}T000000Z"
weekDay = checkWeekDay(planWeek)
val event= contentValuesOf(
// 事件的日历_ID。
CalendarContract.Events.CALENDAR_ID to account,
// 事件标题
CalendarContract.Events.TITLE to name,
// 事件描述
CalendarContract.Events.DESCRIPTION to "提醒你该去行动啦!",
// 事件开始时间
CalendarContract.Events.DTSTART to startTime,
// 事件结束时间
CalendarContract.Events.DTEND to endTime,
// 事件重复模式
CalendarContract.Events.RRULE to "FREQ=WEEKLY;BYDAY=$weekDay;UNTIL=$Date",
// 设置有闹钟提醒
CalendarContract.Events.HAS_ALARM to 1,
// 事件时区
CalendarContract.Events.EVENT_TIMEZONE to TimeZone.getDefault().id
)
val newEvent= context.contentResolver.insert(Uri.parse(CONSTANT_CALENDAR_EVENT_URL),event) ?: return
//事件提醒的设定
val values= contentValuesOf(
CalendarContract.Reminders.EVENT_ID to ContentUris.parseId(newEvent),
CalendarContract.Reminders.MINUTES to 0,
CalendarContract.Reminders.METHOD to CalendarContract.Reminders.METHOD_ALERT
)
val uri=context.contentResolver.insert(Uri.parse(CONSTANT_CALENDAR_REMINDER_URL),values) ?: return
}
/**
*判断重复规则里星期,比较简单
*/
private fun checkWeekDay(planWeek: String):String {
var replaceWeek=planWeek.replace("一","MO")
.replace("二","TU")
.replace("三","WE")
.replace("四","TH")
.replace("五","FR")
.replace("六","SA")
.replace("日","SU")
return replaceWeek.substring(0,replaceWeek.length-1)
}
/**
* 删除日历事件
*/
fun deleteCalendarEvent(context: Context,punchEntity: PunchEntity?){
val title=punchEntity?.name
context.contentResolver.delete(
Uri.parse(CONSTANT_CALENDAR_EVENT_URL),
CalendarContract.Events.TITLE+"=?",
arrayOf(title)
)
}
/**
* 更新日历事件
*/
fun updateCalendarEvent(context: Context, name:String, startTime: Long?, endTime:Long?){
val values= contentValuesOf(
// 事件标题
CalendarContract.Events.TITLE to name,
// 事件描述
CalendarContract.Events.DESCRIPTION to "提醒你该去行动啦!",
// 事件开始时间
CalendarContract.Events.DTSTART to startTime,
// 事件结束时间
CalendarContract.Events.DTEND to endTime,
)
context.contentResolver.update(
Uri.parse(CONSTANT_CALENDAR_EVENT_URL),
values,
CalendarContract.Events.TITLE+"=?",
arrayOf(name)
)
}
}