안드로이드 4대 컴포넌트 - Content Provider_4(calendar provider)

권민주·2025년 1월 22일

안드로이드

목록 보기
12/23
post-thumbnail

안드로이드에서 제공하는 provider 중 하나인 calendar provider에 대해 설명하겠습니다.


1. Calendar provider란

1)content provider?

Content provider는 데이터를 저장하고 이를 애플리케이션이 접근할 수 있도록 합니다. Android 플랫폼에서 제공하는 content provider는 일반적으로 관계형 데이터베이스 모델을 기반으로 테이블 형태의 데이터를 가집니다. 그렇기에 각 행은 record을 뜻하고 각 열은 특정 유형과 의미를 가지는 데이터입니다.

모든 content provider는 자신의 데이터를 고유하게 식별하는 공개 URI를 가지며 Uri 객체 형태입니다. 여러 데이터 혹은 테이블을 제어하는 provider는 각 데이터에 대한 별도의 URI를 가집니다. Provider의 모든 URI는 "content://"로 시작하는 문자열 형태입니다. 이 식별자를 통해 provider에 의해 제어되는 데이터로 인식합니다. 또한 각 class나 테이블에 대한 URI의 상수를 정의합니다. 이러한 URI는 <class\>.CONTENT_URI 형식을 가집니다. 예를 들어 Events.CONTENT_URI처럼 표현합니다.

2)calendar provider?

Calendar provider는 사용자의 calendar 일정을 저장합니다. Calendar provider API를 통해 calendar, 이벤트, 참석자, 알림 등에 대한 쿼리, 삽입, 업데이트 및 삭제 작업을 수행할 수 있습니다. Calendar provider API는 애플리케이션과 동기화 adapter에서 사용할 수 있습니다. 어디에서 사용하느냐에 따라 사용 방법이 달라집니다. 저희는 애플리케이션에서 사용하는 법을 알아볼겁니다. 애플리케이션과 동기화 adapter는 calendar provider API를 사용하여 사용자의 calendar 데이터를 저장하는 데이터베이스 테이블에 대한 읽기/쓰기 접근 권한을 얻을 수 있습니다.

일반적으로 calendar의 데이터를 읽거나 쓰려면 manifest에 적절한 권한이 포함되어야 합니다. 작업을 더 쉽게 수행할 수 있도록 calendar provider는 관련된 여러 intent를 제공합니다. 이러한 intent는 삽입, 보기 및 편집하도록 calendar 앱으로 이동시킵니다. Calendar 앱에서의 작업이 끝나면 사용자는 원래의 앱으로 돌아가게 됩니다. 따라서 애플리케이션이 권한을 요청할 필요도 없고 이벤트를 보거나 생성할 수 있는 UI를 제공할 필요도 없습니다.

위의 그림은 calendar provider 데이터 모델을 보여줍니다. 주요 테이블과 서로 연결된 필드를 보여줍니다. 사용자는 여러 개의 calendar를 가질 수 있으며 다양한 유형의 계정과 연결할 수 있습니다. CalendarContract는 calendar와 이벤트에 관련된 정보의 데이터 모델을 정의합니다. 해당 데이터는 다음과 같습니다.

  • CalendarContract.Calendars
    달력 정보 포함. 테이블의 각 행은 이름, 색상, 동기화 정보 등 단일 달력에 대한 세부 정보가 포함

  • CalendarContract.Events
    이벤트 정보 포함. 테이블의 각 행은 이벤트 이름, 위치, 시작 시간, 종료 시간 등 단일 이벤트에 대한 정보가 포함. 일회용 또는 반복 가능. 별도의 테이블에 참석자, 알림, 기타 속성이 저장. 각 행에는 이벤트 테이블의 _ID를 참조하는 EVENT_ID 존재.

  • CalendarContract.Instances
    이벤트의 시작 및 종료 시간 포함. 테이블의 각 행은 단일 발생의 이벤트에 대한 정보가 포함. 이벤트와 instance에 1:1로 매핑. 반복 이벤트의 경우 해당 이벤트의 반복 수 만큼 여러 행이 자동으로 생성.

  • CalendarContract.Attendees
    이벤트 참석자(손님) 정보 포함. 테이블의 각 행은 이벤트에 대한 단일 참석자에 대한 정보가 포함. 참석자 유형과 해당 이벤트 참석 응답 지정.

  • CalendarContract.Reminders
    알람/알림 데이터 포함. 테이블의 각 행은 이벤트에 대한 단일 알람 정보가 포함. 한 이벤트에 여러 알림 설정 가능. 이벤트 당 최대 알림 수는 해당 calendar의 동기화 adapter에 설정된 MAX_REMINERDER으로 지정. 알림은 이벤트 이전의 시간을 분 단위로 지정하며, 사용자가 어떻게 알림을 받을지를 결정하는 방법 포함.

