סקירה כללית של ספק היומן

ספק היומן הוא מאגר של אירועים ביומן של המשתמש. באמצעות Calendar Provider API אפשר לבצע פעולות של שליחת שאילתות, הוספה, עדכון ומחיקה של יומנים, אירועים, משתתפים, תזכורות וכו'.

אפליקציות ומתאמי סנכרון יכולים להשתמש ב-Calendar Provider API. הכללים משתנים בהתאם לסוג התוכנית שמבצעת את הקריאות. המסמך הזה מתמקד בעיקר בשימוש ב-Calendar Provider API כאפליקציה. במאמר מתאמי סנכרון מוסבר על ההבדלים בין מתאמי הסנכרון.

בדרך כלל, כדי לקרוא או לכתוב נתוני יומן, המניפסט של האפליקציה צריך לכלול את ההרשאות המתאימות, כפי שמתואר בקטע הרשאות משתמשים. כדי להקל על ביצוע פעולות נפוצות, ספק היומן מציע קבוצת כוונות, כפי שמתואר באובייקטים של יומן Google. כשמעבירים את המשתמשים ליומן Google, הם יכולים להוסיף, להציג ולערוך אירועים. המשתמש יוצר אינטראקציה עם אפליקציית יומן Google ואז חוזר לאפליקציה המקורית. לכן האפליקציה לא צריכה לבקש הרשאות, וגם לא לספק ממשק משתמש כדי להציג או ליצור אירועים.

יסודות

ספקי תוכן שומרים נתונים ומאפשרים לאפליקציות לגשת אליהם. ספקי התוכן שמוצעים על ידי פלטפורמת Android (כולל ספק היומן) חושפים בדרך כלל נתונים כקבוצה של טבלאות שמבוססות על מודל של מסד נתונים יחסיים, שבו כל שורה היא רשומה וכל עמודה היא נתונים מסוג ומשמעות מסוימים. באמצעות Calendar Provider API, אפליקציות ומתאמי סנכרון יכולים לקבל הרשאת קריאה/כתיבה לטבלאות של מסד הנתונים שמכילות את נתוני היומן של המשתמש.

כל ספק תוכן חושף URI ציבורי (עטוף כאובייקט Uri) שמזהה באופן ייחודי את קבוצת הנתונים שלו. ספק תוכן ששולט בקבוצות נתונים מרובות (מספר טבלאות) חושף URI נפרד לכל אחת מהן. כל מזהי ה-URI של ספקים מתחילים במחרוזת 'content://‎'. כך אפשר לזהות שהנתונים נמצאים בשליטת ספק תוכן. ספק היומן מגדיר קבועים למזהי ה-URI של כל אחת מהכיתות (טבלאות) שלו. הפורמט של מזהי ה-URI האלה הוא <class>.CONTENT_URI. לדוגמה, Events.CONTENT_URI.

איור 1 מציג ייצוג גרפי של מודל הנתונים של ספק היומן. מוצגות בה הטבלאות הראשיות והשדות שמקשרים ביניהן.

מודל הנתונים של ספק יומן

איור 1. מודל הנתונים של ספק היומן.

