Learning from DashClock: How to display information about your next calendar appointment

DashClock is lock screen clock widget for Android 4.2+.
It is an awesome code example, from which we can learn and take snippets.

Do you want to know how to display information about your next calendar appointment?
CalendarExtension is good answer.

  1. First of all we can access the data by making use of the CalendarContract content provider.
  2. Find next single occurrences of an event in next few hours
  3. Format information and that's all

It's very important to note that these API works only with Android 4.0+.


With this code we are going to query CalendarContract instances
   long now = getCurrentTimestamp();
   Cursor cursor= getContentResolver().query(
	   CalendarContract.Instances.CONTENT_URI.buildUpon()   
         	.appendPath(Long.toString(now))
		.appendPath(Long.toString(now + DEFAULT_LOOK_AHEAD_HOURS * HOUR_MILLIS))
	 	.build(),
	   EventsQuery.PROJECTION,
	   CalendarContract.Instances.ALL_DAY + "=0 AND "
		+ CalendarContract.Instances.SELF_ATTENDEE_STATUS
			+ "!="	+ CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED
		+ " AND " + CalendarContract.Instances.STATUS + "!="
		+ CalendarContract.Instances.STATUS_CANCELED + " AND "
		+ CalendarContract.Instances.VISIBLE + "!=0", null,
		CalendarContract.Instances.BEGIN);
Calendar’s data model consists of calendars, events, event instances.
We can manage multiple calendars on our device. For each calendar we can have multiple events.
An instance is a single occurrence of an event including time zone specific start and end days and minutes

CalendarContract.Instances.CONTENT_URI is the content:// style URL for the top-level calendar authority.
With:
  .appendPath(Long.toString(now))
  .appendPath(Long.toString(now + DEFAULT_LOOK_AHEAD_HOURS * HOUR_MILLIS))
we are choosing a time interval, between now and the next XX hours (=DEFAULT_LOOK_AHEAD_HOURS)

With EventsQuery.PROJECTION we define a list of which columns to return.
  private interface EventsQuery {
    String[] PROJECTION = { CalendarContract.Instances.EVENT_ID,
   			    CalendarContract.Instances.BEGIN,
   			    CalendarContract.Instances.END,
			    CalendarContract.Instances.TITLE, };
    int EVENT_ID = 0;
    int BEGIN = 1;
    int END = 2;
    int TITLE = 3; 
 }
With filter we declare which rows to return: in this case, we skip over events that are not ALL_DAY but span multiple days, including the next XX hours, not cancelled or declined.

Move the cursor to the first row and check if there is an appointment.
  while (cursor.moveToNext()) {
            nextTimestamp = cursor.getLong(EventsQuery.BEGIN);
            timeUntilNextAppointent = nextTimestamp - currentTimestamp;
            if (timeUntilNextAppointent >= 0) {
                break; // We find an appointment !
            }
    }
We have found an appointment. We can retrieve and format information.

We can get the event title:
   String eventTitle = cursor.getString(EventsQuery.TITLE);  
We can get the beginning time of the instance, in UTC milliseconds
  long nextTimestamp = cursor.getLong(EventsQuery.BEGIN);
and format this data in this way:
    Calendar nextEventCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        nextEventCalendar.setTimeInMillis(nextTimestamp);

    StringBuilder expandedBodyFormat = new StringBuilder();
    if (nextTimestamp - currentTimestamp > 24 * HOUR_MILLIS) {
            expandedBodyFormat.append("EEEE, ");
    }

    if (DateFormat.is24HourFormat(this)) {
           expandedBodyFormat.append("HH:mm");
    } else {
           expandedBodyFormat.append("h:mm a");
    }

   String expandedBody = new SimpleDateFormat(expandedBodyFormat.toString())
                .format(nextEventCalendar.getTime());

Finally we can calculate how many hours are missing to next appointment (in minutes,hours or days).
    int minutesUntilNextAppointment = (int) (timeUntilNextAppointent / MINUTE_MILLIS);

    String untilString;
    if (minutesUntilNextAppointment < 60) {   // Minutes 
            untilString = getResources().getQuantityString(
                    R.plurals.calendar_template_mins,
                    minutesUntilNextAppointment,
                    minutesUntilNextAppointment);
     } else {  
            int hours = Math.round(minutesUntilNextAppointment / 60f);
            if (hours < 24) { //Hours
                untilString = getResources().getQuantityString(
                        R.plurals.calendar_template_hours, hours, hours);
            } else {   //Days
                int days = hours / 24; // floor, not round for days
                untilString = getResources().getQuantityString(
                        R.plurals.calendar_template_days, days, days);
            }
     }
Here you can find more information about Events.

To use the Calendar content provider you need to declare the necessary permissions within your manifest file first:
 <uses-permission android:name="android.permission.READ_CALENDAR"/>
That's all!

  Log.d(TAG, "Until=" + untilString);
  Log.d(TAG, "Title=" + eventTitle);
  Log.d(TAG, "Body=" + expandedBody);

This is our log:
02-14 00:11:46.870: D/calendarExtension(16367): Until=48 min
02-14 00:11:46.870: D/calendarExtension(16367): Title=Test calandar
02-14 00:11:46.870: D/calendarExtension(16367): Body=01:00
C

Comments

Popular posts from this blog

AntiPattern: freezing a UI with Broadcast Receiver

NotificationListenerService and kitkat

How to centralize the support libraries dependencies in gradle