CalendarAppWidgetService.java revision a71e0a520493532dbb7e9bfa164ab78e59e797a3
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 194143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.R; 204143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.Utils; 214143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.DayInfo; 224143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo; 234143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.RowInfo; 2421a183875fbbfa54f5a2a87779888a5fb7d1af44Erikimport com.google.common.annotations.VisibleForTesting; 254143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa 26bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.AlarmManager; 27bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.PendingIntent; 28bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.appwidget.AppWidgetManager; 29bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.ContentResolver; 30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context; 31bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent; 32bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor; 3347d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.MatrixCursor; 34bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri; 35bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Attendees; 3621a183875fbbfa54f5a2a87779888a5fb7d1af44Erikimport android.provider.Calendar.CalendarCache; 37bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Calendars; 38bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Instances; 3921a183875fbbfa54f5a2a87779888a5fb7d1af44Erikimport android.text.TextUtils; 40bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils; 41bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time; 42bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log; 43bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View; 44bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews; 4547d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.widget.RemoteViewsService; 4647d40324272ae39af0872bf5cbf27e1800478021Mason Tang 47bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 4847d40324272ae39af0872bf5cbf27e1800478021Mason Tangpublic class CalendarAppWidgetService extends RemoteViewsService { 49bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String TAG = "CalendarAppWidgetService"; 50bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int EVENT_MIN_COUNT = 20; 523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int EVENT_MAX_COUNT = 503; 5447d40324272ae39af0872bf5cbf27e1800478021Mason Tang 55bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, " 56bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, " 573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT; 58bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 59bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // TODO can't use parameter here because provider is dropping them 60bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SELECTION = Calendars.SELECTED + "=1 AND " 61bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED; 62bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 63bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final String[] EVENT_PROJECTION = new String[] { 64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.ALL_DAY, 65bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.BEGIN, 66bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.END, 67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.TITLE, 68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_LOCATION, 69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_ID, 703ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang Instances.START_DAY, 71a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa Instances.END_DAY, 72a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa Instances.COLOR 73bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang }; 74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 75bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_ALL_DAY = 0; 76bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_BEGIN = 1; 77bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_END = 2; 78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_TITLE = 3; 79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_LOCATION = 4; 80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_ID = 5; 813ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int INDEX_START_DAY = 6; 823ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int INDEX_END_DAY = 7; 83a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa static final int INDEX_COLOR = 8; 84bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 853ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int MAX_DAYS = 7; 863ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 873ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS; 88bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 89bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // If no next-update calculated, or bad trigger time in past, schedule 90bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // update about six hours from now. 91bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final long UPDATE_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6; 92bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 9347d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 9447d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViewsFactory onGetViewFactory(Intent intent) { 9547d40324272ae39af0872bf5cbf27e1800478021Mason Tang return new CalendarFactory(getApplicationContext(), intent); 96bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 97bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 9847d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected static class CalendarFactory implements RemoteViewsService.RemoteViewsFactory { 9947d40324272ae39af0872bf5cbf27e1800478021Mason Tang private static final String TAG = CalendarFactory.class.getSimpleName(); 100f9df037f350fad73659307ba05f230d2db69051aMason Tang private static final boolean LOGD = false; 10147d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Context mContext; 10247d40324272ae39af0872bf5cbf27e1800478021Mason Tang private CalendarAppWidgetModel mModel; 10347d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Cursor mCursor; 10447d40324272ae39af0872bf5cbf27e1800478021Mason Tang 10547d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected CalendarFactory(Context context, Intent intent) { 10647d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext = context; 107bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 108bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 10947d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 11047d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onCreate() { 11147d40324272ae39af0872bf5cbf27e1800478021Mason Tang loadData(); 112bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 113bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 11447d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 115d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung public void onDataSetChanged() { 1160054262c76ccc4b20dc6596e161df8a99d0c81c7Mason Tang loadData(); 117d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung } 118d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung 119d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung @Override 12047d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onDestroy() { 12147d40324272ae39af0872bf5cbf27e1800478021Mason Tang mCursor.close(); 122bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 123bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 12447d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 12547d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViews getLoadingView() { 12647d40324272ae39af0872bf5cbf27e1800478021Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 12747d40324272ae39af0872bf5cbf27e1800478021Mason Tang R.layout.appwidget_loading); 12847d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 129bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 130bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 13147d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 13247d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViews getViewAt(int position) { 13347d40324272ae39af0872bf5cbf27e1800478021Mason Tang // we use getCount here so that it doesn't return null when empty 13447d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (position < 0 || position >= getCount()) { 13547d40324272ae39af0872bf5cbf27e1800478021Mason Tang return null; 13647d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 137bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 1383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) { 1393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 1403ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang R.layout.appwidget_no_events); 1414143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(0); 1424143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa views.setOnClickFillInIntent(R.id.appwidget_no_events, intent); 1433ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return views; 1443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 1453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 1463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RowInfo rowInfo = mModel.mRowInfos.get(position); 1473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (rowInfo.mType == RowInfo.TYPE_DAY) { 1483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 1493ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang R.layout.appwidget_day); 1503ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex); 1513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel); 1523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return views; 1533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } else { 15447d40324272ae39af0872bf5cbf27e1800478021Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 15547d40324272ae39af0872bf5cbf27e1800478021Mason Tang R.layout.appwidget_row); 156bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 157a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa final EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex); 158a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa 159a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa updateTextView(views, R.id.when, eventInfo.visibWhen, eventInfo.when); 160a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa updateTextView(views, R.id.where, eventInfo.visibWhere, eventInfo.where); 161a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa updateTextView(views, R.id.title, eventInfo.visibTitle, eventInfo.title); 162bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 163a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa views.setViewVisibility(R.id.color, View.VISIBLE); 164a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa views.setInt(R.id.color, "setBackgroundColor", eventInfo.color); 165bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 1664143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa // An element in ListView. 1674143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa final Intent fillInIntent = 168a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa CalendarAppWidgetProvider.getLaunchFillInIntent(eventInfo.start); 1694143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa views.setOnClickFillInIntent(R.id.appwidget_row, fillInIntent); 17047d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 17147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 172bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 173bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 17447d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 17547d40324272ae39af0872bf5cbf27e1800478021Mason Tang public int getViewTypeCount() { 1763ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return 4; 17747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 178bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 17947d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 18047d40324272ae39af0872bf5cbf27e1800478021Mason Tang public int getCount() { 18147d40324272ae39af0872bf5cbf27e1800478021Mason Tang // if there are no events, we still return 1 to represent the "no 18247d40324272ae39af0872bf5cbf27e1800478021Mason Tang // events" view 1833ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return Math.max(1, mModel.mRowInfos.size()); 18447d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 185bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 18647d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 18747d40324272ae39af0872bf5cbf27e1800478021Mason Tang public long getItemId(int position) { 18847d40324272ae39af0872bf5cbf27e1800478021Mason Tang return position; 18947d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 190bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 19147d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 19247d40324272ae39af0872bf5cbf27e1800478021Mason Tang public boolean hasStableIds() { 19347d40324272ae39af0872bf5cbf27e1800478021Mason Tang return true; 194bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 195bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 19647d40324272ae39af0872bf5cbf27e1800478021Mason Tang private void loadData() { 197a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa final long now = System.currentTimeMillis(); 19847d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) Log.d(TAG, "Querying for widget events..."); 19947d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (mCursor != null) { 20047d40324272ae39af0872bf5cbf27e1800478021Mason Tang mCursor.close(); 20147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 202bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 203a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa final ContentResolver resolver = mContext.getContentResolver(); 204a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa mCursor = getUpcomingInstancesCursor(resolver, SEARCH_DURATION, now); 205a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa String tz = getTimeZoneFromDB(resolver); 20621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik mModel = buildAppWidgetModel(mContext, mCursor, tz); 2073ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang long triggerTime = calculateUpdateTime(mModel); 20847d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Schedule an alarm to wake ourselves up for the next update. We also cancel 20947d40324272ae39af0872bf5cbf27e1800478021Mason Tang // all existing wake-ups because PendingIntents don't match against extras. 21047d40324272ae39af0872bf5cbf27e1800478021Mason Tang 21147d40324272ae39af0872bf5cbf27e1800478021Mason Tang // If no next-update calculated, or bad trigger time in past, schedule 21247d40324272ae39af0872bf5cbf27e1800478021Mason Tang // update about six hours from now. 21347d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (triggerTime == -1 || triggerTime < now) { 21447d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) Log.w(TAG, "Encountered bad trigger time " + 21547d40324272ae39af0872bf5cbf27e1800478021Mason Tang formatDebugTime(triggerTime, now)); 21647d40324272ae39af0872bf5cbf27e1800478021Mason Tang triggerTime = now + UPDATE_NO_EVENTS; 21747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 218bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 21947d40324272ae39af0872bf5cbf27e1800478021Mason Tang AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 22047d40324272ae39af0872bf5cbf27e1800478021Mason Tang PendingIntent pendingUpdate = CalendarAppWidgetProvider.getUpdateIntent(mContext); 221bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 22247d40324272ae39af0872bf5cbf27e1800478021Mason Tang am.cancel(pendingUpdate); 22347d40324272ae39af0872bf5cbf27e1800478021Mason Tang am.set(AlarmManager.RTC, triggerTime, pendingUpdate); 22447d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) Log.d(TAG, "Scheduled next update at " + formatDebugTime(triggerTime, now)); 22547d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 226bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 22747d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 22847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Query across all calendars for upcoming event instances from now until 22947d40324272ae39af0872bf5cbf27e1800478021Mason Tang * some time in the future. 23047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 23147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Widen the time range that we query by one day on each end so that we can 23247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * catch all-day events. All-day events are stored starting at midnight in 23347d40324272ae39af0872bf5cbf27e1800478021Mason Tang * UTC but should be included in the list of events starting at midnight 23447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * local time. This may fetch more events than we actually want, so we 23547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * filter them out later. 23647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 23747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param resolver {@link ContentResolver} to use when querying 23847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * {@link Instances#CONTENT_URI}. 23947d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param searchDuration Distance into the future to look for event 24047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * instances, in milliseconds. 24147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param now Current system time to use for this update, possibly from 24247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * {@link System#currentTimeMillis()}. 24347d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 24447d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Cursor getUpcomingInstancesCursor(ContentResolver resolver, 24547d40324272ae39af0872bf5cbf27e1800478021Mason Tang long searchDuration, long now) { 24647d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Search for events from now until some time in the future 247bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 24847d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Add a day on either side to catch all-day events 24947d40324272ae39af0872bf5cbf27e1800478021Mason Tang long begin = now - DateUtils.DAY_IN_MILLIS; 25047d40324272ae39af0872bf5cbf27e1800478021Mason Tang long end = now + searchDuration + DateUtils.DAY_IN_MILLIS; 251bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 25247d40324272ae39af0872bf5cbf27e1800478021Mason Tang Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, 25347d40324272ae39af0872bf5cbf27e1800478021Mason Tang String.format("%d/%d", begin, end)); 254bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 25547d40324272ae39af0872bf5cbf27e1800478021Mason Tang Cursor cursor = resolver.query(uri, EVENT_PROJECTION, 25647d40324272ae39af0872bf5cbf27e1800478021Mason Tang EVENT_SELECTION, null, EVENT_SORT_ORDER); 257bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 25847d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Start managing the cursor ourselves 25947d40324272ae39af0872bf5cbf27e1800478021Mason Tang MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor); 26047d40324272ae39af0872bf5cbf27e1800478021Mason Tang cursor.close(); 261e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang 26247d40324272ae39af0872bf5cbf27e1800478021Mason Tang return matrixCursor; 263bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 264bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 26521a183875fbbfa54f5a2a87779888a5fb7d1af44Erik private String getTimeZoneFromDB(ContentResolver resolver) { 26621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik String tz = null; 26721a183875fbbfa54f5a2a87779888a5fb7d1af44Erik Cursor tzCursor = null; 26821a183875fbbfa54f5a2a87779888a5fb7d1af44Erik try { 26921a183875fbbfa54f5a2a87779888a5fb7d1af44Erik tzCursor = resolver.query( 27021a183875fbbfa54f5a2a87779888a5fb7d1af44Erik CalendarCache.URI, CalendarCache.POJECTION, null, null, null); 27121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik if (tzCursor != null) { 27221a183875fbbfa54f5a2a87779888a5fb7d1af44Erik int keyColumn = tzCursor.getColumnIndexOrThrow(CalendarCache.KEY); 27321a183875fbbfa54f5a2a87779888a5fb7d1af44Erik int valueColumn = tzCursor.getColumnIndexOrThrow(CalendarCache.VALUE); 27421a183875fbbfa54f5a2a87779888a5fb7d1af44Erik while (tzCursor.moveToNext()) { 27521a183875fbbfa54f5a2a87779888a5fb7d1af44Erik if (TextUtils.equals(tzCursor.getString(keyColumn), 27621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik CalendarCache.TIMEZONE_KEY_INSTANCES)) { 27721a183875fbbfa54f5a2a87779888a5fb7d1af44Erik tz = tzCursor.getString(valueColumn); 27821a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 27921a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 28021a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 28121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik if (tz == null) { 28221a183875fbbfa54f5a2a87779888a5fb7d1af44Erik tz = Time.getCurrentTimezone(); 28321a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 28421a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } finally { 28521a183875fbbfa54f5a2a87779888a5fb7d1af44Erik if (tzCursor != null) { 28621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik tzCursor.close(); 28721a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 28821a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 28921a183875fbbfa54f5a2a87779888a5fb7d1af44Erik return tz; 29021a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 29121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik 29247d40324272ae39af0872bf5cbf27e1800478021Mason Tang @VisibleForTesting 29347d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected static CalendarAppWidgetModel buildAppWidgetModel( 29421a183875fbbfa54f5a2a87779888a5fb7d1af44Erik Context context, Cursor cursor, String timeZone) { 2953ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang CalendarAppWidgetModel model = new CalendarAppWidgetModel(context); 29621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik model.buildFromCursor(cursor, timeZone); 29747d40324272ae39af0872bf5cbf27e1800478021Mason Tang return model; 298bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 299bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 30047d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 30147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Figure out the next time we should push widget updates, usually the time 30247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * calculated by {@link #getEventFlip(Cursor, long, long, boolean)}. 30347d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 30447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param cursor Valid cursor on {@link Instances#CONTENT_URI} 30547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param events {@link MarkedEvents} parsed from the cursor 30647d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 3073ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang private long calculateUpdateTime(CalendarAppWidgetModel model) { 30847d40324272ae39af0872bf5cbf27e1800478021Mason Tang long result = -1; 3093ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (!model.mEventInfos.isEmpty()) { 3103ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang EventInfo firstEvent = model.mEventInfos.get(0); 3113ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang long start = firstEvent.start; 3123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang long end = firstEvent.end; 3133ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang boolean allDay = firstEvent.allDay; 31447d40324272ae39af0872bf5cbf27e1800478021Mason Tang 31547d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Adjust all-day times into local timezone 31647d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (allDay) { 31747d40324272ae39af0872bf5cbf27e1800478021Mason Tang final Time recycle = new Time(); 3183ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang start = Utils.convertUtcToLocal(recycle, start); 3193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang end = Utils.convertUtcToLocal(recycle, end); 320bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 321bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 3223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang result = getEventFlip(start, end, allDay); 323bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 32447d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Make sure an update happens at midnight or earlier 32547d40324272ae39af0872bf5cbf27e1800478021Mason Tang long midnight = getNextMidnightTimeMillis(); 32647d40324272ae39af0872bf5cbf27e1800478021Mason Tang result = Math.min(midnight, result); 327bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 32847d40324272ae39af0872bf5cbf27e1800478021Mason Tang return result; 329bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 330bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 33147d40324272ae39af0872bf5cbf27e1800478021Mason Tang private static long getNextMidnightTimeMillis() { 33247d40324272ae39af0872bf5cbf27e1800478021Mason Tang Time time = new Time(); 33347d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.setToNow(); 33447d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.monthDay++; 33547d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.hour = 0; 33647d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.minute = 0; 33747d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.second = 0; 33847d40324272ae39af0872bf5cbf27e1800478021Mason Tang long midnight = time.normalize(true); 33947d40324272ae39af0872bf5cbf27e1800478021Mason Tang return midnight; 340bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 341bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 34247d40324272ae39af0872bf5cbf27e1800478021Mason Tang static void updateTextView(RemoteViews views, int id, int visibility, String string) { 34347d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setViewVisibility(id, visibility); 34447d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (visibility == View.VISIBLE) { 34547d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setTextViewText(id, string); 34647d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 34747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 3483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 349bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 3503ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang /** 3513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * Format given time for debugging output. 3523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * 3533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param unixTime Target time to report. 3543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param now Current system time from {@link System#currentTimeMillis()} 3553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * for calculating time difference. 3563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang */ 3573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static String formatDebugTime(long unixTime, long now) { 3583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang Time time = new Time(); 3593ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.set(unixTime); 3603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 3613ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang long delta = unixTime - now; 3623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (delta > DateUtils.MINUTE_IN_MILLIS) { 3633ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang delta /= DateUtils.MINUTE_IN_MILLIS; 3643ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return String.format("[%d] %s (%+d mins)", unixTime, 3653ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.format("%H:%M:%S"), delta); 3663ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } else { 3673ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang delta /= DateUtils.SECOND_IN_MILLIS; 3683ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return String.format("[%d] %s (%+d secs)", unixTime, 3693ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.format("%H:%M:%S"), delta); 370bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 371bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 3723ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 3733ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang /** 3743ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * Calculate flipping point for the given event; when we should hide this 3753ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * event and show the next one. This is defined as the end time of the 3763ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * event. 3773ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * 3783ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param start Event start time in local timezone. 3793ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param end Event end time in local timezone. 3803ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param allDay whether or not the event is all-day 3813ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang */ 3823ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static long getEventFlip(long start, long end, boolean allDay) { 3833ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return end; 3843ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 385bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang} 386