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.Calendar; 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 = Calendar.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(Calendar.Events._ID); 56 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_TIME); 57 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_VERSION); 58 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_DATA); 59 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_DIRTY); 60 EVENT_COLUMNS_TO_SKIP.add(Calendar.Events._SYNC_MARK); 61 ATTENDEES_COLUMNS_TO_SKIP.add(Calendar.Attendees._ID); 62 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._ID); 63 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_TIME); 64 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_VERSION); 65 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_DATA); 66 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_DIRTY); 67 CALENDARS_COLUMNS_TO_SKIP.add(Calendar.Calendars._SYNC_MARK); 68 INSTANCES_COLUMNS_TO_SKIP.add(Calendar.Instances._ID); 69 } 70 71 @Override 72 protected void setUp() throws Exception { 73 super.setUp(); 74 mTargetContext = getInstrumentation().getTargetContext(); 75 76 mAccountManager = AccountManager.get(mTargetContext); 77 mAccount = getAccount(); 78 mResolver = mTargetContext.getContentResolver(); 79 } 80 81 /** 82 * A simple method that syncs the calendar provider. 83 * @throws Exception 84 */ 85 protected void syncCalendar() throws Exception { 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 return insertEvent(getDefaultCalendarId(), event); 98 } 99 100 /** 101 * Creates a new event in the given calendarId. 102 * @param calendarId Calendar to be used. 103 * @param event Event to be created. 104 * @return Uri of the event created. 105 * @throws Exception 106 */ 107 protected Uri insertEvent(int calendarId, EventInfo event) throws Exception{ 108 ContentValues m = new ContentValues(); 109 m.put(Calendar.Events.CALENDAR_ID, calendarId); 110 m.put(Calendar.Events.TITLE, event.mTitle); 111 m.put(Calendar.Events.DTSTART, event.mDtstart); 112 m.put(Calendar.Events.ALL_DAY, event.mAllDay ? 1 : 0); 113 114 if (event.mRrule == null) { 115 // This is a normal event 116 m.put(Calendar.Events.DTEND, event.mDtend); 117 } else { 118 // This is a repeating event 119 m.put(Calendar.Events.RRULE, event.mRrule); 120 m.put(Calendar.Events.DURATION, event.mDuration); 121 } 122 123 if (event.mDescription != null) { 124 m.put(Calendar.Events.DESCRIPTION, event.mDescription); 125 } 126 if (event.mTimezone != null) { 127 m.put(Calendar.Events.EVENT_TIMEZONE, event.mTimezone); 128 } 129 130 Uri url = mResolver.insert(mEventsUri, m); 131 syncCalendar(); 132 return url; 133 } 134 135 /** 136 * Edits the given event. 137 * @param eventId EventID of the event to be edited. 138 * @param event Edited event details. 139 * @throws Exception 140 */ 141 protected void editEvent(long eventId, EventInfo event) throws Exception { 142 ContentValues values = new ContentValues(); 143 values.put(Calendar.Events.TITLE, event.mTitle); 144 values.put(Calendar.Events.DTSTART, event.mDtstart); 145 values.put(Calendar.Events.DTEND, event.mDtend); 146 values.put(Calendar.Events.ALL_DAY, event.mAllDay ? 1 : 0); 147 148 if (event.mDescription != null) { 149 values.put(Calendar.Events.DESCRIPTION, event.mDescription); 150 } 151 152 Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId); 153 mResolver.update(uri, values, null, null); 154 syncCalendar(); 155 } 156 157 /** 158 * Deletes a given event. 159 * @param uri 160 * @throws Exception 161 */ 162 protected void deleteEvent(Uri uri) throws Exception { 163 mResolver.delete(uri, null, null); 164 syncCalendar(); 165 } 166 167 /** 168 * Inserts a new calendar. 169 * @param name 170 * @param timezone 171 * @param calendarUrl 172 * @throws Exception 173 */ 174 protected void insertCalendar(String name, String timezone, String calendarUrl) 175 throws Exception { 176 ContentValues values = new ContentValues(); 177 178 values.put(Calendar.Calendars._SYNC_ACCOUNT, getAccount()); 179 values.put(Calendar.Calendars.URL, calendarUrl); 180 values.put(Calendar.Calendars.NAME, name); 181 values.put(Calendar.Calendars.DISPLAY_NAME, name); 182 183 values.put(Calendar.Calendars.SYNC_EVENTS, 1); 184 values.put(Calendar.Calendars.SELECTED, 1); 185 values.put(Calendar.Calendars.HIDDEN, 0); 186 values.put(Calendar.Calendars.COLOR, -14069085 /* blue */); 187 values.put(Calendar.Calendars.ACCESS_LEVEL, Calendar.Calendars.OWNER_ACCESS); 188 189 values.put(Calendar.Calendars.COLOR, "0xff123456"); 190 values.put(Calendar.Calendars.TIMEZONE, timezone); 191 mResolver.insert(Calendar.Calendars.CONTENT_URI, values); 192 syncCalendar(); 193 } 194 195 /** 196 * Returns a fresh count of events. 197 * @return 198 */ 199 protected int getEventsCount() { 200 Cursor cursor; 201 cursor = mResolver.query(mEventsUri, null, null, null, null); 202 return cursor.getCount(); 203 } 204 205 /** 206 * Returns the ID of the default calendar. 207 * @return 208 */ 209 protected int getDefaultCalendarId() { 210 Cursor calendarsCursor; 211 calendarsCursor = mResolver.query(Calendar.Calendars.CONTENT_URI, null, null, null, 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