Calendar provider는 유연하고 강력하게 설계되었습니다. 또한 좋은 사용자 경험을 제공하고 calendar와 calendar 데이터의 무결성 보호를 중요시합니다. 이를 위해 API 사용에 다음 사항을 참고해야합니다.

  • Calendar 이벤트 삽입, 업데이트, 보기
    Calendar provider에서 이벤트를 직접 삽입, 수정, 읽기 위해서는 적절한 권한이 필요. 하지만 calendar의 전체 기능이나 동기화 adapter 사용이 아니라면 intent을 통해서도 충분히 해결. 해당 intent는 안드로이드의 calendar 앱에서 지원하며 읽기와 쓰기 작업을 도와줌. Intent를 사용하면 calendar 앱으로 이동되며 미리 작성된 양식을 바탕으로 작업(이벤트 생성, 수정 등)을 수행하도록 요청. 사용자가 작업을 완료하게 되면 원래의 애플리케이션으로 돌아옴. Intent를 통해 calendar 앱에서 작업을 수행하도록 설계하면 사용자에게 일관되고 안정적인 UI를 제공할 수 있기 때문에 권장되는 방식.

  • 동기화 adapter
    동기화 adapter는 사용자 기기의 calendar 데이터를 다른 서버나 데이터와 동기화. CalendarContract.Calendars와 CalendarContract.Events 테이블에는 동기화 adapter 전용으로 사용할 수 있는 열이 존재. 해당 열을 provider나 일반 애플리케이션에서 수정 불가능. 어차피 동기화 adapter로 접근하지 않으면 보이지 않기는 함.

2. 사용자 권한

Calendar의 데이터를 읽기 위해서는 애플리케이션의 manifest 파일에 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>

3. Calendar 테이블

1)테이블 열

CalendarContract.Calendars 테이블에는 개별 calendar에 대한 상세 정보를 포함하고 있습니다. 해당 열은 대표적으로 다음과 같습니다. 이때 애플리케이션과 동기화 adapter 모두에서 작성할 수 있습니다.

열 이름설명
NAMECalendar의 이름
CALENDAR_DISPLAY_NAME사용자에게 보여지는 calendar 이름
VISIBLECalendar가 보여질 여부를 선택하는 booelan. 값이 0일 경우,
해당 calendar와 관련된 event들이 숨겨짐. 값이 1일 경우,
해당 calendar와 관련된 이벤트들이 보여짐.
CalendarContract.Instances 테이블의 행 생성에 영향을 줌.
SYNC_EVENTSCalendar를 동기화하고 이벤트를 기기에 저장할 여부를 선택하는 boolean.
값이 0일 경우, calendar를 동기화하지 않으며 기기에 저장하지 않음.
값이 1일 경우, 이벤트를 동기화하고 기기에 저장함.

🛑🛑🛑
전체 열 목록
https://developer.android.com/reference/android/provider/CalendarContract.Calendars#calendar-columns

2)모든 연산에 계정 유형 포함 필수

