CalendarSyncTestingBase.java revision b55344ddfebfccbdb07ed7a209e91819a5e9925d
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.providers.calendar; 18 19import android.content.ContentResolver; 20import android.content.ContentUris; 21import android.content.ContentValues; 22import android.content.Context; 23import android.database.Cursor; 24import android.net.Uri; 25import android.text.format.DateUtils; 26import android.text.format.Time; 27import android.provider.Calendar; 28import android.test.SyncBaseInstrumentation; 29import android.util.Log; 30import com.google.android.collect.Maps; 31import com.google.android.googlelogin.GoogleLoginServiceBlockingHelper; 32import com.google.android.googlelogin.GoogleLoginServiceNotFoundException; 33 34import java.util.HashSet; 35import java.util.Map; 36import java.util.Set; 37 38/** 39 * Calendar Sync base tests. Testing creation of setup, syncing calendar, inserting, 40 * deleting and editing events. 41 */ 42public class CalendarSyncTestingBase extends SyncBaseInstrumentation { 43 protected Context mTargetContext; 44 protected String mAccount; 45 protected ContentResolver mResolver; 46 protected Uri mEventsUri = Uri.parse("content://calendar/events"); 47 48 static final String TAG = "calendar"; 49 static final String DEFAULT_TIMEZONE = "America/Los_Angeles"; 50 static final Set<String> EVENT_COLUMNS_TO_SKIP = new HashSet<String>(); 51 static final Set<String> ATTENDEES_COLUMNS_TO_SKIP = new HashSet<String>(); 52 static final Set<String> CALENDARS_COLUMNS_TO_SKIP = new HashSet<String>(); 53 static final Set<String> INSTANCES_COLUMNS_TO_SKIP = new HashSet<String>(); 54 55 static { 56 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._ID); 57 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_TIME); 58 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_VERSION); 59 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_LOCAL_ID); 60 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_DIRTY); 61 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_MARK); 62 ATTENDEES_COLUMNS_TO_SKIP.add(Calendar.Attendees._ID); 63 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._ID); 64 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_TIME); 65 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_VERSION); 66 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_LOCAL_ID); 67 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_DIRTY); 68 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_MARK); 69 INSTANCES_COLUMNS_TO_SKIP.add(Calendar.Instances._ID); 70 } 71 72 @Override 73 protected void setUp() throws Exception { 74 super.setUp(); 75 mTargetContext = getInstrumentation().getTargetContext(); 76 mAccount = getAccount(); 77 mResolver = mTargetContext.getContentResolver(); 78 } 79 80 /** 81 * A simple method that syncs the calendar provider. 82 * @throws Exception 83 */ 84 protected void syncCalendar() throws Exception { 85 86 cancelSyncsandDisableAutoSync(); 87 syncProvider(Calendar.CONTENT_URI, mAccount, Calendar.AUTHORITY); 88 } 89 90 /** 91 * Creates a new event in the default calendar. 92 * @param event Event to be created. 93 * @return Uri of the created event. 94 * @throws Exception 95 */ 96 protected Uri insertEvent(EventInfo event) throws Exception { 97 Log.i(TAG, "Get Dafault Calender Id"); 98 return insertEvent(getDefaultCalendarId(), event); 99 } 100 101 /** 102 * Creates a new event in the given calendarId. 103 * @param calendarId Calendar to be used. 104 * @param event Event to be created. 105 * @return Uri of the event created. 106 * @throws Exception 107 */ 108 protected Uri insertEvent(int calendarId, EventInfo event) throws Exception{ 109 ContentValues m = new ContentValues(); 110 m.put(Calendar.Events.CALENDAR_ID, calendarId); 111 m.put(Calendar.Events.TITLE, event.mTitle); 112 m.put(Calendar.Events.DTSTART, event.mDtstart); 113 m.put(Calendar.Events.ALL_DAY, event.mAllDay ? 1 : 0); 114 115 if (event.mRrule == null) { 116 // This is a normal event 117 m.put(Calendar.Events.DTEND, event.mDtend); 118 } else { 119 // This is a repeating event 120 m.put(Calendar.Events.RRULE, event.mRrule); 121 m.put(Calendar.Events.DURATION, event.mDuration); 122 } 123 124 if (event.mDescription != null) { 125 m.put(Calendar.Events.DESCRIPTION, event.mDescription); 126 } 127 if (event.mTimezone != null) { 128 m.put(Calendar.Events.EVENT_TIMEZONE, event.mTimezone); 129 } 130 131 Log.v(TAG, "Test Calendar Event"); 132 Uri url = mResolver.insert(mEventsUri, m); 133 Log.v(TAG, "Insert Calendar Event"); 134 syncCalendar(); 135 Log.v(TAG, "Test Sync Calendar"); 136 return url; 137 } 138 139 /** 140 * Edits the given event. 141 * @param eventId EventID of the event to be edited. 142 * @param event Edited event details. 143 * @throws Exception 144 */ 145 protected void editEvent(long eventId, EventInfo event) throws Exception { 146 ContentValues values = new ContentValues(); 147 values.put(Calendar.Events.TITLE, event.mTitle); 148 values.put(Calendar.Events.DTSTART, event.mDtstart); 149 values.put(Calendar.Events.DTEND, event.mDtend); 150 values.put(Calendar.Events.ALL_DAY, event.mAllDay ? 1 : 0); 151 152 if (event.mDescription != null) { 153 values.put(Calendar.Events.DESCRIPTION, event.mDescription); 154 } else if(event.mLocation != null) { 155 values.put(Calendar.Events.EVENT_LOCATION, event.mLocation); 156 } else if(event.mTitle != null) { 157 values.put(Calendar.Events.TITLE, event.mTitle); 158 } 159 else { 160 throw new RuntimeException("Unsupported: description or location must be specified"); 161 } 162 163 Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId); 164 mResolver.update(uri, values, null, null); 165 syncCalendar(); 166 } 167 168 /** 169 * Deletes a given event. 170 * @param uri 171 * @throws Exception 172 */ 173 protected void deleteEvent(Uri uri) throws Exception { 174 mResolver.delete(uri, null, null); 175 syncCalendar(); 176 } 177 178 /** 179 * Inserts a new calendar. 180 * @param name 181 * @param timezone 182 * @param calendarUrl 183 * @throws Exception 184 */ 185 protected void insertCalendar(String name, String timezone, String calendarUrl) 186 throws Exception { 187 ContentValues values = new ContentValues(); 188 189 values.put(Calendar.Calendars._SYNC_ACCOUNT, getAccount()); 190 values.put(Calendar.Calendars.URL, calendarUrl); 191 values.put(Calendar.Calendars.NAME, name); 192 values.put(Calendar.Calendars.DISPLAY_NAME, name); 193 values.put(Calendar.Calendars.SYNC_EVENTS, 1); 194 values.put(Calendar.Calendars.SELECTED, 1); 195 values.put(Calendar.Calendars.HIDDEN, 0); 196 values.put(Calendar.Calendars.COLOR, "0xff123456" /* blue */); 197 values.put(Calendar.Calendars.ACCESS_LEVEL, Calendar.Calendars.OWNER_ACCESS); 198 values.put(Calendar.Calendars.TIMEZONE, timezone); 199 mResolver.insert(Calendar.Calendars.CONTENT_URI, values); 200 syncCalendar(); 201 } 202 203 /** 204 * Returns a fresh count of events. 205 * @return 206 */ 207 protected int getEventsCount() { 208 Cursor cursor; 209 cursor = mResolver.query(mEventsUri, null, null, null, null); 210 return cursor.getCount(); 211 } 212 213 /** 214 * Returns the ID of the default calendar. 215 * @return 216 */ 217 protected int getDefaultCalendarId() { 218 Cursor calendarsCursor; 219 calendarsCursor = mResolver.query(Calendar.Calendars.CONTENT_URI, null, null, null, null); 220 calendarsCursor.moveToNext(); 221 return calendarsCursor.getInt(calendarsCursor.getColumnIndex("_id")); 222 } 223 224 /** 225 * This class stores all the useful information about an event. 226 */ 227 protected class EventInfo { 228 String mTitle; 229 String mDescription; 230 String mLocation; 231 String mTimezone; 232 boolean mAllDay; 233 long mDtstart; 234 long mDtend; 235 String mRrule; 236 String mDuration; 237 String mOriginalTitle; 238 long mOriginalInstance; 239 int mSyncId; 240 241 // Constructor for normal events, using the default timezone 242 public EventInfo(String title, String startDate, String endDate, 243 boolean allDay) { 244 init(title, startDate, endDate, allDay, DEFAULT_TIMEZONE); 245 } 246 247 public EventInfo(String title, long startDate, long endDate, 248 boolean allDay) { 249 mTitle = title; 250 mTimezone = DEFAULT_TIMEZONE; 251 mDtstart = startDate; 252 mDtend = endDate; 253 mDuration = null; 254 mRrule = null; 255 mAllDay = allDay; 256 } 257 258 public EventInfo(String title, long startDate, long endDate, 259 boolean allDay, String description) { 260 mTitle = title; 261 mTimezone = DEFAULT_TIMEZONE; 262 mDtstart = startDate; 263 mDtend = endDate; 264 mDuration = null; 265 mRrule = null; 266 mAllDay = allDay; 267 mDescription = description; 268 } 269 270 271 // Constructor for normal events, specifying the timezone 272 public EventInfo(String title, String startDate, String endDate, 273 boolean allDay, String timezone) { 274 init(title, startDate, endDate, allDay, timezone); 275 } 276 277 public void init(String title, String startDate, String endDate, 278 boolean allDay, String timezone) { 279 mTitle = title; 280 Time time = new Time(); 281 if (allDay) { 282 time.timezone = Time.TIMEZONE_UTC; 283 } else if (timezone != null) { 284 time.timezone = timezone; 285 } 286 mTimezone = time.timezone; 287 time.parse3339(startDate); 288 mDtstart = time.toMillis(false /* use isDst */); 289 time.parse3339(endDate); 290 mDtend = time.toMillis(false /* use isDst */); 291 mDuration = null; 292 mRrule = null; 293 mAllDay = allDay; 294 } 295 296 /** 297 * Constructor for repeating events, using the default time zone. 298 * @param title 299 * @param description 300 * @param startDate 301 * @param endDate 302 * @param rrule 303 * @param allDay 304 */ 305 public EventInfo(String title, String description, String startDate, String endDate, 306 String rrule, boolean allDay) { 307 init(title, description, startDate, endDate, rrule, allDay, DEFAULT_TIMEZONE); 308 } 309 310 /** 311 * Constructor for repeating events, using the specific time zone. 312 * @param title 313 * @param description 314 * @param startDate 315 * @param endDate 316 * @param rrule 317 * @param allDay 318 * @param timezone 319 */ 320 public EventInfo(String title, String description, String startDate, String endDate, 321 String rrule, boolean allDay, String timezone) { 322 init(title, description, startDate, endDate, rrule, allDay, timezone); 323 } 324 325 public void init(String title, String description, String startDate, String endDate, 326 String rrule, boolean allDay, String timezone) { 327 mTitle = title; 328 mDescription = description; 329 Time time = new Time(); 330 if (allDay) { 331 time.timezone = Time.TIMEZONE_UTC; 332 } else if (timezone != null) { 333 time.timezone = timezone; 334 } 335 mTimezone = time.timezone; 336 time.parse3339(startDate); 337 mDtstart = time.toMillis(false /* use isDst */); 338 if (endDate != null) { 339 time.parse3339(endDate); 340 mDtend = time.toMillis(false /* use isDst */); 341 } 342 if (allDay) { 343 long days = 1; 344 if (endDate != null) { 345 days = (mDtend - mDtstart) / DateUtils.DAY_IN_MILLIS; 346 } 347 mDuration = "P" + days + "D"; 348 } else { 349 long seconds = (mDtend - mDtstart) / DateUtils.SECOND_IN_MILLIS; 350 mDuration = "P" + seconds + "S"; 351 } 352 mRrule = rrule; 353 mAllDay = allDay; 354 } 355 356 // Constructor for recurrence exceptions, using the default timezone 357 public EventInfo(String originalTitle, String originalInstance, String title, 358 String description, String startDate, String endDate, boolean allDay) { 359 init(originalTitle, originalInstance, 360 title, description, startDate, endDate, allDay, DEFAULT_TIMEZONE); 361 } 362 363 public void init(String originalTitle, String originalInstance, 364 String title, String description, String startDate, String endDate, 365 boolean allDay, String timezone) { 366 mOriginalTitle = originalTitle; 367 Time time = new Time(timezone); 368 time.parse3339(originalInstance); 369 mOriginalInstance = time.toMillis(false /* use isDst */); 370 init(title, description, startDate, endDate, null /* rrule */, allDay, timezone); 371 } 372 } 373 374 /** 375 * Returns the default account on the device. 376 * @return 377 */ 378 protected String getAccount() { 379 try { 380 return GoogleLoginServiceBlockingHelper.getAccount(mTargetContext, false); 381 } catch (GoogleLoginServiceNotFoundException e) { 382 Log.e("SyncCalendarTest", "Could not find Google login service", e); 383 return null; 384 } 385 } 386 387 /** 388 * Compares two cursors 389 */ 390 protected void compareCursors(Cursor cursor1, Cursor cursor2, 391 Set<String> columnsToSkip, String tableName) { 392 String[] cols = cursor1.getColumnNames(); 393 int length = cols.length; 394 395 assertEquals(tableName + " count failed to match", cursor1.getCount(), 396 cursor2.getCount()); 397 Map<String, String> row = Maps.newHashMap(); 398 while (cursor1.moveToNext() && cursor2.moveToNext()) { 399 for (int i = 0; i < length; i++) { 400 String col = cols[i]; 401 if (columnsToSkip != null && columnsToSkip.contains(col)) { 402 continue; 403 } 404 row.put(col, cursor1.getString(i)); 405 406 assertEquals("Row: " + row + " Table: " + tableName + ": " + cols[i] + 407 " failed to match", cursor1.getString(i), 408 cursor2.getString(i)); 409 } 410 } 411 } 412} 413