My struggles with Android’s CalendarContract introduced in ICS

While working on some upgrades for Squiggly I wanted to subscribe to Android’s calendar provider events. This is how you would do it.

Assuming you have a receiver in your app, if you have declared it in the manifest xml, then add this to get provider_changed intents. Thanks to this stackoverflow question.

<intent-filter>
    <action android:name="android.intent.action.PROVIDER_CHANGED"/>
    <data android:scheme="content"/>
    <data android:host="com.android.calendar"/>
</intent-filter>

Now, the above intent-filter is missing a few things that could have made my interaction with the calendar provider easier. They are the attributes paths, pathPattern and pathPrefix for the “ part of the intent-filter. Unfortunately, even specifying those attributes won’t do you any good because the CalendarProvider class itself doesn’t specify those parameters on the intent that is fired from within.

The other problem is that the intent that is fired from CalendarProvider doesn’t even carry the event id that is passed to it from the methods that call it. See sendUpdateNotification from CalendarProvider2.java in the JellyBean source code here. This method sends a message to its Handler instance which then broadcasts the intent for provider_changed action. ref: this line. The problem is this simply obtains a message from the message pool and passes the value that sets the what of the Message instance returned. Instead, they could have done this:

//get a message using the obtainMessage(int what, Object obj) version
Message msg = mBroadcastHandler.obtainMessage(UPDATE_BROADCAST_MSG, new Long(eventId));

With the eventId now getting lost with the aforementioned line#4478 from CalendarProvider2.java, the handler now doesn’t even add any extras to the intent before broadcasting it. The control eventually reaches doSendUpdateNotification, which as you can see does nothing but set the action in the intent and broadcasts it.

What’s even more frustrating is that if you look at the usages of this method, the callers actually do pass the eventId and the actions that trigger a call to this method are actually the ones I want to know the event ID for!!

Usages for sendUpdateNotification in JellyBean r1
Usages for sendUpdateNotification in JellyBean r1

Well, looks like the latest KitKat revision still hasn’t changed this. Until then, I guess I will have to scan the instances table whenever the provider_changed action is received. 😦

If you care about this issue and want to bring this to the Android team’s attention then please vote for this issue.

Dynamic entries in Android’s MultiSelectListPreference

So I was finally excited that my brother and I decided to drop support for Gingerbread for the next iteration of Squiggly and that allows me to do things like use the MultiSelectListPreference and use the local Calendar API exclusively.

But there is a drawback with MultiSelectListPreference. You have to pre-define the string arrays for entries and entryValues. This sucks. This is why I have now added CustomMultiSelectListPreference to CustomPreferences. I have followed the same pattern which I did with CustomListPreference by implementing the IDynamicProvider which I introduced a while back when I needed to populate a regular list preference dynamically.

This is the class to reference in your preference xml file.

To use it:

<com.myappfactory.preferences.CustomMultiSelectListPreference
            android:key="@string/calendarsToMonitorKey"
            android:dependency="@string/enableCalendarBackgroundServicesKey"
            android:title="@string/calendarsToMonitorTitle"
            android:dialogTitle="@string/calendarsToMonitorDialogTitle"
            android:summary="@string/calendarsToMonitorSummary"
            customPreference:dynamicEntriesProvider="your.app.package.ClassThatImplementsIDynamicProvider"
            customPreference:dynamicEntryValuesProvider="your.app.package.ClassThatImplementsIDynamicProvider"
            customPreference:selectAllValuesByDefault="true"
            android:entries="@array/empty_array"
            android:entryValues="@array/empty_array"
            android:defaultValue="@array/empty_array"/>

selectAllValuesByDefault does exactly what it says. It selects all the values that are dynamically added. The class that provides these dynamic entries and entryValues need to implement IDynamicProvider. The no-argument version of populate method will no longer be supported. So be sure to implement the populate that accepts a Context. CustomMultiSelectListPreference first calls populate then calls getItem. I have tried to keep the same naming convention that Android’s adapter classes use. So you will see getCount and getItems in there.

Here’s a preview of what it will look like:

CustomMultiSelectListPreference
CustomMultiSelectListPreference