Calendars.ACCOUNT_NAME을 기준으로 쿼리할 경우, 반드시 Calendars.ACCOUNT_TYPE도 selection에 포함해야 합니다. 왜냐하면 ACCOUNT_NAMEACCOUNT_TYPE이 함께 사용될 때만 고유하게 구별되기 때문입니다. ACCOUNT_TYPE는 계정이 AccountManager에 등록될 때 사용된 계정 인증자 문자열로 계정의 서비스 제공자를 구분합니다. 즉, 해당 계정이 Google인지 Microsoft인지 구별해줍니다. 또한, 디바이스 계정과 연결되지 않은 calendar를 위한 특별한 계정 유형인 ACCOUNT_TYPE_LOCAL이 존재합니다. ACCOUNT_TYPE_LOCAL계정은 동기화되지 않습니다.

3)calendar 쿼리

다음은 특정 사용자가 소유한 calendar를 가져오는 방법을 보여주는 예시입니다. 간단히 표현하기 위해 이 예시에서는 쿼리 작업을 UI 스레드에 실행합니다. 실제로는 메인 스레드가 아닌 비동기 스레드에서 수행되어야 합니다. 데이터를 읽는 것뿐만 아니라 수정하는 경우에는 AsyncQueryHandler를 참조해야합니다. ContentResolver를 비동기로 처리하기 위해 제공되는 헬퍼 클래스로 작업을 백그라운드에서 처리하여, UI 스레드가 차단되지 않도록 도와줍니다. onInsertComplete(), onUpdateComplete(), onDeleteComplete()를 제공하기 때문에 데이터 작업에 유용합니다. 이러한 이점으로 Loader와 달리 depcreated되지 않았습니다. Loader는 비동기적으로 데이터를 로드하는 클래스입니다. UI 관련 작업에 적합하여 list나 recyclerview에 자주 사용되었습니다.

// Projection 배열. 
//동적 조회 대신 배열 인덱스를 사용하면 성능이 향상
//동적 조회는 cursor.getColumnIndex()처럼 
//런타임에 데이터 이름이나 키 찾는 과정
public static final String[] EVENT_PROJECTION = new String[] {
    Calendars._ID,                           // 0
    Calendars.ACCOUNT_NAME,                  // 1
    Calendars.CALENDAR_DISPLAY_NAME,         // 2
    Calendars.OWNER_ACCOUNT                  // 3
};

//EVENT_PROJECTION 배열에 대한 인덱스.
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;

이제 쿼리를 구성합니다. Selection는 쿼리에 대한 기준을 지정합니다. 이 예제에서 쿼리는 ACCOUNT_NAME "hera@example.com ", ACCOUNT_TYPE "com.example", OWNER_ACCOUNT "hera@example.com "를 포함하는 calendar를 찾고 있습니다. 다른 사용자의 calendar를 보고 싶다면, OWNER_ACCOUNT를 생략하면 됩니다. 쿼리는 결과 데이터 탐색에 사용되는 Cursor 객체를 반환합니다.

// 쿼리 생성
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"};
// 쿼리 실행 후 Cursor 객체 반환
cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);

다음은 결과로 얻은 cursor를 통해 데이터를 얻을 겁니다. 위에서 Projection 배열 인덱스로 설정된 상수를 사용하여 각 필드의 값을 반환합니다.

//Cursor를 사용하여 반환된 레코드 사용
while (cur.moveToNext()) {
    long calID = 0;
    String displayName = null;
    String accountName = null;
    String ownerName = null;

    // 필드 값 얻기
    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);

    //필드 값 처리
   ...
}

4)calendar 수정

Calender를 업데이트하려면 calendar의 _ID가 필요합니다. withAppendId() 사용하여 Uri에 ID를 추가하거나 selection의 첫 번째 항목으로 제공할 수 있습니다. selection은 _id=?로 시작해야 하며, 첫 번째 selectionArg는 calendar의 _ID여야 합니다. 아래 예제에서는 withAppendId()을 사용하여 calendar의 표시 이름을 변경할 겁니다.

private static final String DEBUG_TAG = "MyActivity";
...
long calID = 2;
ContentValues values = new ContentValues();
// 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);

