diff options
author | Nicolai Ehemann <en@enlightened.de> | 2013-03-08 23:33:02 +0100 |
---|---|---|
committer | DvTonder <david.vantonder@gmail.com> | 2013-03-16 10:48:34 -0400 |
commit | a8a1502dd658652183ffc71246b642e8c4381847 (patch) | |
tree | 3981e8a9b3951c4ca80c1c11dacf6732bc7f185c /src/com/cyanogenmod/lockclock | |
parent | 8c94e90435ba0601db6ea66b3038f519f29af335 (diff) | |
download | android_packages_apps_LockClock-a8a1502dd658652183ffc71246b642e8c4381847.tar.gz android_packages_apps_LockClock-a8a1502dd658652183ffc71246b642e8c4381847.tar.bz2 android_packages_apps_LockClock-a8a1502dd658652183ffc71246b642e8c4381847.zip |
cLock: Make the list of calendar entries scrollable (max. 10 entries)
This changes the rendering and calendar data aquiration quite drastically, as
a new widget service had to be introduced.
Change-Id: I5bcdaa952acde2caa5a3f04b2c8c98868fd7cbfb
Diffstat (limited to 'src/com/cyanogenmod/lockclock')
-rw-r--r-- | src/com/cyanogenmod/lockclock/ClockWidgetProvider.java | 15 | ||||
-rwxr-xr-x[-rw-r--r--] | src/com/cyanogenmod/lockclock/ClockWidgetService.java | 382 | ||||
-rwxr-xr-x | src/com/cyanogenmod/lockclock/calendar/CalendarWidgetService.java | 454 | ||||
-rw-r--r-- | src/com/cyanogenmod/lockclock/misc/CalendarInfo.java | 8 | ||||
-rwxr-xr-x[-rw-r--r--] | src/com/cyanogenmod/lockclock/misc/Constants.java | 2 |
5 files changed, 489 insertions, 372 deletions
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java index d69b7ff..f9d281a 100644 --- a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java +++ b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java @@ -16,15 +16,12 @@ package com.cyanogenmod.lockclock; -import android.app.AlarmManager; -import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.util.Log; - import com.cyanogenmod.lockclock.misc.Constants; import com.cyanogenmod.lockclock.weather.WeatherUpdateService; @@ -60,16 +57,16 @@ public class ClockWidgetProvider extends AppWidgetProvider { context.stopService(i); } - // Boot completed, schedule next weather update + // Boot completed, schedule next weather update } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { WeatherUpdateService.scheduleNextUpdate(context); - // A widget has been deleted, prevent our handling and ask the super class handle it + // A widget has been deleted, prevent our handling and ask the super class handle it } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action) || AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) { super.onReceive(context, intent); - // Calendar, Time or a settings change, force a calendar refresh + // Calendar, Time or a settings change, force a calendar refresh } else if (Intent.ACTION_PROVIDER_CHANGED.equals(action) || Intent.ACTION_TIME_CHANGED.equals(action) || Intent.ACTION_TIMEZONE_CHANGED.equals(action) @@ -78,8 +75,8 @@ public class ClockWidgetProvider extends AppWidgetProvider { || ClockWidgetService.ACTION_REFRESH_CALENDAR.equals(action)) { updateWidgets(context, true); - // Something we did not handle, let the super class deal with it. - // This includes the REFRESH_CLOCK intent from Clock settings + // Something we did not handle, let the super class deal with it. + // This includes the REFRESH_CLOCK intent from Clock settings } else { if (D) Log.v(TAG, "We did not handle the intent, trigger normal handling"); super.onReceive(context, intent); @@ -95,7 +92,7 @@ public class ClockWidgetProvider extends AppWidgetProvider { Intent i = new Intent(context.getApplicationContext(), ClockWidgetService.class); i.setAction(refreshCalendar ? ClockWidgetService.ACTION_REFRESH_CALENDAR - : ClockWidgetService.ACTION_REFRESH); + : ClockWidgetService.ACTION_REFRESH); // Start the service. The service itself will take care of scheduling refreshes if needed if (D) Log.d(TAG, "Starting the service to update the widgets..."); diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetService.java b/src/com/cyanogenmod/lockclock/ClockWidgetService.java index ae741e7..30b3cc8 100644..100755 --- a/src/com/cyanogenmod/lockclock/ClockWidgetService.java +++ b/src/com/cyanogenmod/lockclock/ClockWidgetService.java @@ -24,29 +24,22 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.database.Cursor; import android.net.Uri; -import android.provider.CalendarContract; import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.format.Time; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.RemoteViews; -import com.cyanogenmod.lockclock.misc.CalendarInfo; -import com.cyanogenmod.lockclock.misc.CalendarInfo.EventInfo; +import com.cyanogenmod.lockclock.calendar.CalendarWidgetService; import com.cyanogenmod.lockclock.misc.Constants; import com.cyanogenmod.lockclock.misc.Preferences; import com.cyanogenmod.lockclock.misc.WidgetUtils; import com.cyanogenmod.lockclock.weather.WeatherInfo; import com.cyanogenmod.lockclock.weather.WeatherUpdateService; - import java.util.Date; -import java.util.Set; public class ClockWidgetService extends IntentService { private static final String TAG = "ClockWidgetService"; @@ -55,12 +48,8 @@ public class ClockWidgetService extends IntentService { public static final String ACTION_REFRESH = "com.cyanogenmod.lockclock.action.REFRESH_WIDGET"; public static final String ACTION_REFRESH_CALENDAR = "com.cyanogenmod.lockclock.action.REFRESH_CALENDAR"; - private static final long DAY_IN_MILLIS = 24L * 60L * 60L * 1000L; - private int[] mWidgetIds; private AppWidgetManager mAppWidgetManager; - private boolean mRefreshCalendar; - public ClockWidgetService() { super("ClockWidgetService"); } @@ -72,8 +61,6 @@ public class ClockWidgetService extends IntentService { ComponentName thisWidget = new ComponentName(this, ClockWidgetProvider.class); mAppWidgetManager = AppWidgetManager.getInstance(this); mWidgetIds = mAppWidgetManager.getAppWidgetIds(thisWidget); - - mRefreshCalendar = false; } @Override @@ -81,7 +68,7 @@ public class ClockWidgetService extends IntentService { if (D) Log.d(TAG, "Got intent " + intent); if (intent != null && ACTION_REFRESH_CALENDAR.equals(intent.getAction())) { if (D) Log.v(TAG, "Forcing a calendar refresh"); - mRefreshCalendar = true; + mAppWidgetManager.notifyAppWidgetViewDataChanged(mWidgetIds, R.id.calendar_list); } if (mWidgetIds != null && mWidgetIds.length != 0) { @@ -123,7 +110,7 @@ public class ClockWidgetService extends IntentService { // Don't bother with Calendar if its not enabled if (showCalendar) { - showCalendar &= refreshCalendar(remoteViews); + refreshCalendar(remoteViews, id); } // Hide the Loading indicator @@ -160,10 +147,6 @@ public class ClockWidgetService extends IntentService { // Do the update mAppWidgetManager.updateAppWidget(id, remoteViews); } - - if (showCalendar) { - scheduleCalendarUpdate(); - } } //=============================================================================================== @@ -408,350 +391,35 @@ public class ClockWidgetService extends IntentService { //=============================================================================================== // Calendar related functionality //=============================================================================================== - private static CalendarInfo mCalendarInfo = new CalendarInfo(); - - private boolean refreshCalendar(RemoteViews calendarViews) { - // Load the settings - Set<String> calendarList = Preferences.calendarsToDisplay(this); - boolean remindersOnly = Preferences.showEventsWithRemindersOnly(this); - boolean hideAllDay = !Preferences.showAllDayEvents(this); - long lookAhead = Preferences.lookAheadTimeInMs(this); - boolean hasEvents = false; + private void refreshCalendar(RemoteViews calendarViews, int widgetId) { + // Calendar icon: Overlay the selected color and set the imageview int color = Preferences.calendarFontColor(this); - int detailsColor = Preferences.calendarDetailsFontColor(this); - - // Remove all the views to start - calendarViews.removeAllViews(R.id.calendar_panel); - - // If we don't have any events yet or forcing a refresh, get the next batch of events - if (!mCalendarInfo.hasEvents() || mRefreshCalendar) { - if (D) Log.d(TAG, "Checking for calendar events..."); - getCalendarEvents(lookAhead, calendarList, remindersOnly, hideAllDay); - mRefreshCalendar = false; - } - - // Iterate through the events and add them to the views - for (EventInfo event : mCalendarInfo.getEvents()) { - final RemoteViews itemViews = new RemoteViews(getPackageName(), R.layout.calendar_item); - - // Only set the icon on the first event - if (!hasEvents) { - // Overlay the selected color and set the imageview - itemViews.setImageViewBitmap(R.id.calendar_icon, - WidgetUtils.getOverlaidBitmap(this, R.drawable.ic_lock_idle_calendar, color)); - } - - // Add the event text fields - itemViews.setTextViewText(R.id.calendar_event_title, event.title); - itemViews.setTextViewText(R.id.calendar_event_details, event.description); - itemViews.setTextColor(R.id.calendar_event_title, color); - itemViews.setTextColor(R.id.calendar_event_details, detailsColor); - - if (D) Log.v(TAG, "Showing event: " + event.title); - - // Add the view to the panel - calendarViews.addView(R.id.calendar_panel, itemViews); - hasEvents = true; - } - - // Register an onClickListener on Calendar if it contains any events, starting the Calendar app - if (hasEvents) { - Intent i = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_CALENDAR); - PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); - calendarViews.setOnClickPendingIntent(R.id.calendar_panel, pi); - } - return hasEvents; - } - - /** - * Get the next set of calendar events (up to MAX_CALENDAR_ITEMS) within a certain look-ahead time. - * Result is stored in the CalendarInfo object - */ - private void getCalendarEvents(long lookahead, Set<String> calendars, - boolean remindersOnly, boolean hideAllDay) { - long now = System.currentTimeMillis(); - long later = now + lookahead; - - // Build the 'where' clause - StringBuilder where = new StringBuilder(); - if (remindersOnly) { - where.append(CalendarContract.Events.HAS_ALARM + "=1"); - } - if (hideAllDay) { - if (remindersOnly) { - where.append(" AND "); - } - where.append(CalendarContract.Events.ALL_DAY + "!=1"); - } - if (calendars != null && calendars.size() > 0) { - if (remindersOnly || hideAllDay) { - where.append(" AND "); - } - where.append(CalendarContract.Events.CALENDAR_ID + " in ("); - int i = 0; - for (String s : calendars) { - where.append(s); - if (i != calendars.size() - 1) { - where.append(","); - } - i++; - } - where.append(") "); - } - - // Projection array - String[] projection = new String[] { - CalendarContract.Events._ID, - CalendarContract.Events.TITLE, - CalendarContract.Instances.BEGIN, - CalendarContract.Instances.END, - CalendarContract.Events.DESCRIPTION, - CalendarContract.Events.EVENT_LOCATION, - CalendarContract.Events.ALL_DAY, - }; - - // The indices for the projection array - int EVENT_ID_INDEX = 0; - int TITLE_INDEX = 1; - int BEGIN_TIME_INDEX = 2; - int END_TIME_INDEX = 3; - int DESCRIPTION_INDEX = 4; - int LOCATION_INDEX = 5; - int ALL_DAY_INDEX = 6; - - // all day events are stored in UTC, that is why we need to fetch events after 'later' - Uri uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI, - String.format("%d/%d", now - DAY_IN_MILLIS, later + DAY_IN_MILLIS)); - Cursor cursor = null; - mCalendarInfo.clearEvents(); - - try { - cursor = getContentResolver().query(uri, projection, - where.toString(), null, CalendarContract.Instances.BEGIN + " ASC"); - - if (cursor != null) { - final int showLocation = Preferences.calendarLocationMode(this); - final int showDescription = Preferences.calendarDescriptionMode(this); - final Time time = new Time(); - int eventCount = 0; - - cursor.moveToPosition(-1); - // Iterate through returned rows to a maximum number of calendar events - while (cursor.moveToNext() && eventCount < Constants.MAX_CALENDAR_ITEMS) { - long eventId = cursor.getLong(EVENT_ID_INDEX); - String title = cursor.getString(TITLE_INDEX); - long begin = cursor.getLong(BEGIN_TIME_INDEX); - long end = cursor.getLong(END_TIME_INDEX); - String description = cursor.getString(DESCRIPTION_INDEX); - String location = cursor.getString(LOCATION_INDEX); - boolean allDay = cursor.getInt(ALL_DAY_INDEX) != 0; - int format = 0; - - if (allDay) { - begin = convertUtcToLocal(time, begin); - end = convertUtcToLocal(time, end); - } - - if (end < now || begin > later) { - continue; - } - - if (D) Log.v(TAG, "Adding event: " + title + " with id: " + eventId); - - // Start building the event details string - // Starting with the date - StringBuilder sb = new StringBuilder(); - - if (allDay) { - format = Constants.CALENDAR_FORMAT_ALLDAY; - } else if (DateUtils.isToday(begin)) { - format = Constants.CALENDAR_FORMAT_TODAY; - } else { - format = Constants.CALENDAR_FORMAT_FUTURE; - } - if (allDay || begin == end) { - sb.append(DateUtils.formatDateTime(this, begin, format)); - } else { - sb.append(DateUtils.formatDateRange(this, begin, end, format)); - } - - // Add the event location if it should be shown - if (showLocation != Preferences.SHOW_NEVER && !TextUtils.isEmpty(location)) { - switch (showLocation) { - case Preferences.SHOW_FIRST_LINE: - int stringEnd = location.indexOf('\n'); - if(stringEnd == -1) { - sb.append(": " + location); - } else { - sb.append(": " + location.substring(0, stringEnd)); - } - break; - case Preferences.SHOW_ALWAYS: - sb.append(": " + location); - break; - } - } - - // Add the event description if it should be shown - if (showDescription != Preferences.SHOW_NEVER && !TextUtils.isEmpty(description)) { - // Show the appropriate separator - if (showLocation == Preferences.SHOW_NEVER) { - sb.append(": "); - } else { - sb.append(" - "); - } - - switch (showDescription) { - case Preferences.SHOW_FIRST_LINE: - int stringEnd = description.indexOf('\n'); - if(stringEnd == -1) { - sb.append(description); - } else { - sb.append(description.substring(0, stringEnd)); - } - break; - case Preferences.SHOW_ALWAYS: - sb.append(description); - break; - } - } - - // Add the event details to the CalendarInfo object and move to next record - mCalendarInfo.addEvent(populateEventInfo(eventId, title, sb.toString(), begin, end, allDay)); - eventCount++; - } - } - } catch (Exception e) { - // Do nothing - } finally { - if (cursor != null) { - cursor.close(); - } - } - - // check for first event outside of lookahead window - long endOfLookahead = now + lookahead; - long minUpdateTime = getMinUpdateFromNow(endOfLookahead); - - // don't bother with querying if the end result is later than the - // minimum update time anyway - if (endOfLookahead < minUpdateTime) { - if (where.length() > 0) { - where.append(" AND "); - } - where.append(CalendarContract.Instances.BEGIN); - where.append(" > "); - where.append(endOfLookahead); - - uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI, - String.format("%d/%d", endOfLookahead, minUpdateTime)); - projection = new String[] { CalendarContract.Instances.BEGIN }; - cursor = getContentResolver().query(uri, projection, where.toString(), null, - CalendarContract.Instances.BEGIN + " ASC limit 1"); - - if (cursor != null) { - if (cursor.moveToFirst()) { - mCalendarInfo.setFollowingEventStart(cursor.getLong(0)); - } - cursor.close(); - } - } - } - - private long convertUtcToLocal(Time time, long utcTime) { - time.timezone = Time.TIMEZONE_UTC; - time.set(utcTime); - time.timezone = Time.getCurrentTimezone(); - return time.normalize(true); + calendarViews.setImageViewBitmap(R.id.calendar_icon, + WidgetUtils.getOverlaidBitmap(this, R.drawable.ic_lock_idle_calendar, color)); + + // Set up and start the Calendar RemoteViews service + final Intent remoteAdapterIntent = new Intent(this, CalendarWidgetService.class); + remoteAdapterIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); + remoteAdapterIntent.setData(Uri.parse(remoteAdapterIntent.toUri(Intent.URI_INTENT_SCHEME))); + calendarViews.setRemoteAdapter(R.id.calendar_list, remoteAdapterIntent); + calendarViews.setEmptyView(R.id.calendar_list, R.id.calendar_empty_view); + + // Register an onClickListener on Calendar starting the Calendar app + final Intent calendarClickIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_CALENDAR); + final PendingIntent calendarClickPendingIntent = PendingIntent.getActivity(this, 0, calendarClickIntent, PendingIntent.FLAG_UPDATE_CURRENT); + calendarViews.setOnClickPendingIntent(R.id.calendar_icon, calendarClickPendingIntent); + + final Intent eventClickIntent = new Intent(Intent.ACTION_VIEW); + final PendingIntent eventClickPendingIntent = PendingIntent.getActivity(this, 0, eventClickIntent, PendingIntent.FLAG_UPDATE_CURRENT); + calendarViews.setPendingIntentTemplate(R.id.calendar_list, eventClickPendingIntent); } - /** - * Construct the EventInfo object - */ - private EventInfo populateEventInfo(long eventId, String title, String description, - long begin, long end, boolean allDay) { - EventInfo eventInfo = new EventInfo(); - - // Populate - eventInfo.id = eventId; - eventInfo.title = title; - eventInfo.description = description; - eventInfo.start = begin; - eventInfo.end = end; - eventInfo.allDay = allDay; - - return eventInfo; - } - - //=============================================================================================== - // Update timer related functionality - //=============================================================================================== - /** - * Calculates and returns the next time we should push widget updates. - */ - private long calculateUpdateTime() { - final long now = System.currentTimeMillis(); - long lookAhead = Preferences.lookAheadTimeInMs(this); - long minUpdateTime = getMinUpdateFromNow(now); - - // Check if there is a calendar event earlier - for (EventInfo event : mCalendarInfo.getEvents()) { - final long end = event.end; - final long start = event.start; - if (now < start) { - minUpdateTime = Math.min(minUpdateTime, start); - } - if (now < end) { - minUpdateTime = Math.min(minUpdateTime, end); - } - } - - if (mCalendarInfo.getFollowingEventStart() > 0) { - // Make sure to update when the next event gets into the lookahead window - minUpdateTime = Math.min(minUpdateTime, mCalendarInfo.getFollowingEventStart() - lookAhead); - } - - // Construct a log entry in human readable form - if (D) { - Date date1 = new Date(now); - Date date2 = new Date(minUpdateTime); - Log.i(TAG, "Chronus: It is now " + DateFormat.getTimeFormat(this).format(date1) - + ", next widget update at " + DateFormat.getTimeFormat(this).format(date2)); - } - - // Return the next update time - return minUpdateTime; - } - - private long getMinUpdateFromNow(long now) { - /* we update at least once a day */ - return now + DAY_IN_MILLIS; - } - - private static PendingIntent getRefreshIntent(Context context) { + public static PendingIntent getRefreshIntent(Context context) { Intent i = new Intent(context, ClockWidgetService.class); - i.setAction(ACTION_REFRESH_CALENDAR); + i.setAction(ClockWidgetService.ACTION_REFRESH_CALENDAR); return PendingIntent.getService(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); } - /** - * Schedule an alarm to trigger an update at the next weather refresh or at the next event - * time boundary (start/end). - */ - private void scheduleCalendarUpdate() { - PendingIntent pi = getRefreshIntent(this); - long updateTime = calculateUpdateTime(); - - // Clear any old alarms and schedule the new alarm - // Since the updates are now only done very infrequently, it can wake the device to ensure the - // latest date is available when the user turns the screen on after a few hours sleep - AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - am.cancel(pi); - if (updateTime > 0) { - am.set(AlarmManager.RTC_WAKEUP, updateTime, pi); - } - } - public static void cancelUpdates(Context context) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.cancel(getRefreshIntent(context)); diff --git a/src/com/cyanogenmod/lockclock/calendar/CalendarWidgetService.java b/src/com/cyanogenmod/lockclock/calendar/CalendarWidgetService.java new file mode 100755 index 0000000..c4d5078 --- /dev/null +++ b/src/com/cyanogenmod/lockclock/calendar/CalendarWidgetService.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project (DvTonder) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.lockclock.calendar; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.provider.CalendarContract; +import android.provider.CalendarContract.Events; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.util.Log; +import android.widget.RemoteViews; +import android.widget.RemoteViewsService; +import android.widget.RemoteViewsService.RemoteViewsFactory; + +import com.cyanogenmod.lockclock.ClockWidgetService; +import com.cyanogenmod.lockclock.R; +import com.cyanogenmod.lockclock.misc.CalendarInfo; +import com.cyanogenmod.lockclock.misc.Constants; +import com.cyanogenmod.lockclock.misc.Preferences; +import com.cyanogenmod.lockclock.misc.CalendarInfo.EventInfo; +import java.util.Date; +import java.util.Set; + +public class CalendarWidgetService extends RemoteViewsService { + + @Override + public RemoteViewsFactory onGetViewFactory(Intent intent) { + return new CalendarRemoteViewsFactory(this.getApplicationContext(), intent); + } + +} + +class CalendarRemoteViewsFactory implements RemoteViewsFactory { + private static final String TAG = "CalendarRemoteViewsFactory"; + private static boolean D = Constants.DEBUG; + + private static final long DAY_IN_MILLIS = 24L * 60L * 60L * 1000L; + + private Context mContext; + private CalendarInfo mCalendarInfo = new CalendarInfo(); + + public CalendarRemoteViewsFactory(Context applicationContext, Intent intent) { + mContext = applicationContext; + } + + @Override + public int getCount() { + return mCalendarInfo.getEvents().size(); + } + + @Override + public long getItemId(int position) { + return mCalendarInfo.getEvents().get(position).id; + } + + @Override + public RemoteViews getLoadingView() { + return null; + } + + @Override + public RemoteViews getViewAt(int position) { + if (0 > position || mCalendarInfo.getEvents().size() < position) { + return null; + } + + int color = Preferences.calendarFontColor(mContext); + int detailsColor = Preferences.calendarDetailsFontColor(mContext); + final RemoteViews itemViews = new RemoteViews(mContext.getPackageName(), + R.layout.calendar_item); + final EventInfo event = mCalendarInfo.getEvents().get(position); + + // Add the event text fields + itemViews.setTextViewText(R.id.calendar_event_title, event.title); + itemViews.setTextViewText(R.id.calendar_event_details, event.description); + itemViews.setTextColor(R.id.calendar_event_title, color); + itemViews.setTextColor(R.id.calendar_event_details, detailsColor); + if (D) Log.v(TAG, "Showing at position " + position + " event: " + event.title); + + final Intent fillInIntent = new Intent(); + fillInIntent.setData(ContentUris.withAppendedId(Events.CONTENT_URI, event.id)); + // work around stock calendar not displaying the correct date with only uri + fillInIntent.putExtra("beginTime", event.start); + fillInIntent.putExtra("endTime", event.end); + fillInIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NO_HISTORY + | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + itemViews.setOnClickFillInIntent(R.id.calendar_item, fillInIntent); + + return itemViews; + } + + @Override + public int getViewTypeCount() { + // There's only one view type for the events + return 1; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public void onCreate() { + updateCalendarInfo(mContext, false); + } + + @Override + public void onDataSetChanged() { + if (D) Log.v(TAG, "onDataSetChanged()"); + updateCalendarInfo(mContext, true); + } + + private void updateCalendarInfo(Context context, boolean force) { + // Load the settings + Set<String> calendarList = Preferences.calendarsToDisplay(context); + boolean remindersOnly = Preferences.showEventsWithRemindersOnly(context); + boolean hideAllDay = !Preferences.showAllDayEvents(context); + long lookAhead = Preferences.lookAheadTimeInMs(context); + + // If we don't have any events yet or forcing a refresh, get the next + // batch of events + if (force || !mCalendarInfo.hasEvents()) { + if (D) Log.d(TAG, "Checking for calendar events..." + (force ? " (forced)" : "")); + getCalendarEvents(context, lookAhead, calendarList, remindersOnly, hideAllDay); + } + scheduleCalendarUpdate(context); + } + + /** + * Get the next set of calendar events (up to MAX_CALENDAR_ITEMS) within a + * certain look-ahead time. Result is stored in the CalendarInfo object + */ + private void getCalendarEvents(Context context, long lookahead, Set<String> calendars, + boolean remindersOnly, boolean hideAllDay) { + long now = System.currentTimeMillis(); + long later = now + lookahead; + CalendarInfo newCalendarInfo = new CalendarInfo(); + + // Build the 'where' clause + StringBuilder where = new StringBuilder(); + if (remindersOnly) { + where.append(CalendarContract.Events.HAS_ALARM + "=1"); + } + if (hideAllDay) { + if (remindersOnly) { + where.append(" AND "); + } + where.append(CalendarContract.Events.ALL_DAY + "!=1"); + } + if (calendars != null && calendars.size() > 0) { + if (remindersOnly || hideAllDay) { + where.append(" AND "); + } + where.append(CalendarContract.Events.CALENDAR_ID + " in ("); + int i = 0; + for (String s : calendars) { + where.append(s); + if (i != calendars.size() - 1) { + where.append(","); + } + i++; + } + where.append(") "); + } + + // Projection array + String[] projection = new String[] { + CalendarContract.Instances.EVENT_ID, + CalendarContract.Events.TITLE, + CalendarContract.Instances.BEGIN, + CalendarContract.Instances.END, + CalendarContract.Events.DESCRIPTION, + CalendarContract.Events.EVENT_LOCATION, + CalendarContract.Events.ALL_DAY, + }; + + // The indices for the projection array + int EVENT_ID_INDEX = 0; + int TITLE_INDEX = 1; + int BEGIN_TIME_INDEX = 2; + int END_TIME_INDEX = 3; + int DESCRIPTION_INDEX = 4; + int LOCATION_INDEX = 5; + int ALL_DAY_INDEX = 6; + + // all day events are stored in UTC, that is why we need to fetch events + // after 'later' + Uri uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI, + String.format("%d/%d", now - DAY_IN_MILLIS, later + DAY_IN_MILLIS)); + Cursor cursor = null; + + try { + cursor = context.getContentResolver().query(uri, projection, + where.toString(), null, CalendarContract.Instances.BEGIN + " ASC"); + + if (cursor != null) { + final int showLocation = Preferences.calendarLocationMode(context); + final int showDescription = Preferences.calendarDescriptionMode(context); + final Time time = new Time(); + int eventCount = 0; + + cursor.moveToPosition(-1); + // Iterate through returned rows to a maximum number of calendar + // events + while (cursor.moveToNext() && eventCount < Constants.MAX_CALENDAR_ITEMS) { + long eventId = cursor.getLong(EVENT_ID_INDEX); + String title = cursor.getString(TITLE_INDEX); + long begin = cursor.getLong(BEGIN_TIME_INDEX); + long end = cursor.getLong(END_TIME_INDEX); + String description = cursor.getString(DESCRIPTION_INDEX); + String location = cursor.getString(LOCATION_INDEX); + boolean allDay = cursor.getInt(ALL_DAY_INDEX) != 0; + int format = 0; + + if (allDay) { + begin = convertUtcToLocal(time, begin); + end = convertUtcToLocal(time, end); + } + + if (end < now || begin > later) { + continue; + } + + if (D) Log.v(TAG, "Adding event: " + title + " with id: " + eventId); + + // Start building the event details string + // Starting with the date + StringBuilder sb = new StringBuilder(); + + if (allDay) { + format = Constants.CALENDAR_FORMAT_ALLDAY; + } else if (DateUtils.isToday(begin)) { + format = Constants.CALENDAR_FORMAT_TODAY; + } else { + format = Constants.CALENDAR_FORMAT_FUTURE; + } + if (allDay || begin == end) { + sb.append(DateUtils.formatDateTime(context, begin, format)); + } else { + sb.append(DateUtils.formatDateRange(context, begin, end, format)); + } + + // Add the event location if it should be shown + if (showLocation != Preferences.SHOW_NEVER && !TextUtils.isEmpty(location)) { + switch (showLocation) { + case Preferences.SHOW_FIRST_LINE: + int stringEnd = location.indexOf('\n'); + if (stringEnd == -1) { + sb.append(": " + location); + } else { + sb.append(": " + location.substring(0, stringEnd)); + } + break; + case Preferences.SHOW_ALWAYS: + sb.append(": " + location); + break; + } + } + + // Add the event description if it should be shown + if (showDescription != Preferences.SHOW_NEVER + && !TextUtils.isEmpty(description)) { + // Show the appropriate separator + if (showLocation == Preferences.SHOW_NEVER) { + sb.append(": "); + } else { + sb.append(" - "); + } + + switch (showDescription) { + case Preferences.SHOW_FIRST_LINE: + int stringEnd = description.indexOf('\n'); + if (stringEnd == -1) { + sb.append(description); + } else { + sb.append(description.substring(0, stringEnd)); + } + break; + case Preferences.SHOW_ALWAYS: + sb.append(description); + break; + } + } + + // Add the event details to the CalendarInfo object and move + // to next record + newCalendarInfo.addEvent(populateEventInfo(eventId, title, sb.toString(), begin, + end, allDay)); + eventCount++; + } + } + } catch (Exception e) { + // Do nothing + } finally { + mCalendarInfo = newCalendarInfo; + if (cursor != null) { + cursor.close(); + } + } + + // check for first event outside of lookahead window + long endOfLookahead = now + lookahead; + long minUpdateTime = getMinUpdateFromNow(endOfLookahead); + + // don't bother with querying if the end result is later than the + // minimum update time anyway + if (endOfLookahead < minUpdateTime) { + if (where.length() > 0) { + where.append(" AND "); + } + where.append(CalendarContract.Instances.BEGIN); + where.append(" > "); + where.append(endOfLookahead); + + uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI, + String.format("%d/%d", endOfLookahead, minUpdateTime)); + projection = new String[] { + CalendarContract.Instances.BEGIN + }; + cursor = context.getContentResolver().query(uri, projection, where.toString(), null, + CalendarContract.Instances.BEGIN + " ASC limit 1"); + + if (cursor != null) { + if (cursor.moveToFirst()) { + mCalendarInfo.setFollowingEventStart(cursor.getLong(0)); + } + cursor.close(); + } + } + } + + private static long convertUtcToLocal(Time time, long utcTime) { + time.timezone = Time.TIMEZONE_UTC; + time.set(utcTime); + time.timezone = Time.getCurrentTimezone(); + return time.normalize(true); + } + + /** + * Construct the EventInfo object + */ + private static EventInfo populateEventInfo(long eventId, String title, String description, + long begin, long end, boolean allDay) { + EventInfo eventInfo = new EventInfo(); + + // Populate + eventInfo.id = eventId; + eventInfo.title = title; + eventInfo.description = description; + eventInfo.start = begin; + eventInfo.end = end; + eventInfo.allDay = allDay; + + return eventInfo; + } + + private static long getMinUpdateFromNow(long now) { + /* we update at least once a day */ + return now + DAY_IN_MILLIS; + } + + // =============================================================================================== + // Update timer related functionality + // =============================================================================================== + /** + * Calculates and returns the next time we should push widget updates. + */ + private long calculateUpdateTime(Context context) { + final long now = System.currentTimeMillis(); + long lookAhead = Preferences.lookAheadTimeInMs(context); + long minUpdateTime = getMinUpdateFromNow(now); + + // Check if there is a calendar event earlier + for (EventInfo event : mCalendarInfo.getEvents()) { + final long end = event.end; + final long start = event.start; + if (now < start) { + minUpdateTime = Math.min(minUpdateTime, start); + } + if (now < end) { + minUpdateTime = Math.min(minUpdateTime, end); + } + } + + if (mCalendarInfo.getFollowingEventStart() > 0) { + // Make sure to update when the next event gets into the lookahead + // window + minUpdateTime = Math.min(minUpdateTime, mCalendarInfo.getFollowingEventStart() + - lookAhead); + } + + // Construct a log entry in human readable form + if (D) { + Date date1 = new Date(now); + Date date2 = new Date(minUpdateTime); + Log.i(TAG, "Chronus: It is now " + DateFormat.getTimeFormat(context).format(date1) + + ", next widget update at " + DateFormat.getTimeFormat(context).format(date2)); + } + + // Return the next update time + return minUpdateTime; + } + + /** + * Schedule an alarm to trigger an update at the next weather refresh or at + * the next event time boundary (start/end). + */ + private void scheduleCalendarUpdate(Context context) { + PendingIntent pi = ClockWidgetService.getRefreshIntent(context); + long updateTime = calculateUpdateTime(context); + + // Clear any old alarms and schedule the new alarm + // Since the updates are now only done very infrequently, it can wake + // the device to ensure the + // latest date is available when the user turns the screen on after a + // few hours sleep + AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + am.cancel(pi); + if (updateTime > 0) { + am.set(AlarmManager.RTC_WAKEUP, updateTime, pi); + } + } + + @Override + public void onDestroy() { + mCalendarInfo.clearEvents(); + } +} diff --git a/src/com/cyanogenmod/lockclock/misc/CalendarInfo.java b/src/com/cyanogenmod/lockclock/misc/CalendarInfo.java index 681a75c..7e634cf 100644 --- a/src/com/cyanogenmod/lockclock/misc/CalendarInfo.java +++ b/src/com/cyanogenmod/lockclock/misc/CalendarInfo.java @@ -22,13 +22,11 @@ import java.util.Collections; import java.util.List; public class CalendarInfo { - private static List<EventInfo> mEventsList; - private static long mFollowingEventStart; + private List<EventInfo> mEventsList; + private long mFollowingEventStart; public CalendarInfo() { - if (mEventsList == null) { - mEventsList = new ArrayList<EventInfo>(Constants.MAX_CALENDAR_ITEMS); - } + mEventsList = new ArrayList<EventInfo>(Constants.MAX_CALENDAR_ITEMS); mFollowingEventStart = 0; } diff --git a/src/com/cyanogenmod/lockclock/misc/Constants.java b/src/com/cyanogenmod/lockclock/misc/Constants.java index 6f59a87..09ca18a 100644..100755 --- a/src/com/cyanogenmod/lockclock/misc/Constants.java +++ b/src/com/cyanogenmod/lockclock/misc/Constants.java @@ -60,7 +60,7 @@ public class Constants { public static final String WEATHER_LAST_UPDATE = "last_weather_update"; public static final String WEATHER_DATA = "weather_data"; - public static final int MAX_CALENDAR_ITEMS = 3; + public static final int MAX_CALENDAR_ITEMS = 10; public static final int CALENDAR_FORMAT_TIME = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NO_NOON |