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 17 package com.android.providers.calendar; 18 19 import android.accounts.Account; 20 import android.accounts.AccountManager; 21 import android.content.ContentResolver; 22 import android.content.ContentUris; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.database.Cursor; 26 import android.net.Uri; 27 import android.os.SystemClock; 28 import android.provider.CalendarContract; 29 import android.test.SyncBaseInstrumentation; 30 import android.text.format.DateUtils; 31 import android.text.format.Time; 32 import android.util.Log; 33 34 import com.google.android.collect.Maps; 35 36 import java.util.HashSet; 37 import java.util.Map; 38 import java.util.Set; 39 40 public 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 setUp()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 */ syncCalendar()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 */ insertEvent(EventInfo event)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 */ insertEvent(int calendarId, EventInfo event)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 */ editEvent(long eventId, EventInfo event)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 */ deleteEvent(Uri uri)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 */ insertCalendar(String name, String timezone, String calendarUrl)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 */ getEventsCount()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 */ getDefaultCalendarId()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 EventInfo(String title, String startDate, String endDate, boolean allDay)233 public EventInfo(String title, String startDate, String endDate, 234 boolean allDay) { 235 init(title, startDate, endDate, allDay, DEFAULT_TIMEZONE); 236 } 237 EventInfo(String title, long startDate, long endDate, boolean allDay)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 EventInfo(String title, long startDate, long endDate, boolean allDay, String description)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 EventInfo(String title, String startDate, String endDate, boolean allDay, String timezone)262 public EventInfo(String title, String startDate, String endDate, 263 boolean allDay, String timezone) { 264 init(title, startDate, endDate, allDay, timezone); 265 } 266 init(String title, String startDate, String endDate, boolean allDay, String timezone)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 EventInfo(String title, String description, String startDate, String endDate, String rrule, boolean allDay)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 EventInfo(String title, String description, String startDate, String endDate, String rrule, boolean allDay, String 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 init(String title, String description, String startDate, String endDate, String rrule, boolean allDay, String timezone)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 EventInfo(String originalTitle, String originalInstance, String title, String description, String startDate, String endDate, boolean allDay)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 init(String originalTitle, String originalInstance, String title, String description, String startDate, String endDate, boolean allDay, String timezone)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 */ getAccount()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 */ compareCursors(Cursor cursor1, Cursor cursor2, Set<String> columnsToSkip, String tableName)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