למשתמש יכולים להיות כמה יומנים, וניתן לשייך יומנים שונים לסוגים שונים של חשבונות (יומן Google, Exchange וכו').

השדה CalendarContract מגדיר את מודל הנתונים של מידע שקשור ליומן ולאירועים. הנתונים האלה מאוחסנים במספר טבלאות, שמפורטות בהמשך.

טבלה (כיתה) תיאור

CalendarContract.Calendars

הטבלה הזו מכילה את המידע הספציפי ליומן. כל שורה בטבלה הזו כוללת את הפרטים של יומן יחיד, כמו השם, הצבע, פרטי הסנכרון וכו'.
CalendarContract.Events בטבלה הזו נשמרים הפרטים הספציפיים לאירוע. כל שורה בטבלה הזו מכילה את המידע על אירוע אחד – לדוגמה, שם האירוע, המיקום, שעת ההתחלה, שעת הסיום וכו'. האירוע יכול להתרחש פעם אחת או לחזור על עצמו כמה פעמים. המשתתפים, התזכורות והמאפיינים המורחבים מאוחסנים בטבלאות נפרדות. לכל אחד מהם יש EVENT_ID שמתייחס ל-_ID בטבלה Events.
CalendarContract.Instances בטבלה הזו מופיעים שעת ההתחלה ושעת הסיום של כל אירוע. כל שורה בטבלה הזו מייצגת אירוע יחיד. באירועים חד-פעמיים יש מיפוי של מכונות לאירועים ביחס של 1:1. לגבי אירועים חוזרים, נוצרות באופן אוטומטי כמה שורות שתואמות למספר מופעים של אותו אירוע.
CalendarContract.Attendees הטבלה הזו מכילה את פרטי המשתתפים באירוע (אורח). כל שורה מייצגת אורח אחד באירוע. הוא מציין את סוג האורח ואת תשובת האורח לגבי נוכחות באירוע.
CalendarContract.Reminders בטבלה הזו נשמרים נתוני ההתראות. כל שורה מייצגת התראה אחת על אירוע. לאירוע יכולות להיות כמה תזכורות. המספר המקסימלי של תזכורות לכל אירוע מצוין ב-MAX_REMINDERS, שנקבע על ידי מתאם הסנכרון שבבעלותו היומן הנתון. התזכורות מצוינות בדקות לפני האירוע, ויש להן שיטה שקובעת איך המשתמש יקבל התראה.

ממשק ה-API של ספק יומן Google תוכנן להיות גמיש וחזק. עם זאת, חשוב לספק חוויית משתמש טובה למשתמשי הקצה ולשמור על תקינות לוח השנה והנתונים שלו. לשם כך, ריכזנו כאן כמה דברים שחשוב לזכור כשמשתמשים ב-API:

  • הוספה, עדכון והצגה של אירועים ביומן כדי להוסיף, לשנות ולקרוא אירועים ישירות מספק היומן, צריך את ההרשאות המתאימות. עם זאת, אם אתם לא יוצרים אפליקציית יומן בגודל מלא או מתאם סנכרון, אין צורך לבקש את ההרשאות האלה. במקום זאת, אפשר להשתמש בכוונות (intents) שנתמכות באפליקציית יומן Google ל-Android כדי להעביר את פעולות הקריאה והכתיבה לאפליקציה הזו. כשמשתמשים בכוונות, האפליקציה שולחת את המשתמשים לאפליקציית יומן Google כדי לבצע את הפעולה הרצויה בטופס שהוזן מראש. בסיום, הם יחזרו לאפליקציה שלכם. אם תתכננו את האפליקציה כך שתוכל לבצע פעולות נפוצות דרך יומן Google, תוכלו לספק למשתמשים ממשק משתמש עקבי וחזק. זו הגישה המומלצת. למידע נוסף, ראו אובייקטים של יומן Google.
  • מתאמי סנכרון מתאם סנכרון מסנכרן את נתוני היומן במכשיר של המשתמש עם שרת או מקור נתונים אחר. בטבלאות CalendarContract.Calendars ו-CalendarContract.Events יש עמודות שמורות לשימוש של מתאמי הסנכרון. הספק והאפליקציות לא אמורים לשנות אותם. למעשה, הם לא גלויים אלא אם ניגשים אליהם כמתאם סנכרון. מידע נוסף על מתאמי סנכרון זמין במאמר מתאמי סנכרון.

הרשאות המשתמשים

כדי לקרוא נתוני יומן, האפליקציה צריכה לכלול את ההרשאה READ_CALENDAR בקובץ המניפסט שלה. היא חייבת לכלול את ההרשאה WRITE_CALENDAR למחיקת נתוני יומן, להוספת נתוני יומן או לעדכון שלהם:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
    <uses-sdk android:minSdkVersion="14" />
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
    ...
</manifest>

טבלת היומנים

הטבלה CalendarContract.Calendars מכילה פרטים של יומנים ספציפיים. אפשר לכתוב בעמודות הבאות של יומנים גם באפליקציה וגם במתאם סנכרון. רשימה מלאה של השדות הנתמכים מפורטת במאמר העזרה של CalendarContract.Calendars.

קבוע תיאור
NAME שם היומן.
CALENDAR_DISPLAY_NAME השם של היומן שיוצג למשתמש.
VISIBLE ערך בוליאני שמציין אם היומן נבחר להצגה. הערך 0 מציין שלא צריך להציג אירועים שמשויכים ליומן הזה. הערך 1 מציין שאירועים שמשויכים ליומן הזה צריכים להיות מוצגים. הערך הזה משפיע על יצירת השורות בטבלה CalendarContract.Instances.
SYNC_EVENTS ערך בוליאני שמציין אם צריך לסנכרן את היומן ולשמור את האירועים שלו במכשיר. הערך 0 מציין שלא מסנכרנים את היומן הזה ולא שומרים את האירועים שלו במכשיר. ערך של 1 מציין שצריך לסנכרן את האירועים ביומן הזה ולאחסן את האירועים במכשיר.

הכללת סוג חשבון לכל הפעולות

אם שולחים שאילתה על Calendars.ACCOUNT_NAME, צריך לכלול גם את Calendars.ACCOUNT_TYPE בבחירה. הסיבה לכך היא שחשבון נתון נחשב ייחודי רק אם יש לו גם ACCOUNT_NAME וגם ACCOUNT_TYPE. השדה ACCOUNT_TYPE הוא המחרוזת התואמת למאמת החשבון שבו השתמשתם כשנרשמתם ל-AccountManager. יש גם סוג מיוחד של חשבון שנקרא ACCOUNT_TYPE_LOCAL, שמשויך ללוחות שנה שלא משויכים לחשבון מכשיר. חשבונות ACCOUNT_TYPE_LOCAL לא מסתנכרנים.

שליחת שאילתה ליומן

הדוגמה הבאה מראה איך לאחזר את היומנים שבבעלות משתמש מסוים. כדי לפשט את הדוגמה, פעולת השאילתה מוצגת בשרשור של ממשק המשתמש ('השרשור הראשי'). בפועל, צריך לעשות זאת ב-thread אסינכרוני במקום ב-thread הראשי. מידע נוסף זמין במאמר מערכי טעינה. אם אתם לא רק קוראים את הנתונים אלא משנים אותם, תוכלו לעיין במאמר AsyncQueryHandler.

Kotlin

// Projection array. Creating indices for this array instead of doing
// dynamic lookups improves performance.
private val EVENT_PROJECTION: Array<String> = arrayOf(
        CalendarContract.Calendars._ID,                     // 0
        CalendarContract.Calendars.ACCOUNT_NAME,            // 1
        CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,   // 2
        CalendarContract.Calendars.OWNER_ACCOUNT            // 3
)

// The indices for the projection array above.
private const val PROJECTION_ID_INDEX: Int = 0
private const val PROJECTION_ACCOUNT_NAME_INDEX: Int = 1
private const val PROJECTION_DISPLAY_NAME_INDEX: Int = 2
private const val PROJECTION_OWNER_ACCOUNT_INDEX: Int = 3

Java

// Projection array. Creating indices for this array instead of doing
// dynamic lookups improves performance.
public static final String[] EVENT_PROJECTION = new String[] {
    Calendars._ID,                           // 0
    Calendars.ACCOUNT_NAME,                  // 1
    Calendars.CALENDAR_DISPLAY_NAME,         // 2
    Calendars.OWNER_ACCOUNT                  // 3
};

// The indices for the projection array above.
private static final int PROJECTION_ID_INDEX = 0;
private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;

בחלק הבא של הדוגמה, נרכיב את השאילתה. הבחירה מציינת את הקריטריונים לשאילתה. בדוגמה הזו, השאילתה מחפשת יומנים עם ACCOUNT_NAME‏"hera@example.com", ACCOUNT_TYPE‏"com.example" ו-OWNER_ACCOUNT‏"hera@example.com". אם רוצים לראות את כל היומנים שהמשתמש צפה בהם, ולא רק יומנים שבבעלותו, צריך להשמיט את OWNER_ACCOUNT. השאילתה מחזירה אובייקט Cursor שאפשר להשתמש בו כדי לעבור על קבוצת התוצאות שמוחזרת על ידי שאילתת מסד הנתונים. למידע נוסף על שימוש בשאילתות בספקי תוכן, ראו ספקי תוכן.

Kotlin

// Run query
val uri: Uri = CalendarContract.Calendars.CONTENT_URI
val selection: String = "((${CalendarContract.Calendars.ACCOUNT_NAME} = ?) AND (" +
        "${CalendarContract.Calendars.ACCOUNT_TYPE} = ?) AND (" +
        "${CalendarContract.Calendars.OWNER_ACCOUNT} = ?))"
val selectionArgs: Array<String> = arrayOf("hera@example.com", "com.example", "hera@example.com")
val cur: Cursor = contentResolver.query(uri, EVENT_PROJECTION, selection, selectionArgs, null)

Java

// Run query
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = Calendars.CONTENT_URI;
String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND ("
                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
                        + Calendars.OWNER_ACCOUNT + " = ?))";
String[] selectionArgs = new String[] {"hera@example.com", "com.example",
        "hera@example.com"};
// Submit the query and get a Cursor object back.
cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);

בקטע הבא משתמשים בסמן כדי לעבור דרך קבוצת התוצאות. הפונקציה משתמשת בערכי הקבועים שהוגדרו בתחילת הדוגמה כדי להחזיר את הערכים של כל שדה.

Kotlin

// Use the cursor to step through the returned records
while (cur.moveToNext()) {
    // Get the field values
    val calID: Long = cur.getLong(PROJECTION_ID_INDEX)
    val displayName: String = cur.getString(PROJECTION_DISPLAY_NAME_INDEX)
    val accountName: String = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX)
    val ownerName: String = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX)
    // Do something with the values...
}