5)calendar 삽입

Calendar는 주로 동기화 adapter에 의해 관리되도록 설계하였기에 새 calendar를 삽입해야 할 때는 동기화 adapter로 수행해야 합니다. 대부분의 경우 애플리케이션은 표시 이름을 변경하는 등 겉보기로만 calendar를 변경할 수 있습니다. 로컬 calendar를 만들어야 하는 경우에도 동기화 adapter를 사용해야 합니다. 계정 유형을 ACCOUNT_TYPE_LOCAL으로 설정하여 calendar 삽입을 동기화 adapter로 수행합니다. ACCOUNT_TYPE_LOCAL은 기기의 계정과 연결되지 않은 calendar를 위한 특수 계정 유형입니다. 이러한 유형의 calendar는 서버에 동기화되지 않습니다. calendar를 삽입하는 경우를 제외하고는 로컬에서 사용한다면 동기화 adapter는 필요하지 않습니다.

4. Events 테이블

1)테이블 열

CalendarContract.Events 테이블에는 개별 event에 대한 상세 정보를 포함하고 있습니다. 삽입, 삭제, 업데이트를 하기 위해서는 애플리케이션의 manifest에 WRITE_CALENDAR 권한을 반드시 포함해야합니다. 해당 열은 대표적으로 다음과 같습니다. 이때 애플리케이션과 동기화 adapter 모두에서 작성할 수 있습니다.

열 이름설명
CALENDAR_IDevent가 속한 캘린더의 ID
ORGANIZERevent 주최자(소유자)의 이메일
TITLEevent 이름
EVENT_LOCATIONevent 열릴 장소
DESCRIPTIONevent 상세 정보
DTSTARTevent가 시작되는 시간을 UTC 기준으로 1970년 1월 1일 자정(Epoch Time)부터
경과된 시간을 밀리초로 나타낸 값
DTENDevent가 끝나는 시간을 UTC 기준으로 1970년 1월 1일 자정(Epoch Time)부터
경과된 시간을 밀리초로 나타낸 값
EVENT_TIMEZONEevent의 time zone(동일한 로컬 시간을 따르는 지역)
EVENT_END_TIMEZONEevent 끝나는 시간의 time zone
DURATIONevent의 지속 시간을 RFC5545 포맷으로 나타냄. 예를 들어 PT1H는
1시간 동안 지속, P2W는 2주 동안 지속.
+ 참고 사이트(https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.2.5)
ALL_DAY값이 1일 경우, 로컬 time zone를 기준으로 하루 종일 event 지속. 값이 0일 경우,
특정 시간에 시작하고 종료
되는 일반 이벤트
RRULEevent의 반복 규칙. 예를 들어 FREQ=WEEKLY;COUNT=10;WKST=SU는 매주
반복하며 총 10번 발생. 주의 시작일은 일요일.
+ 참고 사이트(https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.3)
RDATEevent의 반복 날짜. 일반적으로 RDATE는 RRULE과 함께 사용. 특정 날짜로
반복되는 모든 event의 목록을 정의.
+ 참고 사이트(https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.2)
AVAILABILITY해당 event가 바쁜 시간(다른 일정 추가가 불가)인지
여유시간
(다른 일정 추가가 가능)인지를 나타냄
GUESTS_CAN_MODIFY게스트가 event 수정 가능 여부
GUESTS_CAN_INVITE_OTHERS게스트가 다른 게스트 초대 가능 여부
GUESTS_CAN_SEE_GUESTS게스트가 attendees 목록 볼 수 있는 여부

🛑🛑🛑
전체 열 목록
https://developer.android.com/reference/android/provider/CalendarContract.Events#writing-to-events

2)events 추가

