Search code examples
androidandroid-contentproviderandroid-4.0-ice-cream-sandwich

Creating a Calendar with Android 4.0's (ICS) Content Provider


I'm trying to create a calendar using ICS's new Content Provider for Calendars. Since Calendar creation can only be done in a SyncAdapter, I've created one:

public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter 
{

    public CalendarSyncAdapter(Context context, boolean autoInitialize)
    {
        super(context, autoInitialize);
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)
    {
        Log.i( Debug.TAG, "onPerformSync()" );
    }

    public void doCreateCalendar( Account account )
    {
            ContentResolver cr = getContext().getContentResolver();

            ContentValues values = new ContentValues();
            values.put(Calendars.ACCOUNT_NAME, account.name);
            values.put(Calendars.ACCOUNT_TYPE, account.type);
            values.put(Calendars.NAME, "newesttest");
            values.put(Calendars.CALENDAR_DISPLAY_NAME, "newestTest");
            values.put(Calendars.CALENDAR_COLOR, 0xFFFFFFFF);
            values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER );
            values.put(Calendars.OWNER_ACCOUNT, account.name);

            values.put(Calendars.SYNC_EVENTS, 1);
            values.put(Calendars.VISIBLE, 1);

            Uri creationUri = asSyncAdapter( Calendars.CONTENT_URI, account.name, account.type );
            Uri created = cr.insert( creationUri, values );
            long id = Long.parseLong( created.getLastPathSegment() );

            Log.i( Debug.TAG, "Calendar created: " + id + " - " + created );
    }

    private Uri asSyncAdapter( Uri uri, String account, String accountType )
    {
        return uri.buildUpon()
        .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER,"true")
        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
    }
}

But it doesn't run inside a Service like I see everywhere, since all I want is to use it to insert a new Calendar. Running in a Service is not required is it? Doesn't appear to be.

Anyway, I instantiate it in my Activity and call my method:

public void createCalendar( View v )
{
    m_syncAdapter.doCreateCalendar( this, m_account );
}

It executes successfully, no complaints, but the URI it returns is always the same. When I parse it for the Row ID, it's always 12. And when I check my calendars, there is nothing new.

In my manifest I have everything setup:

...

<application

...

    <activity android:name="Setup" android:label="@string/activity_title_setup">
        <intent-filter>
            <action android:name="android.content.SyncAdapter" />
        </intent-filter>
        <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/calendar_sync_adapter" />
    </activity>

And calendar_sync_adapter.xml:

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
   android:contentAuthority="com.android.calendar"
   android:accountType="com.google"
   android:userVisible="true"
/>

So not sure what I'm doing wrong here. But this is what I see in the logs when I execute it:

02-17 16:43:07.783: I/ActivityManager(184): Start proc com.android.providers.calendar for content provider com.android.providers.calendar/.CalendarProvider2: pid=20132 uid=10008 gids={3003, 1015}
02-17 16:43:07.806: I/ActivityThread(20132): Pub com.android.calendar: com.android.providers.calendar.CalendarProvider2
02-17 16:43:07.869: I/ActivityManager(184): Start proc com.google.android.calendar for service com.google.android.calendar/com.google.android.syncadapters.calendar.CalendarSyncAdapterService: pid=20145 uid=10007 gids={3003}
02-17 16:43:07.900: I/ActivityThread(20145): Pub com.google.android.calendar.CalendarRecentSuggestionsProvider: com.android.calendar.CalendarRecentSuggestionsProvider

// This is my print of the returned URI
02-17 16:43:08.009: I/C3(19995): Calendar created: 12 - content://com.android.calendar/calendars/12?caller_is_syncadapter=true&account_name=xxx%40gmail.com&account_type=com.google

02-17 16:43:08.876: I/CalendarProvider2(20132): Sending notification intent: Intent { act=android.intent.action.PROVIDER_CHANGED dat=content://com.android.calendar }
02-17 16:43:08.876: W/ContentResolver(20132): Failed to get type for: content://com.android.calendar (Unknown URL content://com.android.calendar)

Solution

  • The immediate answer is: Sync adapters have to be associated with a Service. The Service instantiates the sync adapter in onCreate(), and returns an IBinder to the sync adapter in onBind(). The SyncManager calls the Service when it wants to run your sync adapter.

    Sync adapters are controlled by the Android system, specifically by the SyncManager, not by your application.

    The SampleSyncAdapter sample app shows how to write a sync adapter and service. If you don't want to do authentication, you can drop that part. All you need to do is implement the class that extends AbstractThreadedSyncAdapter and the Service class that binds to it, along with the XML files for the sync adapter.

    The better answer is: Sync adapters help you synchronize data between a server and a device. If all you want to do is download calendar data from a server to your device, you can use an application that scrapes data out of the server and writes it to the calendar provider. That's not synchronization; you're not making any guarantee that you'll be doing any more downloads or uploads. Of course, if you do want to keep the two data sources in sync, a sync adapter has these advantages:

    • The system will run it on a regular basis, without user intervention.
    • The system ensures that the network is available before attempting to run the sync. Note, though, that the system can't prevent you from doing dumb stuff like trying to authenticate against the server when your network services are offline.
    • The system ensures that no other syncs for the same server and content provider are running when it starts your sync.
    • The system provides a standard facility for displaying the available sync adapters when the user wants to add an account.