Java

// Use the cursor to step through the returned records
while (cur.moveToNext()) {
    long calID = 0;
    String displayName = null;
    String accountName = null;
    String ownerName = null;

    // Get the field values
    calID = cur.getLong(PROJECTION_ID_INDEX);
    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);

    // Do something with the values...

   ...
}

שינוי יומן

כדי לבצע עדכון של יומן, אפשר לספק את _ID של היומן כמזהה שמצורף ל-Uri‏ (withAppendedId()) או כפריט הבחירה הראשון. הבחירה צריכה להתחיל ב-"_id=?", וה-selectionArg הראשון צריך להיות ה-_ID של היומן. אפשר גם לבצע עדכונים על ידי קידוד המזהה ב-URI. בדוגמה הזו משנים את שם התצוגה של לוח שנה באמצעות הגישה (withAppendedId()):

Kotlin

const val DEBUG_TAG: String = "MyActivity"
...
val calID: Long = 2
val values = ContentValues().apply {
    // The new display name for the calendar
    put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar")
}
val updateUri: Uri = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calID)
val rows: Int = contentResolver.update(updateUri, values, null, null)
Log.i(DEBUG_TAG, "Rows updated: $rows")

Java

private static final String DEBUG_TAG = "MyActivity";
...
long calID = 2;
ContentValues values = new ContentValues();
// The new display name for the calendar
values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
int rows = getContentResolver().update(updateUri, values, null, null);
Log.i(DEBUG_TAG, "Rows updated: " + rows);

הוספת יומן

יומנים נועדו לניהול בעיקר באמצעות מתאם סינכרון, לכן מומלץ להוסיף יומנים חדשים רק כמתאם סינכרון. ברוב המקרים, אפליקציות יכולות לבצע רק שינויים שטחיים בלוחות השנה, כמו שינוי השם המוצג. אם אפליקציה צריכה ליצור יומן מקומי, ניתן לעשות זאת על ידי הוספת היומן כמתאם סנכרון באמצעות ACCOUNT_TYPE של ACCOUNT_TYPE_LOCAL. ACCOUNT_TYPE_LOCAL הוא סוג חשבון מיוחד של יומנים שלא משויכים לחשבון מכשיר. לוחות שנה מהסוג הזה לא מסתנכרנים עם שרת. במאמר מתאמי סנכרון מוסבר על מתאמי סנכרון.

טבלת האירועים

הטבלה CalendarContract.Events מכילה פרטים של אירועים ספציפיים. כדי להוסיף, לעדכן ולמחוק אירועים, האפליקציה צריכה לכלול את ההרשאה WRITE_CALENDAR בקובץ המניפסט.

אפשר לכתוב בעמודות הבאות של Events גם באפליקציה וגם במתאם לסנכרון. רשימה מלאה של השדות הנתמכים מופיעה במסמך העזרה CalendarContract.Events.