애플리케이션에 새로운 event를 추가할 때는 INSERT intent 사용을 권장합니다. 하지만 원한다면 직접 삽입할 수 있습니다. event 삽입에는 다음과 같은 규칙이 있습니다.

  • CALENDAR_IDDTSTART가 포함 필수

  • EVENT_TIMEZONE 포함 필수. 시스템에 설치된 timezone ID 목록을 얻으려면 getAvailableIDs() 사용. INSERT intent를 통해 이벤트를 삽입하는 경우에는 불필요. 이때는 기본 time zone 제공

  • 반복 이벤트가 아닌 경우, DTEND 포함 필수

  • 반복 이벤트인 경우, DURATION + RRUE 또는 DURATION +RDATE 포함 필수. INSERT intent를 통한 삽입에는 불필요. 이때는 RRUEDTSTARTDTEND 함께 사용 가능. 그럼 Calendar가 기간으로 자동 변환.

다음은 event 삽입 예시입니다. 단순화를 위해 UI 스레드에서 수행합니다. 실제로는 백그라운드 스레드에서 작업하기 위해 비동기 스레드에서 삽입 및 업데이트를 수행해야 합니다.

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);

// URI의 마지막 요소에서 event ID 가져오기
long eventID = Long.parseLong(uri.getLastPathSegment());
//
// event ID 처리
//
//

이 예제는 event가 생성된 후 event ID를 가져왔습니다. getLastPathSegment()을 통해 event ID를 얻는 방식이 가장 쉽습니다. event에 attendee 또는 reminder을 추가하는 등의 calendar 작업을 수행하려면 event ID가 필요합니다. 또한 필수 조건을 위해 CALENDAR_IDEVENT_TIMEZONE, DTSTART가 포함되었습니다. 반복 이벤트가 아니기 때문에 DTEND가 추가되었습니다.

3) event 업데이트

애플리케이션에서 사용자가 event를 편집할 때는 EDIT Intent 사용이 좋습니다. 그러나 필요한 경우에는 직접 편집할 수 있습니다. event 업데이트하려면 event의 _ID가 필요합니다. withAppendId() 사용하여 Uri에 ID를 추가하거나 selection의 첫 번째 항목으로 제공할 수 있습니다. selection은 _id=?로 시작해야 하며, 첫 번째 selectionArg는 event의 _ID여야 합니다. selection에서는 꼭 ID가 아니라도 조건을 사용하여 업데이트할 수도 있습니다. 다음은 event 업데이트의 예입니다. withAppendId()로 event 제목을 변경합니다.

private static final String DEBUG_TAG = "MyActivity";
...
long eventID = 188;
...
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
Uri updateUri = null;
// 이벤트의 새로운 제목
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);

4)event 삭제

event를 ID로 삭제하려면 URI에 추가된 ID를 사용하거나 selection을 사용해야 합니다. 첫번째의 경우에는 selection이 필요없습니다. 삭제에는 애플리케이션과 동기화 adapter의 두 가지 방식이 있습니다. 애플리케이션 삭제에서는 provider가 삭제된 열을 1로 설정하여 삭제 상태를 관리합니다. 이 플래그는 동기화 adapter에게 행이 삭제되었으며 이 변경 사항이 서버로 전파되어야 함을 알립니다. 반면, 동기화 adapter는 event와 관련된 모든 데이터를 데이터베이스에서 곧바로 제거합니다. 이 경우, 삭제 플래그를 사용하지 않습니다. 다음은 애플리케이션이 ID를 통해 event를 삭제하는 예제입니다.

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);

5. Attendees 테이블

1)테이블 열

CalendarContract.Attendees 테이블의 각 행은 event에 대한 단일 attendee를 포함하고 있습니다. query()를 호출하면 해당 EVENT_ID에 해당하는 event의 attendee 목록을 반환합니다. 이 EVENT_ID는 특정 event의 _ID와 일치해야 합니다. 새 attendee를 삽입할 때는 ATEUNTEE_NAME을 제외한 모든 필드를 포함해야 합니다.

