CalendarAppWidgetService.java revision 9a3cb14e28536e4133dddbe952f47189fe344ec1
1bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang/* 2bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Copyright (C) 2009 The Android Open Source Project 3bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 4bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Licensed under the Apache License, Version 2.0 (the "License"); 5bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * you may not use this file except in compliance with the License. 6bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * You may obtain a copy of the License at 7bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 8bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * http://www.apache.org/licenses/LICENSE-2.0 9bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 10bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Unless required by applicable law or agreed to in writing, software 11bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * distributed under the License is distributed on an "AS IS" BASIS, 12bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * See the License for the specific language governing permissions and 14bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * limitations under the License. 15bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 16bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 179a3cb14e28536e4133dddbe952f47189fe344ec1Mason Tangpackage com.android.calendar.widget; 18bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 19bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport com.google.common.annotations.VisibleForTesting; 20bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 219a3cb14e28536e4133dddbe952f47189fe344ec1Mason Tangimport com.android.calendar.R; 229a3cb14e28536e4133dddbe952f47189fe344ec1Mason Tangimport com.android.calendar.Utils; 239a3cb14e28536e4133dddbe952f47189fe344ec1Mason Tangimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo; 24bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 25bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 26bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.AlarmManager; 27bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.IntentService; 28bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.PendingIntent; 29bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.appwidget.AppWidgetManager; 30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.ComponentName; 31bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.ContentResolver; 32bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context; 33bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent; 34bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor; 35bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri; 36bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Attendees; 37bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Calendars; 38bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Instances; 39bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.TextUtils; 40bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateFormat; 41bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils; 42bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time; 43bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log; 44bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View; 45bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews; 46bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 47bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport java.util.ArrayList; 48bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport java.util.HashSet; 49bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport java.util.List; 50bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport java.util.Set; 51bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport java.util.TimeZone; 52bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 53bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 54bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangpublic class CalendarAppWidgetService extends IntentService { 55bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String TAG = "CalendarAppWidgetService"; 56bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final boolean LOGD = false; 57bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 58bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /* TODO query doesn't handle all-day events properly, we should fix this in 59bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * the provider in a manner similar to how it is handled in Event.loadEvents 60bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * in the Calendar application. 61bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 62bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, " 63bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, " 64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.END_MINUTE + " ASC LIMIT 10"; 65bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 66bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // TODO can't use parameter here because provider is dropping them 67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SELECTION = Calendars.SELECTED + "=1 AND " 68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED; 69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 70bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final String[] EVENT_PROJECTION = new String[] { 71bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.ALL_DAY, 72bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.BEGIN, 73bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.END, 74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.TITLE, 75bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_LOCATION, 76bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_ID, 77bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang }; 78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_ALL_DAY = 0; 80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_BEGIN = 1; 81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_END = 2; 82bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_TITLE = 3; 83bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_LOCATION = 4; 84bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_ID = 5; 85bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 86bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final long SEARCH_DURATION = DateUtils.WEEK_IN_MILLIS; 87bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 88bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // If no next-update calculated, or bad trigger time in past, schedule 89bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // update about six hours from now. 90bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final long UPDATE_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6; 91bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 92bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang public CalendarAppWidgetService() { 93bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang super(TAG); 94bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 95bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 96bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang @Override 97bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang protected void onHandleIntent(Intent intent) { 98bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // These will be null if the extra data doesn't exist 99bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int[] widgetIds = intent.getIntArrayExtra(CalendarAppWidgetProvider.EXTRA_WIDGET_IDS); 100bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long[] eventIds = null; 101bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang HashSet<Long> eventIdsSet = null; 102bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (intent.hasExtra(CalendarAppWidgetProvider.EXTRA_EVENT_IDS)) { 103bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang eventIds = intent.getExtras().getLongArray(CalendarAppWidgetProvider.EXTRA_EVENT_IDS); 104bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang eventIdsSet = new HashSet<Long>(eventIds.length); 105bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang for (int i = 0; i < eventIds.length; i++) { 106bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang eventIdsSet.add(eventIds[i]); 107bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 108bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 109bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long now = System.currentTimeMillis(); 110bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 111bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang performUpdate(this, widgetIds, eventIdsSet, now); 112bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 113bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 114bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 115bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Process and push out an update for the given appWidgetIds. 116bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 117bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param context Context to use when updating widget. 118bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param appWidgetIds List of appWidgetIds to update, or null for all. 119bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param changedEventIds Specific events known to be changed, otherwise 120bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * null. If present, we use to decide if an update is necessary. 121bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param now System clock time to use during this update. 122bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 123bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private void performUpdate(Context context, int[] appWidgetIds, 124bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Set<Long> changedEventIds, long now) { 125bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang ContentResolver resolver = context.getContentResolver(); 126bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 127bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Cursor cursor = null; 128bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang RemoteViews views = null; 129bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long triggerTime = -1; 130bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 131bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang try { 132bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang cursor = getUpcomingInstancesCursor(resolver, SEARCH_DURATION, now); 133bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (cursor != null) { 134bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang MarkedEvents events = buildMarkedEvents(cursor, changedEventIds, now); 135bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 136bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean shouldUpdate = true; 137bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (changedEventIds != null && changedEventIds.size() > 0) { 138bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang shouldUpdate = events.watchFound; 139bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 140bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 141bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (events.markedIds.isEmpty()) { 142bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views = getAppWidgetNoEvents(context); 143bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else if (shouldUpdate) { 144bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views = getAppWidgetUpdate(context, cursor, events); 145bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang triggerTime = calculateUpdateTime(cursor, events); 146bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 147bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 148bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views = getAppWidgetNoEvents(context); 149bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 150bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } finally { 151bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (cursor != null) { 152bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang cursor.close(); 153bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 154bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 155bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 156bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Bail out early if no update built 157bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (views == null) { 158bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (LOGD) Log.d(TAG, "Didn't build update, possibly because changedEventIds=" + 159bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang changedEventIds.toString()); 160bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return; 161bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 162bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 163bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang AppWidgetManager gm = AppWidgetManager.getInstance(context); 164bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (appWidgetIds != null && appWidgetIds.length > 0) { 165bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang gm.updateAppWidget(appWidgetIds, views); 166bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 167bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang ComponentName thisWidget = CalendarAppWidgetProvider.getComponentName(context); 168bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang gm.updateAppWidget(thisWidget, views); 169bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 170bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 171bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Schedule an alarm to wake ourselves up for the next update. We also cancel 172bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // all existing wake-ups because PendingIntents don't match against extras. 173bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 174bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // If no next-update calculated, or bad trigger time in past, schedule 175bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // update about six hours from now. 176bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (triggerTime == -1 || triggerTime < now) { 177bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (LOGD) Log.w(TAG, "Encountered bad trigger time " + 178bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang formatDebugTime(triggerTime, now)); 179bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang triggerTime = now + UPDATE_NO_EVENTS; 180bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 181bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 182bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 183bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang PendingIntent pendingUpdate = CalendarAppWidgetProvider.getUpdateIntent(context); 184bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 185bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang am.cancel(pendingUpdate); 186bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang am.set(AlarmManager.RTC, triggerTime, pendingUpdate); 187bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 188bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (LOGD) Log.d(TAG, "Scheduled next update at " + formatDebugTime(triggerTime, now)); 189bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 190bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 191bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 192bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Format given time for debugging output. 193bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 194bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param unixTime Target time to report. 195bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param now Current system time from {@link System#currentTimeMillis()} 196bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * for calculating time difference. 197bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 198bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static private String formatDebugTime(long unixTime, long now) { 199bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Time time = new Time(); 200bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.set(unixTime); 201bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 202bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long delta = unixTime - now; 203bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (delta > DateUtils.MINUTE_IN_MILLIS) { 204bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang delta /= DateUtils.MINUTE_IN_MILLIS; 205bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return String.format("[%d] %s (%+d mins)", unixTime, time.format("%H:%M:%S"), delta); 206bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 207bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang delta /= DateUtils.SECOND_IN_MILLIS; 208bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return String.format("[%d] %s (%+d secs)", unixTime, time.format("%H:%M:%S"), delta); 209bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 210bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 211bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 212bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 213bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Convert given UTC time into current local time. 214bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 215bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param recycle Time object to recycle, otherwise null. 216bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param utcTime Time to convert, in UTC. 217bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 218bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static private long convertUtcToLocal(Time recycle, long utcTime) { 219bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (recycle == null) { 220bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang recycle = new Time(); 221bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 222bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang recycle.timezone = Time.TIMEZONE_UTC; 223bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang recycle.set(utcTime); 224bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang recycle.timezone = TimeZone.getDefault().getID(); 225bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return recycle.normalize(true); 226bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 227bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 228bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 229bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Figure out the next time we should push widget updates, usually the time 230bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * calculated by {@link #getEventFlip(Cursor, long, long, boolean)}. 231bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 232bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param cursor Valid cursor on {@link Instances#CONTENT_URI} 233bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param events {@link MarkedEvents} parsed from the cursor 234bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 235bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private long calculateUpdateTime(Cursor cursor, MarkedEvents events) { 236bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long result = -1; 237bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (!events.markedIds.isEmpty()) { 238bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang cursor.moveToPosition(events.markedIds.get(0)); 239bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long start = cursor.getLong(INDEX_BEGIN); 240bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long end = cursor.getLong(INDEX_END); 241bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0; 242bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 243bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Adjust all-day times into local timezone 244bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (allDay) { 245bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang final Time recycle = new Time(); 246bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang start = convertUtcToLocal(recycle, start); 247bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang end = convertUtcToLocal(recycle, end); 248bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 249bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 250bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang result = getEventFlip(cursor, start, end, allDay); 251bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 252bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Make sure an update happens at midnight or earlier 253bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long midnight = getNextMidnightTimeMillis(); 254bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang result = Math.min(midnight, result); 255bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 256bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return result; 257bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 258bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 259bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private long getNextMidnightTimeMillis() { 260bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Time time = new Time(); 261bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.setToNow(); 262bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.monthDay++; 263bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.hour = 0; 264bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.minute = 0; 265bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.second = 0; 266bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long midnight = time.normalize(true); 267bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return midnight; 268bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 269bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 270bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 271bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Calculate flipping point for the given event; when we should hide this 272bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * event and show the next one. This is defined as the end time of the 273bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * event. 274bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 275bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param start Event start time in local timezone. 276bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param end Event end time in local timezone. 277bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 278bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static private long getEventFlip(Cursor cursor, long start, long end, boolean allDay) { 279bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return end; 280bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 281bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 282bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 283bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Set visibility of various widget components if there are events, or if no 284bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * events were found. 285bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 286bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param views Set of {@link RemoteViews} to apply visibility. 287bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param noEvents True if no events found, otherwise false. 288bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 289bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private void setNoEventsVisible(RemoteViews views, boolean noEvents) { 290bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.no_events, noEvents ? View.VISIBLE : View.GONE); 291bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.page_flipper, View.GONE); 292bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.single_page, View.GONE); 293bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 294bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 295bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 296bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Build a set of {@link RemoteViews} that describes how to update any 297bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * widget for a specific event instance. 298bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 299bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param cursor Valid cursor on {@link Instances#CONTENT_URI} 300bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param events {@link MarkedEvents} parsed from the cursor 301bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 302bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private RemoteViews getAppWidgetUpdate(Context context, Cursor cursor, MarkedEvents events) { 303bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget); 304bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang setNoEventsVisible(views, false); 305bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 306bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long currentTime = System.currentTimeMillis(); 307bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang CalendarAppWidgetModel model = buildAppWidgetModel(context, cursor, events, currentTime); 308bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 309bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang applyModelToView(context, model, views); 310bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 311bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Clicking on the widget launches Calendar 312bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long startTime = Math.max(currentTime, events.firstTime); 313bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 314bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang PendingIntent pendingIntent = getLaunchPendingIntent(context, startTime); 315bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setOnClickPendingIntent(R.id.appwidget, pendingIntent); 316bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 317e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang PendingIntent newEventIntent = getNewEventPendingIntent(context); 318e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang views.setOnClickPendingIntent(R.id.new_event_button, newEventIntent); 319e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang 320bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return views; 321bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 322bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 323bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private void applyModelToView(Context context, CalendarAppWidgetModel model, 324bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang RemoteViews views) { 325bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setTextViewText(R.id.day_of_week, model.dayOfWeek); 326bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setTextViewText(R.id.day_of_month, model.dayOfMonth); 327bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.no_events, model.visibNoEvents); 328bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 329bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Make sure we have a clean slate first 330bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.removeAllViews(R.id.page_flipper); 331bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.removeAllViews(R.id.single_page); 332bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 333bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // If we don't have any events, just hide the relevant views and return 334bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (model.visibNoEvents != View.GONE) { 335bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.page_flipper, View.GONE); 336bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.single_page, View.GONE); 337bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return; 338bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 339bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 340bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Luckily, length of this array is guaranteed to be even 341bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int pages = model.eventInfos.length / 2; 342bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 343bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // We use a separate container for the case of only one page to prevent 344bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // a ViewFlipper from repeatedly animating one view 345bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (pages > 1) { 346bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.page_flipper, View.VISIBLE); 347bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.single_page, View.GONE); 348bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 349bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.single_page, View.VISIBLE); 350bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(R.id.page_flipper, View.GONE); 351bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 352bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 353bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Iterate two at a time through the events and populate the views 354bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang for (int i = 0; i < model.eventInfos.length; i += 2) { 355bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang RemoteViews pageViews = new RemoteViews(context.getPackageName(), 356bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang R.layout.appwidget_page); 357bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang EventInfo e1 = model.eventInfos[i]; 358bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang EventInfo e2 = model.eventInfos[i + 1]; 359bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 360bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang updateTextView(pageViews, R.id.when1, e1.visibWhen, e1.when); 361bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang updateTextView(pageViews, R.id.where1, e1.visibWhere, e1.where); 362bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang updateTextView(pageViews, R.id.title1, e1.visibTitle, e1.title); 363bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang updateTextView(pageViews, R.id.when2, e2.visibWhen, e2.when); 364bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang updateTextView(pageViews, R.id.where2, e2.visibWhere, e2.where); 365bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang updateTextView(pageViews, R.id.title2, e2.visibTitle, e2.title); 366bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 367bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (pages > 1) { 368bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.addView(R.id.page_flipper, pageViews); 369bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang updateTextView(pageViews, R.id.page_count, View.VISIBLE, 370bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang makePageCount((i / 2) + 1, pages)); 371bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 372bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.addView(R.id.single_page, pageViews); 373bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 374bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 375bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 376bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 377bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 378bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static String makePageCount(int current, int total) { 379bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return Integer.toString(current) + " / " + Integer.toString(total); 380bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 381bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 382bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static void updateTextView(RemoteViews views, int id, int visibility, String string) { 383bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setViewVisibility(id, visibility); 384bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (visibility == View.VISIBLE) { 385bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setTextViewText(id, string); 386bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 387bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 388bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 389bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static CalendarAppWidgetModel buildAppWidgetModel(Context context, Cursor cursor, 390bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang MarkedEvents events, long currentTime) { 391bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int eventCount = events.markedIds.size(); 392bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang CalendarAppWidgetModel model = new CalendarAppWidgetModel(eventCount); 393bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Time time = new Time(); 394bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.set(currentTime); 395bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.monthDay++; 396bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.hour = 0; 397bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.minute = 0; 398bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.second = 0; 399bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long startOfNextDay = time.normalize(true); 400bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 401bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.set(currentTime); 402bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 403bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Calendar header 404bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang String dayOfWeek = DateUtils.getDayOfWeekString(time.weekDay + 1, DateUtils.LENGTH_MEDIUM) 405bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang .toUpperCase(); 406bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 407bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.dayOfWeek = dayOfWeek; 408bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.dayOfMonth = Integer.toString(time.monthDay); 409bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 410bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int i = 0; 411bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang for (Integer id : events.markedIds) { 412bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang populateEvent(context, cursor, id, model, time, i, true, startOfNextDay, currentTime); 413bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang i++; 414bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 415bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 416bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return model; 417bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 418bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 419bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 420bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Pulls the information for a single event from the cursor and populates 421bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * the corresponding model object with the data. 422bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 423bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param context a Context to use for accessing resources 424bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param cursor the cursor to retrieve the data from 425bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param rowId the ID of the row to retrieve 426bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param model the model object to populate 427bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param recycle a Time instance to recycle 428bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param eventIndex which event index in the model to populate 429bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param showTitleLocation whether or not to show the title and location 430bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param startOfNextDay the beginning of the next day 431bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param currentTime the current time 432bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 433bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static private void populateEvent(Context context, Cursor cursor, int rowId, 434bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang CalendarAppWidgetModel model, Time recycle, int eventIndex, 435bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean showTitleLocation, long startOfNextDay, long currentTime) { 436bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang cursor.moveToPosition(rowId); 437bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 438bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // When 439bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0; 440bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long start = cursor.getLong(INDEX_BEGIN); 441bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long end = cursor.getLong(INDEX_END); 442bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (allDay) { 443bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang start = convertUtcToLocal(recycle, start); 444bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang end = convertUtcToLocal(recycle, end); 445bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 446bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 447bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean eventIsInProgress = start <= currentTime && end > currentTime; 448bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean eventIsToday = start < startOfNextDay; 449bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean eventIsTomorrow = !eventIsToday && !eventIsInProgress 450bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang && (start < (startOfNextDay + DateUtils.DAY_IN_MILLIS)); 451bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 452bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Compute a human-readable string for the start time of the event 453bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang String whenString; 454bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (eventIsInProgress && allDay) { 455bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // All day events for the current day display as just "Today" 456bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang whenString = context.getString(R.string.today); 457bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else if (eventIsTomorrow && allDay) { 458bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // All day events for the next day display as just "Tomorrow" 459bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang whenString = context.getString(R.string.tomorrow); 460bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 461bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int flags = DateUtils.FORMAT_ABBREV_ALL; 462bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (allDay) { 463bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang flags |= DateUtils.FORMAT_UTC; 464bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 465bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang flags |= DateUtils.FORMAT_SHOW_TIME; 466bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (DateFormat.is24HourFormat(context)) { 467bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang flags |= DateUtils.FORMAT_24HOUR; 468bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 469bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 470bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Show day of the week if not today or tomorrow 471bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (!eventIsTomorrow && !eventIsToday) { 472bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang flags |= DateUtils.FORMAT_SHOW_WEEKDAY; 473bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 474bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang whenString = DateUtils.formatDateRange(context, start, start, flags); 475bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (eventIsTomorrow) { 476bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang whenString += (", "); 477bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang whenString += context.getString(R.string.tomorrow); 478bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else if (eventIsInProgress) { 479bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang whenString += " ("; 480bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang whenString += context.getString(R.string.in_progress); 481bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang whenString += ")"; 482bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 483bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 484bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 485bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.eventInfos[eventIndex].when = whenString; 486bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.eventInfos[eventIndex].visibWhen = View.VISIBLE; 487bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 488bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (showTitleLocation) { 489bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // What 490bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang String titleString = cursor.getString(INDEX_TITLE); 491bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (TextUtils.isEmpty(titleString)) { 492bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang titleString = context.getString(R.string.no_title_label); 493bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 494bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.eventInfos[eventIndex].title = titleString; 495bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.eventInfos[eventIndex].visibTitle = View.VISIBLE; 496bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 497bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Where 498bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang String whereString = cursor.getString(INDEX_EVENT_LOCATION); 499bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (!TextUtils.isEmpty(whereString)) { 500bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.eventInfos[eventIndex].visibWhere = View.VISIBLE; 501bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.eventInfos[eventIndex].where = whereString; 502bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 503bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang model.eventInfos[eventIndex].visibWhere = View.GONE; 504bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 505bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (LOGD) Log.d(TAG, " Title:" + titleString + " Where:" + whereString); 506bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 507bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 508bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 509bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 510bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Build a set of {@link RemoteViews} that describes an error state. 511bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 512bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private RemoteViews getAppWidgetNoEvents(Context context) { 513bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget); 514bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang setNoEventsVisible(views, true); 515bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 516bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Calendar header 517bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Time time = new Time(); 518bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang time.setToNow(); 519bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang String dayOfWeek = DateUtils.getDayOfWeekString(time.weekDay + 1, DateUtils.LENGTH_MEDIUM) 520bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang .toUpperCase(); 521bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setTextViewText(R.id.day_of_week, dayOfWeek); 522bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setTextViewText(R.id.day_of_month, Integer.toString(time.monthDay)); 523bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 524bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Clicking on widget launches the agenda view in Calendar 525bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang PendingIntent pendingIntent = getLaunchPendingIntent(context, 0); 526bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang views.setOnClickPendingIntent(R.id.appwidget, pendingIntent); 527bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 528bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return views; 529bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 530bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 531bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 532bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Build a {@link PendingIntent} to launch the Calendar app. This correctly 533bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * sets action, category, and flags so that we don't duplicate tasks when 534bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Calendar was also launched from a normal desktop icon. 535bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param goToTime time that calendar should take the user to 536bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 537bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private PendingIntent getLaunchPendingIntent(Context context, long goToTime) { 538bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Intent launchIntent = new Intent(); 539bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang String dataString = "content://com.android.calendar/time"; 540bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang launchIntent.setAction(Intent.ACTION_VIEW); 541bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 542bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | 543bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Intent.FLAG_ACTIVITY_CLEAR_TOP); 544bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (goToTime != 0) { 545d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan launchIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true); 546bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang dataString += "/" + goToTime; 547bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 548bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Uri data = Uri.parse(dataString); 549bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang launchIntent.setData(data); 550bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return PendingIntent.getActivity(context, 0 /* no requestCode */, 551bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang launchIntent, PendingIntent.FLAG_UPDATE_CURRENT); 552bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 553bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 554e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang private PendingIntent getNewEventPendingIntent(Context context) { 555e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang Intent newEventIntent = new Intent(Intent.ACTION_EDIT); 556e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang newEventIntent.setType("vnd.android.cursor.item/event"); 557e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang return PendingIntent.getActivity(context, 0, newEventIntent, 558e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang PendingIntent.FLAG_UPDATE_CURRENT); 559e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang } 560e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang 561bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static class MarkedEvents { 562bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 563bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 564bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * The row IDs of all events marked for display 565bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 566bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang List<Integer> markedIds = new ArrayList<Integer>(10); 567bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 568bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 569bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * The start time of the first marked event 570bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 571bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long firstTime = -1; 572bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 573bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** The number of events currently in progress */ 574bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int inProgressCount = 0; // Number of events with same start time as the primary evt. 575bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 576bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** The start time of the next upcoming event */ 577bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long primaryTime = -1; 578bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 579bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 580bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * The number of events that share the same start time as the next 581bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * upcoming event 582bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 583bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int primaryCount = 0; // Number of events with same start time as the secondary evt. 584bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 585bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** The start time of the next next upcoming event */ 586bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long secondaryTime = 1; 587bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 588bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 589bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * The number of events that share the same start time as the next next 590bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * upcoming event. 591bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 592bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int secondaryCount = 0; 593bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 594bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean watchFound = false; 595bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 596bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 597bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 598bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Walk the given instances cursor and build a list of marked events to be 599bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * used when updating the widget. This structure is also used to check if 600bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * updates are needed. 601bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 602bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param cursor Valid cursor across {@link Instances#CONTENT_URI}. 603bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param watchEventIds Specific events to watch for, setting 604bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * {@link MarkedEvents#watchFound} if found during marking. 605bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param now Current system time to use for this update, possibly from 606bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * {@link System#currentTimeMillis()} 607bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 608bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang @VisibleForTesting 609bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static MarkedEvents buildMarkedEvents(Cursor cursor, Set<Long> watchEventIds, long now) { 610bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang MarkedEvents events = new MarkedEvents(); 611bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang final Time recycle = new Time(); 612bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 613bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang cursor.moveToPosition(-1); 614bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang while (cursor.moveToNext()) { 615bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang int row = cursor.getPosition(); 616bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long eventId = cursor.getLong(INDEX_EVENT_ID); 617bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long start = cursor.getLong(INDEX_BEGIN); 618bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long end = cursor.getLong(INDEX_END); 619bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 620bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0; 621bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 622bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (LOGD) { 623bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Log.d(TAG, "Row #" + row + " allDay:" + allDay + " start:" + start + " end:" + end 624bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + " eventId:" + eventId); 625bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 626bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 627bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Adjust all-day times into local timezone 628bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (allDay) { 629bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang start = convertUtcToLocal(recycle, start); 630bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang end = convertUtcToLocal(recycle, end); 631bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 632bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 633bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang boolean inProgress = now < end && now > start; 634bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 635bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Skip events that have already passed their flip times 636bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long eventFlip = getEventFlip(cursor, start, end, allDay); 637bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (LOGD) Log.d(TAG, "Calculated flip time " + formatDebugTime(eventFlip, now)); 638bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (eventFlip < now) { 639bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang continue; 640bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 641bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 642bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Mark if we've encountered the watched event 643bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (watchEventIds != null && watchEventIds.contains(eventId)) { 644bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.watchFound = true; 645bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 646bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 647bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /* Scan through the events with the following logic: 648bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Rule #1 Show A) all the events that are in progress including 649bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * all day events and B) the next upcoming event and any events 650bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * with the same start time. 651bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 652bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Rule #2 If there are no events in progress, show A) the next 653bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * upcoming event and B) any events with the same start time. 654bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 655bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Rule #3 If no events start at the same time at A in rule 2, 656bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * show A) the next upcoming event and B) the following upcoming 657bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * event + any events with the same start time. 658bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 659bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (inProgress) { 660bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // events for part A of Rule #1 661bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.markedIds.add(row); 662bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.inProgressCount++; 663bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (events.firstTime == -1) { 664bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.firstTime = start; 665bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 666bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 667bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (events.primaryCount == 0) { 668bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // first upcoming event 669bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.markedIds.add(row); 670bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.primaryTime = start; 671bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.primaryCount++; 672bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (events.firstTime == -1) { 673bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.firstTime = start; 674bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 675bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else if (events.primaryTime == start) { 676bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // any events with same start time as first upcoming event 677bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.markedIds.add(row); 678bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.primaryCount++; 679bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else if (events.markedIds.size() == 1) { 680bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // only one upcoming event, so we take the next upcoming 681bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.markedIds.add(row); 682bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.secondaryTime = start; 683bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.secondaryCount++; 684bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else if (events.secondaryCount > 0 685bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang && events.secondaryTime == start) { 686bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // any events with same start time as next upcoming 687bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.markedIds.add(row); 688bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang events.secondaryCount++; 689bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 690bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // looks like we're done 691bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang break; 692bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 693bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 694bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 695bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return events; 696bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 697bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 698bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 699bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Query across all calendars for upcoming event instances from now until 700bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * some time in the future. 701bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 702bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param resolver {@link ContentResolver} to use when querying 703bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * {@link Instances#CONTENT_URI}. 704bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param searchDuration Distance into the future to look for event 705bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * instances, in milliseconds. 706bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * @param now Current system time to use for this update, possibly from 707bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * {@link System#currentTimeMillis()}. 708bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 709bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private Cursor getUpcomingInstancesCursor(ContentResolver resolver, 710bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long searchDuration, long now) { 711bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // Search for events from now until some time in the future 712bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long end = now + searchDuration; 713bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 714bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, 715bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang String.format("%d/%d", now, end)); 716bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 717bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang return resolver.query(uri, EVENT_PROJECTION, EVENT_SELECTION, null, 718bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang EVENT_SORT_ORDER); 719bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 720bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang} 721