קבוע תיאור
CALENDAR_ID ה-_ID של היומן שאליו שייך האירוע.
ORGANIZER כתובת האימייל של מארגן (הבעלים) האירוע.
TITLE שם האירוע.
EVENT_LOCATION המיקום שבו מתקיים האירוע.
DESCRIPTION תיאור האירוע.
DTSTART השעה שבה האירוע מתחיל, באלפיות השנייה לפי שעון UTC, מאז תחילת התקופה של זמן המערכת.
DTEND שעת הסיום של האירוע, במיליוניות השנייה לפי שעון UTC, מאז תחילת התקופה של זמן המערכת.
EVENT_TIMEZONE אזור הזמן של האירוע.
EVENT_END_TIMEZONE אזור הזמן של שעת הסיום של האירוע.
DURATION משך האירוע בפורמט RFC5545. לדוגמה, הערך "PT1H" מציין שהאירוע צריך להימשך שעה אחת, והערך "P2W" מציין משך זמן של שבועיים.
ALL_DAY ערך של 1 מציין שהאירוע הזה נמשך כל היום, כפי שמוגדר לפי אזור הזמן המקומי. הערך 0 מציין שמדובר באירוע רגיל שיכול להתחיל ולהסתיים בכל שלב במהלך היום.
RRULE כלל המחזוריות של פורמט האירוע. למשל, "FREQ=WEEKLY;COUNT=10;WKST=SU". דוגמאות נוספות זמינות כאן.
RDATE תאריכי החזרה של האירוע. בדרך כלל משתמשים ב-RDATE בשילוב עם RRULE כדי להגדיר קבוצה מצטברת של אירועים חוזרים. לפרטים נוספים, אפשר לעיין במפרט RFC5545.
AVAILABILITY אם האירוע נחשב לזמן תפוס או לזמן פנוי שאפשר לתזמן מחדש.
GUESTS_CAN_MODIFY אם האורחים יכולים לשנות את האירוע.
GUESTS_CAN_INVITE_OTHERS אם האורחים יכולים להזמין אורחים אחרים.
GUESTS_CAN_SEE_GUESTS אם האורחים יוכלו לראות את רשימת הנוכחים.

הוספת אירועים

כשהאפליקציה מוסיפה אירוע חדש, מומלץ להשתמש ב-Intent‏ INSERT, כפי שמתואר במאמר שימוש ב-Intent להוספת אירוע. עם זאת, אם צריך, אפשר להוסיף אירועים ישירות. בקטע הזה נסביר איך לעשות את זה.

אלו הכללים להוספת אירוע חדש:

  • חובה לכלול את CALENDAR_ID ואת DTSTART.
  • חובה לכלול EVENT_TIMEZONE. כדי לקבל רשימה של מזהי אזורי הזמן המותקנים במערכת, משתמשים ב-getAvailableIDs(). חשוב לזכור שהכלל הזה לא חל אם מוסיפים אירוע באמצעות הכוונה INSERT, כפי שמתואר במאמר שימוש בכוונה כדי להוסיף אירוע. בתרחיש כזה, מערכת GA4 מספקת אזור זמן שמוגדר כברירת מחדל.
  • באירועים לא חוזרים, צריך לכלול את הערך DTEND.
  • באירועים חוזרים, עליך לכלול DURATION בנוסף ל-RRULE או ל-RDATE. שימו לב שהכלל הזה לא חל אם מוסיפים אירוע באמצעות ה-Intent INSERT, שמתואר במאמר שימוש בכוונת הוספת אירוע – בתרחיש הזה, אפשר להשתמש ב-RRULE בשילוב עם DTSTART ו-DTEND, ואפליקציית יומן Google ממירה אותו למשך זמן באופן אוטומטי.

דוגמה להוספת אירוע. הפעולה הזו מתבצעת בשרשור של ממשק המשתמש כדי לפשט את התהליך. בפועל, כדאי לבצע הוספות ועדכונים בשרשור אסינכרוני כדי להעביר את הפעולה לשרשור ברקע. מידע נוסף זמין ב-AsyncQueryHandler.

Kotlin

val calID: Long = 3
val startMillis: Long = Calendar.getInstance().run {
    set(2012, 9, 14, 7, 30)
    timeInMillis
}
val endMillis: Long = Calendar.getInstance().run {
    set(2012, 9, 14, 8, 45)
    timeInMillis
}
...

val values = ContentValues().apply {
    put(CalendarContract.Events.DTSTART, startMillis)
    put(CalendarContract.Events.DTEND, endMillis)
    put(CalendarContract.Events.TITLE, "Jazzercise")
    put(CalendarContract.Events.DESCRIPTION, "Group workout")
    put(CalendarContract.Events.CALENDAR_ID, calID)
    put(CalendarContract.Events.EVENT_TIMEZONE, "America/Los_Angeles")
}
val uri: Uri = contentResolver.insert(CalendarContract.Events.CONTENT_URI, values)

// get the event ID that is the last element in the Uri
val eventID: Long = uri.lastPathSegment.toLong()
//
// ... do something with event ID
//
//

Java

long calID = 3;
long startMillis = 0;
long endMillis = 0;
Calendar beginTime = Calendar.getInstance();
beginTime.set(2012, 9, 14, 7, 30);
startMillis = beginTime.getTimeInMillis();
Calendar endTime = Calendar.getInstance();
endTime.set(2012, 9, 14, 8, 45);
endMillis = endTime.getTimeInMillis();
...

ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(Events.DTSTART, startMillis);
values.put(Events.DTEND, endMillis);
values.put(Events.TITLE, "Jazzercise");
values.put(Events.DESCRIPTION, "Group workout");
values.put(Events.CALENDAR_ID, calID);
values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
Uri uri = cr.insert(Events.CONTENT_URI, values);