열 이름설명
EVENT_IDevent ID 값
ATTENDEE_NAMEattendee 이름
ATTENDEE_EMAILattendee 이메일 주소
ATTENDEE_RELATIONSHIPevent에서 attendee 관계. 다음 중 하나를 가짐
(RELATIONSHIP_ATTENDEE, RELATIONSHIP_NONE,
RELATIONSHIP_ORGANIZER, RELATIONSHIP_PERFORMER,
RELATIONSHIP_SPEAKER)
ATTENDEE_TYPEattendee 유형. 다음 중 하나를 가짐
(TYPE_REQUIRED, TYPE_OPTIONAL)
ATTENDEE_STATUSattendee의 출석 상태. 다음 중 하나를 가짐
(ATTENDEE_STATUS_ACCEPTED, ATTENDEE_STATUS_DECLINED,
ATTENDEE_STATUS_INVITED, ATTENDEE_STATUS_NONE,
ATTENDEE_STATUS_TENTATIVE

2)attendee 추가

다음은 event에 attendee 한 명을 추가하는 예시입니다. EVENT_ID 사용을 잊지 말아야 합니다.

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);

6.Reminders 테이블

1)테이블 열

CalendarContract.Reminders 테이블의 각 행은 event에 대한 단일 reminder를 포함하고 있습니다. qeury()를 호출하면 해당 EVENT_ID와 일치하는 event의 reminder 목록을 반환합니다. 새 reminder을 삽입할 때는 모든 reminder 필드를 포함해야 합니다. 동기화 adapter는 CalendarContract.Calendars 테이블에서 지원하는 reminder 유형을 지정해야 합니다. 동기화 adapter에서만 직접적으로 설정할 수 있는 필드며 애플리케이션은 읽어서 참조만 가능합니다. ALWALLED_REMINERDS 필드를 통해 reminder 유형을 지정할 수 있고 다음 값이 있습니다. Reminders.METHOD_DEFAULT, Reminders.METHOD_ALERT, Reminders.METHOD_EMAIL, Reminders.METHOD_SMS, Reminders.METHOD_ALARM. "#,#,#"형식으로 여러 유형을 지정할 수 있습니다.

열 이름설명
EVENT_IDevent ID값
MINUTESreminder이 실행되기 전까지의 시간
METHOD서버에 설정된 reminder 방식. 다음 중 하나의 값을 가집니다
(METHOD_ALERT, METHOD_DEFAULT, ETHOD_EMAIL, METHOD_SMS)

2)reminder 추가

이 예제는 event에 reminder을 추가합니다. reminder은 이벤트 15분 전에 실행됩니다.

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);

7. Instance 테이블

1)테이블 열

CalendarContract.Instances 테이블의 각 행은 event에 대한 단일의 시작과 종료 시간를 포함하고 있습니다. Instance 테이블은 쓸 수 없으며, 오직 event 발생 정보를 조회하는 용도로만 사용됩니다.다음 표에는 Instance에 대해 쿼리할 수 있는 필드 중 일부가 나열되어 있습니다. time zone은 KEY_TIMEZONE_TYPEKEY_TIMEZONE_INSTANCE에 의해 정의됩니다.

열 이름설명
BEGINInstance의 시작 시간(UTC 밀리초)
ENDInstance의 종료 시간(UTC 밀리초)
END_DAYCalendar의 time zone을 기준으로 종료일을 줄리안 날짜 변환
(일수로 변환 후 시간은 소수점으로 변환)
END_MINUTECalendar의 time zone을 기준으로 자정부터 Instance 종료 시간까지의 분
EVENT_ID해당 Instance에 대한 이벤트 ID
START_DAYCalendar의 time zone을 기준으로 시작일을 줄리안 날짜 변환
(일수로 변환 후 시간은 소수점으로 변환)
START_MINUTECalendar의 time zone을 기준으로 자정부터 Instance 시작 시간까지의 분

🛑🛑🛑
전체 열 목록
https://developer.android.com/reference/android/provider/CalendarContract.Instances

2)Instances 테이블 쿼리

