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.accounts.Account; 20import android.accounts.AccountManager; 21import android.content.ContentResolver; 22import android.content.ContentUris; 23import android.content.ContentValues; 24import android.content.Context; 25import android.database.Cursor; 26import android.net.Uri; 27import android.os.SystemClock; 28import android.provider.CalendarContract; 29import android.test.SyncBaseInstrumentation; 30import android.text.format.DateUtils; 31import android.text.format.Time; 32import android.util.Log; 33 34import com.google.android.collect.Maps; 35 36import java.util.HashSet; 37import java.util.Map; 38import java.util.Set; 39 40public class CalendarSyncTestingBase extends SyncBaseInstrumentation { 41 protected AccountManager mAccountManager; 42 protected Context mTargetContext; 43 protected String mAccount; 44 protected ContentResolver mResolver; 45 protected Uri mEventsUri = CalendarContract.Events.CONTENT_URI; 46 47 static final String TAG = "calendar"; 48 static final String DEFAULT_TIMEZONE = "America/Los_Angeles"; 49 static final Set<String> EVENT_COLUMNS_TO_SKIP = new HashSet<String>(); 50 static final Set<String> ATTENDEES_COLUMNS_TO_SKIP = new HashSet<String>(); 51 static final Set<String> CALENDARS_COLUMNS_TO_SKIP = new HashSet<String>(); 52 static final Set<String> INSTANCES_COLUMNS_TO_SKIP = new HashSet<String>(); 53 54 static { 55 EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events._ID); 56 EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.SYNC_DATA5); 57 EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.SYNC_DATA4); 58 EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.SYNC_DATA2); 59 EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.DIRTY); 60 EVENT_COLUMNS_TO_SKIP.add(CalendarContract.Events.SYNC_DATA8); 61 ATTENDEES_COLUMNS_TO_SKIP.add(CalendarContract.Attendees._ID); 62 CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars._ID); 63 CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars.CAL_SYNC8); 64 CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars.CAL_SYNC7); 65 CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars.DIRTY); 66 CALENDARS_COLUMNS_TO_SKIP.add(CalendarContract.Calendars.CAL_SYNC6); 67 INSTANCES_COLUMNS_TO_SKIP.add(CalendarContract.Instances._ID); 68 } 69 70 @Override 71 protected void setUp() throws Exception { 72 super.setUp(); 73 mTargetContext = getInstrumentation().getTargetContext(); 74 75 mAccountManager = AccountManager.get(mTargetContext); 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 cancelSyncsandDisableAutoSync(); 86 syncProvider(CalendarContract.CONTENT_URI, mAccount, CalendarContract.AUTHORITY); 87 } 88 89 /** 90 * Creates a new event in the default calendar. 91 * @param event Event to be created. 92 * @return Uri of the created event. 93 * @throws Exception 94 */ 95 protected Uri insertEvent(EventInfo event) throws Exception { 96 return insertEvent(getDefaultCalendarId(), event); 97 } 98 99 /** 100 * Creates a new event in the given calendarId. 101 * @param calendarId Calendar to be used. 102 * @param event Event to be created. 103 * @return Uri of the event created. 104 * @throws Exception 105 */ 106 protected Uri insertEvent(int calendarId, EventInfo event) throws Exception{ 107 ContentValues m = new ContentValues(); 108 m.put(CalendarContract.Events.CALENDAR_ID, calendarId); 109 m.put(CalendarContract.Events.TITLE, event.mTitle); 110 m.put(CalendarContract.Events.DTSTART, event.mDtstart); 111 m.put(CalendarContract.Events.ALL_DAY, event.mAllDay ? 1 : 0); 112 113 if (event.mRrule == null) { 114 // This is a normal event 115 m.put(CalendarContract.Events.DTEND, event.mDtend); 116 } else { 117 // This is a repeating event 118 m.put(CalendarContract.Events.RRULE, event.mRrule); 119 m.put(CalendarContract.Events.DURATION, event.mDuration); 120 } 121 122 if (event.mDescription != null) { 123 m.put(CalendarContract.Events.DESCRIPTION, event.mDescription); 124 } 125 if (event.mTimezone != null) { 126 m.put(CalendarContract.Events.EVENT_TIMEZONE, event.mTimezone); 127 } 128 129 Uri url = mResolver.insert(mEventsUri, m); 130 syncCalendar(); 131 return url; 132 } 133 134 /** 135 * Edits the given event. 136 * @param eventId EventID of the event to be edited. 137 * @param event Edited event details. 138 * @throws Exception 139 */ 140 protected void editEvent(long eventId, EventInfo event) throws Exception { 141 ContentValues values = new ContentValues(); 142 values.put(CalendarContract.Events.TITLE, event.mTitle); 143 values.put(CalendarContract.Events.DTSTART, event.mDtstart); 144 values.put(CalendarContract.Events.DTEND, event.mDtend); 145 values.put(CalendarContract.Events.ALL_DAY, event.mAllDay ? 1 : 0); 146 147 if (event.mDescription != null) { 148 values.put(CalendarContract.Events.DESCRIPTION, event.mDescription); 149 } 150 151 Uri uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId); 152 mResolver.update(uri, values, null, null); 153 syncCalendar(); 154 } 155 156 /** 157 * Deletes a given event. 158 * @param uri 159 * @throws Exception 160 */ 161 protected void deleteEvent(Uri uri) throws Exception { 162 mResolver.delete(uri, null, null); 163 syncCalendar(); 164 } 165 166 /** 167 * Inserts a new calendar. 168 * @param name 169 * @param timezone 170 * @param calendarUrl 171 * @throws Exception 172 */ 173 protected void insertCalendar(String name, String timezone, String calendarUrl) 174 throws Exception { 175 ContentValues values = new ContentValues(); 176 177 values.put(CalendarContract.Calendars.ACCOUNT_NAME, getAccount()); 178 values.put(CalendarContract.Calendars.CAL_SYNC1, calendarUrl); 179 values.put(CalendarContract.Calendars.NAME, name); 180 values.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, name); 181 182 values.put(CalendarContract.Calendars.SYNC_EVENTS, 1); 183 values.put(CalendarContract.Calendars.VISIBLE, 1); 184 values.put(CalendarContract.Calendars.CALENDAR_COLOR, -14069085 /* blue */); 185 values.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, 186 CalendarContract.Calendars.CAL_ACCESS_OWNER); 187 188 values.put(CalendarContract.Calendars.CALENDAR_COLOR, "0xff123456"); 189 values.put(CalendarContract.Calendars.CALENDAR_TIME_ZONE, timezone); 190 mResolver.insert(CalendarContract.Calendars.CONTENT_URI, values); 191 syncCalendar(); 192 } 193 194 /** 195 * Returns a fresh count of events. 196 * @return 197 */ 198 protected int getEventsCount() { 199 Cursor cursor; 200 cursor = mResolver.query(mEventsUri, null, null, null, null); 201 return cursor.getCount(); 202 } 203 204 /** 205 * Returns the ID of the default calendar. 206 * @return 207 */ 208 protected int getDefaultCalendarId() { 209 Cursor calendarsCursor; 210 calendarsCursor = mResolver.query(CalendarContract.Calendars.CONTENT_URI, null, null, null, 211 null); 212 calendarsCursor.moveToNext(); 213 return calendarsCursor.getInt(calendarsCursor.getColumnIndex("_id")); 214 } 215 216 /** 217 * This class stores all the useful information about an event. 218 */ 219 protected class EventInfo { 220 String mTitle; 221 String mDescription; 222 String mTimezone; 223 boolean mAllDay; 224 long mDtstart; 225 long mDtend; 226 String mRrule; 227 String mDuration; 228 String mOriginalTitle; 229 long mOriginalInstance; 230 int mSyncId; 231 232 // Constructor for normal events, using the default timezone 233 public EventInfo(String title, String startDate, String endDate, 234 boolean allDay) { 235 init(title, startDate, endDate, allDay, DEFAULT_TIMEZONE); 236 } 237 238 public EventInfo(String title, long startDate, long endDate, 239 boolean allDay) { 240 mTitle = title; 241 mTimezone = DEFAULT_TIMEZONE; 242 mDtstart = startDate; 243 mDtend = endDate; 244 mDuration = null; 245 mRrule = null; 246 mAllDay = allDay; 247 } 248 249 public EventInfo(String title, long startDate, long endDate, 250 boolean allDay, String description) { 251 mTitle = title; 252 mTimezone = DEFAULT_TIMEZONE; 253 mDtstart = startDate; 254 mDtend = endDate; 255 mDuration = null; 256 mRrule = null; 257 mAllDay = allDay; 258 mDescription = description; 259 } 260 261 // Constructor for normal events, specifying the timezone 262 public EventInfo(String title, String startDate, String endDate, 263 boolean allDay, String timezone) { 264 init(title, startDate, endDate, allDay, timezone); 265 } 266 267 public void init(String title, String startDate, String endDate, 268 boolean allDay, String timezone) { 269 mTitle = title; 270 Time time = new Time(); 271 if (allDay) { 272 time.timezone = Time.TIMEZONE_UTC; 273 } else if (timezone != null) { 274 time.timezone = timezone; 275 } 276 mTimezone = time.timezone; 277 time.parse3339(startDate); 278 mDtstart = time.toMillis(false /* use isDst */); 279 time.parse3339(endDate); 280 mDtend = time.toMillis(false /* use isDst */); 281 mDuration = null; 282 mRrule = null; 283 mAllDay = allDay; 284 } 285 286 // Constructor for repeating events, using the default timezone 287 public EventInfo(String title, String description, String startDate, String endDate, 288 String rrule, boolean allDay) { 289 init(title, description, startDate, endDate, rrule, allDay, DEFAULT_TIMEZONE); 290 } 291 292 // Constructor for repeating events, specifying the timezone 293 public EventInfo(String title, String description, String startDate, String endDate, 294 String rrule, boolean allDay, String timezone) { 295 init(title, description, startDate, endDate, rrule, allDay, timezone); 296 } 297 298 public void init(String title, String description, String startDate, String endDate, 299 String rrule, boolean allDay, String timezone) { 300 mTitle = title; 301 mDescription = description; 302 Time time = new Time(); 303 if (allDay) { 304 time.timezone = Time.TIMEZONE_UTC; 305 } else if (timezone != null) { 306 time.timezone = timezone; 307 } 308 mTimezone = time.timezone; 309 time.parse3339(startDate); 310 mDtstart = time.toMillis(false /* use isDst */); 311 if (endDate != null) { 312 time.parse3339(endDate); 313 mDtend = time.toMillis(false /* use isDst */); 314 } 315 if (allDay) { 316 long days = 1; 317 if (endDate != null) { 318 days = (mDtend - mDtstart) / DateUtils.DAY_IN_MILLIS; 319 } 320 mDuration = "P" + days + "D"; 321 } else { 322 long seconds = (mDtend - mDtstart) / DateUtils.SECOND_IN_MILLIS; 323 mDuration = "P" + seconds + "S"; 324 } 325 mRrule = rrule; 326 mAllDay = allDay; 327 } 328 329 // Constructor for recurrence exceptions, using the default timezone 330 public EventInfo(String originalTitle, String originalInstance, String title, 331 String description, String startDate, String endDate, boolean allDay) { 332 init(originalTitle, originalInstance, 333 title, description, startDate, endDate, allDay, DEFAULT_TIMEZONE); 334 } 335 336 public void init(String originalTitle, String originalInstance, 337 String title, String description, String startDate, String endDate, 338 boolean allDay, String timezone) { 339 mOriginalTitle = originalTitle; 340 Time time = new Time(timezone); 341 time.parse3339(originalInstance); 342 mOriginalInstance = time.toMillis(false /* use isDst */); 343 init(title, description, startDate, endDate, null /* rrule */, allDay, timezone); 344 } 345 } 346 347 /** 348 * Returns the default account on the device. 349 * @return 350 */ 351 protected String getAccount() { 352 Account[] accounts = mAccountManager.getAccountsByType("com.google"); 353 354 assertTrue("Didn't find any Google accounts", accounts.length > 0); 355 356 Account account = accounts[accounts.length - 1]; 357 Log.v(TAG, "Found " + accounts.length + " accounts; using the last one, " + account.name); 358 return account.name; 359 } 360 361 /** 362 * Compares two cursors 363 */ 364 protected void compareCursors(Cursor cursor1, Cursor cursor2, 365 Set<String> columnsToSkip, String tableName) { 366 String[] cols = cursor1.getColumnNames(); 367 int length = cols.length; 368 369 assertEquals(tableName + " count failed to match", cursor1.getCount(), 370 cursor2.getCount()); 371 Map<String, String> row = Maps.newHashMap(); 372 while (cursor1.moveToNext() && cursor2.moveToNext()) { 373 for (int i = 0; i < length; i++) { 374 String col = cols[i]; 375 if (columnsToSkip != null && columnsToSkip.contains(col)) { 376 continue; 377 } 378 row.put(col, cursor1.getString(i)); 379 380 assertEquals("Row: " + row + " Table: " + tableName + ": " + cols[i] + 381 " failed to match", cursor1.getString(i), 382 cursor2.getString(i)); 383 } 384 } 385 } 386} 387