// get the event ID that is the last element in the Uri
long eventID = Long.parseLong(uri.getLastPathSegment());
//
// ... do something with event ID
//
//

הערה: תוכלו להיעזר בדוגמה הזו כדי לתעד את מזהה האירוע אחרי שהאירוע נוצר. זו הדרך הקלה ביותר לקבל מזהה אירוע. לעיתים קרובות צריך את מזהה האירוע כדי לבצע פעולות אחרות ביומן, כמו הוספת משתתפים או תזכורות לאירוע.

אירועי עדכון

אם רוצים לאפשר למשתמש לערוך אירוע באפליקציה, מומלץ להשתמש בכוונה EDIT, כפי שמתואר בקטע שימוש בכוונה כדי לערוך אירוע. עם זאת, אם צריך, אפשר לערוך אירועים ישירות. כדי לעדכן אירוע, תוכלו לציין את ה-_ID של האירוע כמזהה מצורף ל-URI (withAppendedId()) או כפריט הבחירה הראשון. הבחירה צריכה להתחיל ב-"_id=?", וה-selectionArg הראשון צריך להיות ה-_ID של האירוע. אפשר גם לבצע עדכונים באמצעות בחירה ללא מזהה. דוגמה לעדכון אירוע. הוא משנה את שם האירוע לפי הגישה withAppendedId():

Kotlin

val DEBUG_TAG = "MyActivity"
...
val eventID: Long = 188
...
val values = ContentValues().apply {
    // The new title for the event
    put(CalendarContract.Events.TITLE, "Kickboxing")
}
val updateUri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID)
val rows: Int = contentResolver.update(updateUri, values, null, null)
Log.i(DEBUG_TAG, "Rows updated: $rows")

Java

private static final String DEBUG_TAG = "MyActivity";
...
long eventID = 188;
...
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
Uri updateUri = null;
// The new title for the event
values.put(Events.TITLE, "Kickboxing");
updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
int rows = cr.update(updateUri, values, null, null);
Log.i(DEBUG_TAG, "Rows updated: " + rows);

מחיקת אירועים

אפשר למחוק אירוע לפי _ID שלו כמזהה שמצורף ל-URI, או באמצעות בחירה רגילה. אם משתמשים במזהה שמצורף, אי אפשר גם לבצע בחירה. יש שתי גרסאות של המחיקה: כאפליקציה וכמתאם סנכרון. מחיקה של אפליקציה מגדירה את העמודה deleted לערך 1. הדגל הזה מאפשר למתאם הסנכרון לדעת שהשורה נמחקה וצריך להעביר את המחיקה הזו לשרת. מחיקה של מתאם סנכרון מסירה את האירוע ממסד הנתונים יחד עם כל הנתונים שמשויכים אליו. דוגמה למחיקת אירוע על ידי אפליקציה באמצעות _ID:

Kotlin

val DEBUG_TAG = "MyActivity"
...
val eventID: Long = 201
...
val deleteUri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID)
val rows: Int = contentResolver.delete(deleteUri, null, null)
Log.i(DEBUG_TAG, "Rows deleted: $rows")

Java

private static final String DEBUG_TAG = "MyActivity";
...
long eventID = 201;
...
ContentResolver cr = getContentResolver();
Uri deleteUri = null;
deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
int rows = cr.delete(deleteUri, null, null);
Log.i(DEBUG_TAG, "Rows deleted: " + rows);

טבלת המשתתפים

כל שורה בטבלה CalendarContract.Attendees מייצגת משתתף או אורח יחיד באירוע. קריאה ל-query() תחזיר רשימת משתתפים באירוע עם EVENT_ID הנתון. הערך של EVENT_ID חייב להתאים ל-_ID של אירוע מסוים.

בטבלה הבאה מפורטים השדות שאפשר לשנות. כשמוסיפים משתתף חדש, צריך לכלול את כולם מלבד ATTENDEE_NAME.

קבוע תיאור
EVENT_ID מזהה האירוע.
ATTENDEE_NAME השם של המשתתף.
ATTENDEE_EMAIL כתובת האימייל של המשתתף.
ATTENDEE_RELATIONSHIP

הקשר של המשתתף לאירוע. אחת מהאפשרויות:

ATTENDEE_TYPE

סוג המשתתף. אחת מהאפשרויות:

ATTENDEE_STATUS

סטטוס ההשתתפות של המשתתף. אחת מהאפשרויות:

הוספת משתתפים

דוגמה להוספת משתתף יחיד לאירוע: חשוב לזכור שהשדה EVENT_ID הוא שדה חובה:

Kotlin

val eventID: Long = 202
...
val values = ContentValues().apply {
    put(CalendarContract.Attendees.ATTENDEE_NAME, "Trevor")
    put(CalendarContract.Attendees.ATTENDEE_EMAIL, "trevor@example.com")
    put(
        CalendarContract.Attendees.ATTENDEE_RELATIONSHIP,
        CalendarContract.Attendees.RELATIONSHIP_ATTENDEE
    )
    put(CalendarContract.Attendees.ATTENDEE_TYPE, CalendarContract.Attendees.TYPE_OPTIONAL)
    put(
        CalendarContract.Attendees.ATTENDEE_STATUS,
        CalendarContract.Attendees.ATTENDEE_STATUS_INVITED
    )
    put(CalendarContract.Attendees.EVENT_ID, eventID)
}
val uri: Uri = contentResolver.insert(CalendarContract.Attendees.CONTENT_URI, values)

Java