Instance 테이블을 쿼리하려면 URI에서 시간 범위을 지정해야 합니다. 이 예제에서는 CalendarContract.InstancesCalendarContract.EventsColumns 인터페이스를 구현하여 TITLE 필드에 접근합니다. 즉, TITLECalendarContract.Instances 테이블의 원본 데이터를 직접 쿼리하는 것이 아니라, 데이터베이스 view를 통해 반환됩니다. 이때 view는 데이터베이스에서 원본 테이블의 데이터를 가공하거나 결합하여 제공하는 가상의 테이블을 말합니다.

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
  };

//projection 배열 인덱스
private static final int PROJECTION_ID_INDEX = 0;
private static final int PROJECTION_BEGIN_INDEX = 1;
private static final int PROJECTION_TITLE_INDEX = 2;
...

// 반복 이벤트에 대한 instance를 검색할 날짜 범위 지정
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();

// Instances 테이블에서 해당 인스턴스와 일치하는 반복 이벤트의 ID
String selection = Instances.EVENT_ID + " = ?";
String[] selectionArgs = new String[] {"207"};

// 원하는 날짜 범위로 쿼리 구성
Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
ContentUris.appendId(builder, startMillis);
ContentUris.appendId(builder, endMillis);

// 쿼리 실행
cur =  cr.query(builder.build(),
    INSTANCE_PROJECTION,
    selection,
    selectionArgs,
    null);

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

    //필드 값 얻기
    eventID = cur.getLong(PROJECTION_ID_INDEX);
    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
    title = cur.getString(PROJECTION_TITLE_INDEX);

    // 값 처리
    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()));
    }
 }

8. Calendar intent

1)지원하는 intent 값

애플리케이션은 calendar 데이터를 읽고 쓰기 위한 권한 permission이 필요하지 않습니다. 이것이 가능한 이유는 android의 calendar 애플리케이션에서 지원하는 intent를 사용하여 읽기 및 쓰기 작업을 하였기 때문이죠. 아래 표는 Calendar Provider에서 지원하는 intent를 나열한 것입니다

ActionURIDescriptionExtras
VIEWcontent://com.android.calendar/time/<ms_since_epoch
(CalendarContract.CONTENT_URI
사용하여 참조 가능)
<ms_since_epoch> 으로 명시된 시간의 calender 열기None
VIEWcontent://com.android.calendar/ events/<event_id>
(Events.CONTENT_URI 사용하여
참조 가능)
<event_id>으로
명시된 event 보기
CalendarContract.EXTRA_EVENT_
BEGIN_TIME, CalendarContract.EXTRA_EVENT_
END_TIME
EDITcontent://com.android.calendar/ events/<event_id>
(Events.CONTENT_URI 사용하여
참조 가능)
<event_id>으로
명시된 event 편집
CalendarContract.EXTRA_EVENT_ BEGIN_TIME, CalendarContract.EXTRA_EVENT_ END_TIME
EDIT, INSERTcontent://com.android.calendar/events (Events.CONTENT_URI 사용하여 참조 가능)event 생성아래 표 참고

Calendar Provider에서 지원하는 intent extras를 나열한 것입니다

extras설명
Events.TITLEevent 이름
Events.EVENT_LOCATIONevent 장소
Events.DESCRIPTIONevent 설명
Intent.EXTRA_EMAIL초대할 사람들의 이메일 주소를 쉼표로 구분된 목록
Events.RRULEevent의 반복 주기 규칙
Events.ACCESS_LEVELevent가 공적인지 사적인지
Events.AVAILABILITY해당 시간에 다른 일정 추가 가능 여부
CalendarContract.EXTRA_EVENT_BEGIN_TIMEEpoch Time으로부터 event 시작 시간을 밀리초로 나타낸 값
CalendarContract.EXTRA_EVENT_END_TIMEEpoch Time으로부터 event 끝나는 시간을 밀리초로 나타낸 값
CalendarContract.EXTRA_EVENT_ALL_DAYevent가 하루종일 진행되는 여부. true 또는 false으로 값 부여

2)intent 사용하여 event 삽입

