Home | History | Annotate | Download | only in calendar
      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.Calendar;
     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 = 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