long eventID = 202;
...
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(Attendees.ATTENDEE_NAME, "Trevor");
values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
values.put(Attendees.EVENT_ID, eventID);
Uri uri = cr.insert(Attendees.CONTENT_URI, values);

טבלת תזכורות

כל שורה בטבלה CalendarContract.Reminders מייצגת תזכורת אחת לאירוע. הפונקציה query() תחזיר רשימת תזכורות לאירוע עם הערך EVENT_ID הנתון.

בטבלה הבאה מפורטים השדות שאפשר לכתוב בהם תזכורות. צריך לכלול את כולם כשמוסיפים תזכורת חדשה. חשוב לזכור שהמתאמים לסנכרון מציינים את סוגי התזכורות שהם תומכים בהם בטבלה CalendarContract.Calendars. פרטים נוספים מופיעים ב-ALLOWED_REMINDERS.

קבוע תיאור
EVENT_ID מזהה האירוע.
MINUTES הדקות לפני האירוע שבו התזכורת צריכה לפעול.
METHOD

שיטת ההתראה, כפי שהוגדר בשרת. אחת מהאפשרויות:

הוספת תזכורות

בדוגמה הזו מתווספת תזכורת לאירוע. התזכורת תופיע 15 דקות לפני האירוע.

Kotlin

val eventID: Long = 221
...
val values = ContentValues().apply {
    put(CalendarContract.Reminders.MINUTES, 15)
    put(CalendarContract.Reminders.EVENT_ID, eventID)
    put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT)
}
val uri: Uri = contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, values)

Java

long eventID = 221;
...
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(Reminders.MINUTES, 15);
values.put(Reminders.EVENT_ID, eventID);
values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
Uri uri = cr.insert(Reminders.CONTENT_URI, values);

טבלת המכונות

בטבלה CalendarContract.Instances רשומות שעת ההתחלה ושעת הסיום של מופעים של אירוע. כל שורה בטבלה הזו מייצגת אירוע יחיד. אי אפשר לכתוב בטבלת המכונות והיא רק מאפשרת לשלוח שאילתה על אירועי אירוע.

בטבלה הבאה מפורטים חלק מהשדות שאפשר לשלוח עליהם שאילתות לגבי מכונה. הערה: אזור הזמן מוגדר על ידי KEY_TIMEZONE_TYPE ו-KEY_TIMEZONE_INSTANCES.

קבוע תיאור
BEGIN שעת ההתחלה של המכונה, במיליוניות השנייה לפי שעון UTC.
END שעת הסיום של המכונה, במיקרו-שניות לפי שעון UTC.
END_DAY היום היוניאני של סיום המכונה, ביחס לאזור הזמן של היומן.
END_MINUTE הדקה האחרונה של המופע, נמדדת מחצות באזור הזמן של היומן.
EVENT_ID השדה _ID של האירוע במקרה הזה.
START_DAY יום ההתחלה של המכונה לפי לוח השנה היוליאני, ביחס לאזור הזמן של יומן Google.
START_MINUTE דקת ההתחלה של המכונה, נמדדת מחצות, ביחס לאזור הזמן של יומן Google.

שליחת שאילתה לטבלת המכונות

כדי לשלוח שאילתה לטבלה Instances, צריך לציין טווח זמן לשאילתה ב-URI. בדוגמה הזו, ל-CalendarContract.Instances יש גישה לשדה TITLE דרך הטמעת הממשק CalendarContract.EventsColumns. במילים אחרות, הערך TITLE מוחזר דרך תצוגת מסד נתונים, ולא באמצעות שאילתות על הטבלה CalendarContract.Instances הגולמית.

Kotlin

const val DEBUG_TAG: String = "MyActivity"
val INSTANCE_PROJECTION: Array<String> = arrayOf(
        CalendarContract.Instances.EVENT_ID, // 0
        CalendarContract.Instances.BEGIN, // 1
        CalendarContract.Instances.TITLE // 2
)

// The indices for the projection array above.
const val PROJECTION_ID_INDEX: Int = 0
const val PROJECTION_BEGIN_INDEX: Int = 1
const val PROJECTION_TITLE_INDEX: Int = 2

// Specify the date range you want to search for recurring
// event instances
val startMillis: Long = Calendar.getInstance().run {
    set(2011, 9, 23, 8, 0)
    timeInMillis
}
val endMillis: Long = Calendar.getInstance().run {
    set(2011, 10, 24, 8, 0)
    timeInMillis
}

// The ID of the recurring event whose instances you are searching
// for in the Instances table
val selection: String = "${CalendarContract.Instances.EVENT_ID} = ?"
val selectionArgs: Array<String> = arrayOf("207")

// Construct the query with the desired date range.
val builder: Uri.Builder = CalendarContract.Instances.CONTENT_URI.buildUpon()
ContentUris.appendId(builder, startMillis)
ContentUris.appendId(builder, endMillis)

// Submit the query
val cur: Cursor = contentResolver.query(
        builder.build(),
        INSTANCE_PROJECTION,
        selection,
        selectionArgs, null
)
while (cur.moveToNext()) {
    // Get the field values
    val eventID: Long = cur.getLong(PROJECTION_ID_INDEX)
    val beginVal: Long = cur.getLong(PROJECTION_BEGIN_INDEX)
    val title: String = cur.getString(PROJECTION_TITLE_INDEX)

    // Do something with the values.
    Log.i(DEBUG_TAG, "Event: $title")
    val calendar = Calendar.getInstance().apply {
        timeInMillis = beginVal
    }
    val formatter = SimpleDateFormat("MM/dd/yyyy")
    Log.i(DEBUG_TAG, "Date: ${formatter.format(calendar.time)}")
}

Java