INSERT intent를 사용하면 event 삽입 작업을 애플리케이션 대신 calendar 자체에 넘겨줄 수 있습니다. 이 접근 방식을 사용하면 애플리케이션의 manifest 파일에 WRITE_CALENDAR 권한이 포함되어 있지 않아도 됩니다. 사용자가 intent를 사용하는 애플리케이션을 실행하면 애플리케이션은 calendar에 event 추가를 완료합니다. INSERT intent는 extra 필드를 사용하여 calendar에 event 세부 정보가 포함된 양식을 미리 입력합니다. 그런 다음 사용자는 event를 취소하거나 필요에 따라 양식을 편집하거나 calendar에 이벤트를 저장할 수 있습니다.

다음은 2012년 1월 19일 오전 7시 30분부터 8시 30분까지 진행되는 이벤트를 예약하는 코드 예제입니다. 이 코드 예제에 대해 다음 사항에 유의하세요:

  • Events.CONTENT_URI를 URI로 지정합니다.
  • CalendarContract.EXTRA_EVENT_BEGIN_TIMECalendarContract.EXTRA_EVENT_END_TIME는 extra 필드를 사용하여 event 시간을 미리 입력합니다. 이 시간의 값은 UTC 기준으로 1970년 1월 1일 자정(Epoch Time)부터 경과된 시간을 밀리초로 나타냅니다.
  • Intent.EXTRA_EMAIL extra 필드를 사용하여 쉼표로 구분된 초대자의 이메일 주소 목록을 제공합니다.
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);

3)intent 사용하여 event 편집

Event를 직접 업데이트할 수 있습니다. 하지만 EDIT Intent를 사용하면 event 편집 권한이 없는 애플리케이션이 calendar에 권한을 넘겨줄 수 있습니다. 사용자가 calendar에서 event 편집을 완료하면 원래 애플리케이션으로 돌아갑니다. 다음은 특정 event에 대한 이름을 편집할 수 있도록 하는 intent의 예입니다.

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);

4)intent 사용하여 event 보기

Calendar Provide는 VIEW intent를 사용하는 두 가지 방법을 제공합니다. 특정 날짜의 calendar 열기와 특정 event 보기 입니다.

다음은 특정 날짜의 calendar를 여는 방법을 보여주는 예입니다.

// 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);

다음은 특정 event를 보는 방법을 보여주는 예입니다.

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

9. 동기화 adapter

애플리케이션과 동기화 adapter가 calendar provider에 접근하는 방식에는 약간의 차이점만 있습니다.

  • 동기화 adapter는 CALLER_IS_SYNCADAPTER를 true로 설정하여 동기화 adapter임을 지정해야 합니다.
  • 동기화 adapter는 URI에서 쿼리 매개변수로 ACCOUNT_NAMEACCOUNT_TYPE을 제공해야 합니다.
  • 동기화 adapter는 애플리케이션이나 위젯보다 더 많은 열에 대한 쓰기 접근 권한을 가지고 있습니다. 예를 들어, 애플리케이션은 calendar의 이름, 표시 이름, 가시성 설정, calendar 동기화 여부와 같은 몇 가지 특성에만 수정할 수 있습니다. 이에 비해 동기화 adapter는 해당 열뿐만 아니라 캘린더 색상, time zone, 접근 수준, 위치 등 많은 열에도 접근할 수 있습니다. 그러나 동기화 adapter는 지정한 ACCOUNT_NAMEACCOUNT_TYPE에서의 열 접근만 가능합니다. 동기화 adapter라고 모든 계정에 관한 접근이 가능한 건 아닙니다.

다음은 동기화 adapter와 함께 사용하여 URI를 반환에 도움을 주는 메서드입니다

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();
 }

출처: https://developer.android.com/

이전 CONTENT PROVIDER 포스트
<contentProvider_1(개념)>
<contentProvider_2(생성 과정)>
<contentProvider_3(예제)>

다음 CONTENT PROVIDER 포스트
<contentProvider_5(calendar 예제)>
<contentProvider_6(비동기 방식)>
<contentProvider_7(contact)>

profile
안드로이드 개발자:D

0개의 댓글