aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/cyanogenmod/lockclock
diff options
context:
space:
mode:
authorNicolai Ehemann <en@enlightened.de>2013-03-08 23:33:02 +0100
committerDvTonder <david.vantonder@gmail.com>2013-03-16 10:48:34 -0400
commita8a1502dd658652183ffc71246b642e8c4381847 (patch)
tree3981e8a9b3951c4ca80c1c11dacf6732bc7f185c /src/com/cyanogenmod/lockclock
parent8c94e90435ba0601db6ea66b3038f519f29af335 (diff)
downloadandroid_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.java15
-rwxr-xr-x[-rw-r--r--]src/com/cyanogenmod/lockclock/ClockWidgetService.java382
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/calendar/CalendarWidgetService.java454
-rw-r--r--src/com/cyanogenmod/lockclock/misc/CalendarInfo.java8
-rwxr-xr-x[-rw-r--r--]src/com/cyanogenmod/lockclock/misc/Constants.java2
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