private static final String DEBUG_TAG = "MyActivity";
public static final String[] INSTANCE_PROJECTION = new String[] {
    Instances.EVENT_ID,      // 0
    Instances.BEGIN,         // 1
    Instances.TITLE          // 2
  };

// The indices for the projection array above.
private static final int PROJECTION_ID_INDEX = 0;
private static final int PROJECTION_BEGIN_INDEX = 1;
private static final int PROJECTION_TITLE_INDEX = 2;
...

// Specify the date range you want to search for recurring
// event instances
Calendar beginTime = Calendar.getInstance();
beginTime.set(2011, 9, 23, 8, 0);
long startMillis = beginTime.getTimeInMillis();
Calendar endTime = Calendar.getInstance();
endTime.set(2011, 10, 24, 8, 0);
long endMillis = endTime.getTimeInMillis();

Cursor cur = null;
ContentResolver cr = getContentResolver();

// The ID of the recurring event whose instances you are searching
// for in the Instances table
String selection = Instances.EVENT_ID + " = ?";
String[] selectionArgs = new String[] {"207"};

// Construct the query with the desired date range.
Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
ContentUris.appendId(builder, startMillis);
ContentUris.appendId(builder, endMillis);

// Submit the query
cur =  cr.query(builder.build(),
    INSTANCE_PROJECTION,
    selection,
    selectionArgs,
    null);

while (cur.moveToNext()) {
    String title = null;
    long eventID = 0;
    long beginVal = 0;

    // Get the field values
    eventID = cur.getLong(PROJECTION_ID_INDEX);
    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
    title = cur.getString(PROJECTION_TITLE_INDEX);

    // Do something with the values.
    Log.i(DEBUG_TAG, "Event:  " + title);
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(beginVal);
    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));
    }
 }

כוונות ביומן

לאפליקציה שלכם לא נדרשות הרשאות כדי לקרוא ולכתוב נתוני יומן. במקום זאת, היא יכולה להשתמש בכוונות (intents) שנתמכות באפליקציית יומן Google ל-Android כדי להעביר את פעולות הקריאה והכתיבה לאפליקציה הזו. בטבלה הבאה מפורטים הכוונות שנתמכות על ידי ספק היומן:

פעולה URI תיאור תוספות

VIEW

content://com.android.calendar/time/<ms_since_epoch>

אפשר גם להפנות ל-URI באמצעות CalendarContract.CONTENT_URI. דוגמה לשימוש בכוונה הזו מופיעה במאמר שימוש בכוונות כדי להציג נתוני יומן.
פתיחת היומן לשעה שצוינה ב-<ms_since_epoch>. ללא.

VIEW

content://com.android.calendar/events/<event_id>

אפשר גם להפנות ל-URI באמצעות Events.CONTENT_URI. דוגמה לשימוש בכוונה הזו מופיעה במאמר שימוש בכוונות כדי להציג נתוני יומן.
הצגת האירוע שצוין ב-<event_id>. CalendarContract.EXTRA_EVENT_BEGIN_TIME


CalendarContract.EXTRA_EVENT_END_TIME
EDIT

content://com.android.calendar/events/<event_id>

אפשר להפנות ל-URI גם באמצעות Events.CONTENT_URI. דוגמה לשימוש בכוונה הזו מופיעה במאמר שימוש בכוונה כדי לערוך אירוע.
עריכת האירוע שצוין על ידי <event_id>. CalendarContract.EXTRA_EVENT_BEGIN_TIME


CalendarContract.EXTRA_EVENT_END_TIME
EDIT

INSERT

content://com.android.calendar/events

אפשר גם להפנות ל-URI באמצעות Events.CONTENT_URI. דוגמה לשימוש בכוונה הזו מופיעה במאמר שימוש בכוונה כדי להוסיף אירוע.
יוצרים אירוע. כל אחד מהשירותים הנוספים שמפורטים בטבלה שבהמשך.

בטבלה הבאה מפורטות התוספות של Intent שנתמכות על ידי ספק היומן:

Intent Extra תיאור
Events.TITLE שם לאירוע.
CalendarContract.EXTRA_EVENT_BEGIN_TIME שעת ההתחלה של האירוע באלפיות השנייה מתחילת התקופה של זמן המערכת.
CalendarContract.EXTRA_EVENT_END_TIME שעת הסיום של האירוע, במיליוניות השנייה מתחילת התקופה של זמן המערכת.
CalendarContract.EXTRA_EVENT_ALL_DAY ערך בוליאני שמציין שאירוע הוא כל היום. הערך יכול להיות true או false.
Events.EVENT_LOCATION מיקום האירוע.
Events.DESCRIPTION תיאור האירוע.
Intent.EXTRA_EMAIL כתובות האימייל של האנשים שרוצים להזמין, כרשימה מופרדת בפסיקים.
Events.RRULE כלל לחזרה של האירוע.
Events.ACCESS_LEVEL האם האירוע הוא פרטי או ציבורי.
Events.AVAILABILITY אם האירוע נספר כזמן תפוס או כזמן פנוי שאפשר לתזמן בו פגישה.

בקטעים הבאים נסביר איך משתמשים בכוונות האלה.

שימוש בכוונה (intent) להוספת אירוע

שימוש ב-Intent‏ INSERT מאפשר לאפליקציה להעביר את המשימה של הוספת האירוע ליומן Google עצמו. בגישה הזו, אפילו לא צריך לכלול את ההרשאה WRITE_CALENDAR בקובץ המניפסט של האפליקציה.

כשמשתמשים מריצים אפליקציה שמשתמשת בגישה הזו, האפליקציה שולחת אותם ליומן כדי לסיים את הוספת האירוע. כוונת האירוע INSERT משתמשת בשדות נוספים כדי לאכלס מראש טופס עם פרטי האירוע ביומן. לאחר מכן, המשתמשים יוכלו לבטל את האירוע, לערוך את הטופס לפי הצורך או לשמור את האירוע ביומן שלהם.

לפניכם קטע קוד שמתזמן אירוע ב-19 בינואר 2012, שיימשך מ-7:30 עד 8:30. שימו לב לנקודות הבאות לגבי קטע הקוד הזה:

Kotlin

val startMillis: Long = Calendar.getInstance().run {
    set(2012, 0, 19, 7, 30)
    timeInMillis
}
val endMillis: Long = Calendar.getInstance().run {
    set(2012, 0, 19, 8, 30)
    timeInMillis
}
val intent = Intent(Intent.ACTION_INSERT)
        .setData(CalendarContract.Events.CONTENT_URI)
        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis)
        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis)
        .putExtra(CalendarContract.Events.TITLE, "Yoga")
        .putExtra(CalendarContract.Events.DESCRIPTION, "Group class")
        .putExtra(CalendarContract.Events.EVENT_LOCATION, "The gym")
        .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY)
        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com")
startActivity(intent)

Java

Calendar beginTime = Calendar.getInstance();
beginTime.set(2012, 0, 19, 7, 30);
Calendar endTime = Calendar.getInstance();
endTime.set(2012, 0, 19, 8, 30);
Intent intent = new Intent(Intent.ACTION_INSERT)
        .setData(Events.CONTENT_URI)
        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
        .putExtra(Events.TITLE, "Yoga")
        .putExtra(Events.DESCRIPTION, "Group class")
        .putExtra(Events.EVENT_LOCATION, "The gym")
        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
startActivity(intent);

שימוש בכוונה לערוך אירוע

אפשר לעדכן אירוע ישירות, כפי שמתואר במאמר עדכון אירועים. אבל השימוש ב-Intent EDIT מאפשר לאפליקציה שאין לה הרשאה להעביר את העריכה של האירוע לאפליקציית יומן Google. כשהמשתמשים מסיימים לערוך את האירוע ביומן, הם מוחזרים לאפליקציה המקורית.

הנה דוגמה ל-Intent שמגדירה כותרת חדשה לאירוע מסוים ומאפשרת למשתמשים לערוך את האירוע ביומן.

Kotlin

val eventID: Long = 208
val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID)
val intent = Intent(Intent.ACTION_EDIT)
        .setData(uri)
        .putExtra(CalendarContract.Events.TITLE, "My New Title")
startActivity(intent)

Java

long eventID = 208;
Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
Intent intent = new Intent(Intent.ACTION_EDIT)
    .setData(uri)
    .putExtra(Events.TITLE, "My New Title");
startActivity(intent);

שימוש בכוונות כדי להציג נתונים מהיומן

ל-Calendar Provider יש שתי דרכים שונות להשתמש ב-Intent‏ VIEW:

  • כדי לפתוח את יומן Google בתאריך מסוים.
  • כדי להציג אירוע.

הדוגמה הבאה מראה איך לפתוח את היומן בתאריך מסוים:

Kotlin

val startMillis: Long
...
val builder: Uri.Builder = CalendarContract.CONTENT_URI.buildUpon()
        .appendPath("time")
ContentUris.appendId(builder, startMillis)
val intent = Intent(Intent.ACTION_VIEW)
        .setData(builder.build())
startActivity(intent)

Java

// A date-time specified in milliseconds since the epoch.
long startMillis;
...
Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
builder.appendPath("time");
ContentUris.appendId(builder, startMillis);
Intent intent = new Intent(Intent.ACTION_VIEW)
    .setData(builder.build());
startActivity(intent);

דוגמה לאופן שבו פותחים אירוע לצפייה:

Kotlin

val eventID: Long = 208
...
val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID)
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent)

Java

long eventID = 208;
...
Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
Intent intent = new Intent(Intent.ACTION_VIEW)
   .setData(uri);
startActivity(intent);

מתאמי סנכרון

יש הבדלים קלים בלבד באופן שבו אפליקציה ומתאמת סנכרון ניגשים לספק היומן:

  • כדי לציין שהוא מתאם סנכרון, מתאם סנכרון צריך להגדיר את CALLER_IS_SYNCADAPTER כ-true.
  • מתאם סנכרון צריך לספק ACCOUNT_NAME ו-ACCOUNT_TYPE כפרמטרים של שאילתה ב-URI.
  • למתאמי סנכרון יש הרשאת כתיבה לעמודות רבות יותר מאשר לאפליקציות או לווידג'טים. לדוגמה, אפליקציה יכולה לשנות רק כמה מאפיינים של יומן, כמו השם, שם התצוגה, הגדרת החשיפה והאפשרות לסנכרן את היומן. לעומת זאת, למתאמי סנכרון יש גישה לא רק לעמודות האלה, אלא גם לעמודות רבות אחרות, כמו צבע היומן, אזור הזמן, רמת הגישה, המיקום ועוד. עם זאת, מתאם סנכרון מוגבל ל-ACCOUNT_NAME ול-ACCOUNT_TYPE שצוינו בו.

הנה שיטה עוזרת שניתן להשתמש בה כדי להחזיר URI לשימוש עם מתאם סנכרון:

Kotlin

fun asSyncAdapter(uri: Uri, account: String, accountType: String): Uri {
    return uri.buildUpon()
            .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
            .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account)
            .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, accountType).build()
}

Java

static Uri asSyncAdapter(Uri uri, String account, String accountType) {
    return uri.buildUpon()
        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
 }