CalendarAppWidgetService.java revision d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5
1bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang/* 2bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Copyright (C) 2009 The Android Open Source Project 3bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 4bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Licensed under the Apache License, Version 2.0 (the "License"); 5bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * you may not use this file except in compliance with the License. 6bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * You may obtain a copy of the License at 7bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 8bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * http://www.apache.org/licenses/LICENSE-2.0 9bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 10bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Unless required by applicable law or agreed to in writing, software 11bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * distributed under the License is distributed on an "AS IS" BASIS, 12bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * See the License for the specific language governing permissions and 14bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * limitations under the License. 15bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 16bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 179a3cb14e28536e4133dddbe952f47189fe344ec1Mason Tangpackage com.android.calendar.widget; 18bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 19bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.AlarmManager; 20bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.PendingIntent; 21bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.appwidget.AppWidgetManager; 2247d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.content.BroadcastReceiver; 23bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.ContentResolver; 24bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context; 25bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent; 2647d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.content.IntentFilter; 2747d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.ContentObserver; 28bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor; 2947d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.MatrixCursor; 30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri; 3147d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.os.Handler; 3247d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.provider.Calendar; 33bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Attendees; 34bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Calendars; 3547d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.provider.Calendar.Events; 36bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Instances; 37bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.TextUtils; 38bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateFormat; 39bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils; 40bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time; 41bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log; 42bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View; 43bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews; 4447d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.widget.RemoteViewsService; 4547d40324272ae39af0872bf5cbf27e1800478021Mason Tang 4647d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.google.common.annotations.VisibleForTesting; 4747d40324272ae39af0872bf5cbf27e1800478021Mason Tang 4847d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.calendar.R; 4947d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.calendar.Utils; 5047d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo; 51bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 52bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport java.util.ArrayList; 53bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport java.util.List; 54bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport java.util.TimeZone; 55bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 56bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 5747d40324272ae39af0872bf5cbf27e1800478021Mason Tangpublic class CalendarAppWidgetService extends RemoteViewsService { 58bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String TAG = "CalendarAppWidgetService"; 59bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final boolean LOGD = false; 60bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 6147d40324272ae39af0872bf5cbf27e1800478021Mason Tang private static final int EVENT_MAX_COUNT = 10; 6247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 63bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, " 64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, " 6547d40324272ae39af0872bf5cbf27e1800478021Mason Tang + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT; 66bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // TODO can't use parameter here because provider is dropping them 68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SELECTION = Calendars.SELECTED + "=1 AND " 69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED; 70bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 71bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final String[] EVENT_PROJECTION = new String[] { 72bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.ALL_DAY, 73bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.BEGIN, 74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.END, 75bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.TITLE, 76bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_LOCATION, 77bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_ID, 78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang }; 79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_ALL_DAY = 0; 81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_BEGIN = 1; 82bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_END = 2; 83bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_TITLE = 3; 84bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_LOCATION = 4; 85bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_ID = 5; 86bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 87bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final long SEARCH_DURATION = DateUtils.WEEK_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 MarkedEvents { 9947d40324272ae39af0872bf5cbf27e1800478021Mason Tang 10047d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 10147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * The row IDs of all events marked for display 10247d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 10347d40324272ae39af0872bf5cbf27e1800478021Mason Tang List<Integer> markedIds = new ArrayList<Integer>(10); 10447d40324272ae39af0872bf5cbf27e1800478021Mason Tang 10547d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 10647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * The start time of the first marked event 10747d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 10847d40324272ae39af0872bf5cbf27e1800478021Mason Tang long firstTime = -1; 10947d40324272ae39af0872bf5cbf27e1800478021Mason Tang 11047d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** The number of events currently in progress */ 11147d40324272ae39af0872bf5cbf27e1800478021Mason Tang int inProgressCount = 0; // Number of events with same start time as the primary evt. 11247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 11347d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** The start time of the next upcoming event */ 11447d40324272ae39af0872bf5cbf27e1800478021Mason Tang long primaryTime = -1; 115bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 11647d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 11747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * The number of events that share the same start time as the next 11847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * upcoming event 11947d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 12047d40324272ae39af0872bf5cbf27e1800478021Mason Tang int primaryCount = 0; // Number of events with same start time as the secondary evt. 12147d40324272ae39af0872bf5cbf27e1800478021Mason Tang 12247d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** The start time of the next next upcoming event */ 12347d40324272ae39af0872bf5cbf27e1800478021Mason Tang long secondaryTime = 1; 12447d40324272ae39af0872bf5cbf27e1800478021Mason Tang 12547d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 12647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * The number of events that share the same start time as the next next 12747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * upcoming event. 12847d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 12947d40324272ae39af0872bf5cbf27e1800478021Mason Tang int secondaryCount = 0; 130bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 131bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 13247d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected static class CalendarFactory implements RemoteViewsService.RemoteViewsFactory { 13347d40324272ae39af0872bf5cbf27e1800478021Mason Tang 13447d40324272ae39af0872bf5cbf27e1800478021Mason Tang private static final String TAG = CalendarFactory.class.getSimpleName(); 135bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 136f9df037f350fad73659307ba05f230d2db69051aMason Tang private static final boolean LOGD = false; 13747d40324272ae39af0872bf5cbf27e1800478021Mason Tang 13847d40324272ae39af0872bf5cbf27e1800478021Mason Tang private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 13947d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 14047d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onReceive(Context context, Intent intent) { 14147d40324272ae39af0872bf5cbf27e1800478021Mason Tang String action = intent.getAction(); 142f9df037f350fad73659307ba05f230d2db69051aMason Tang if (action.equals(CalendarAppWidgetProvider.ACTION_CALENDAR_APPWIDGET_UPDATE) 143f9df037f350fad73659307ba05f230d2db69051aMason Tang || action.equals(Intent.ACTION_TIMEZONE_CHANGED) 14447d40324272ae39af0872bf5cbf27e1800478021Mason Tang || action.equals(Intent.ACTION_TIME_CHANGED) 14547d40324272ae39af0872bf5cbf27e1800478021Mason Tang || action.equals(Intent.ACTION_DATE_CHANGED) 14647d40324272ae39af0872bf5cbf27e1800478021Mason Tang || (action.equals(Intent.ACTION_PROVIDER_CHANGED) 14747d40324272ae39af0872bf5cbf27e1800478021Mason Tang && intent.getData().equals(Calendar.CONTENT_URI))) { 14847d40324272ae39af0872bf5cbf27e1800478021Mason Tang loadData(); 149bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 150bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 15147d40324272ae39af0872bf5cbf27e1800478021Mason Tang }; 15247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 15347d40324272ae39af0872bf5cbf27e1800478021Mason Tang private final ContentObserver mContentObserver = new ContentObserver(new Handler()) { 15447d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 15547d40324272ae39af0872bf5cbf27e1800478021Mason Tang public boolean deliverSelfNotifications() { 15647d40324272ae39af0872bf5cbf27e1800478021Mason Tang return true; 15747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 15847d40324272ae39af0872bf5cbf27e1800478021Mason Tang 15947d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 16047d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onChange(boolean selfChange) { 16147d40324272ae39af0872bf5cbf27e1800478021Mason Tang loadData(); 162bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 16347d40324272ae39af0872bf5cbf27e1800478021Mason Tang }; 16447d40324272ae39af0872bf5cbf27e1800478021Mason Tang 16547d40324272ae39af0872bf5cbf27e1800478021Mason Tang private final int mAppWidgetId; 16647d40324272ae39af0872bf5cbf27e1800478021Mason Tang 16747d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Context mContext; 16847d40324272ae39af0872bf5cbf27e1800478021Mason Tang 16947d40324272ae39af0872bf5cbf27e1800478021Mason Tang private CalendarAppWidgetModel mModel; 17047d40324272ae39af0872bf5cbf27e1800478021Mason Tang 17147d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Cursor mCursor; 17247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 17347d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected CalendarFactory(Context context, Intent intent) { 17447d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext = context; 17547d40324272ae39af0872bf5cbf27e1800478021Mason Tang mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 17647d40324272ae39af0872bf5cbf27e1800478021Mason Tang AppWidgetManager.INVALID_APPWIDGET_ID); 177bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 178bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 17947d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 18047d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onCreate() { 18147d40324272ae39af0872bf5cbf27e1800478021Mason Tang loadData(); 18247d40324272ae39af0872bf5cbf27e1800478021Mason Tang IntentFilter filter = new IntentFilter(); 183f9df037f350fad73659307ba05f230d2db69051aMason Tang filter.addAction(CalendarAppWidgetProvider.ACTION_CALENDAR_APPWIDGET_UPDATE); 18447d40324272ae39af0872bf5cbf27e1800478021Mason Tang filter.addAction(Intent.ACTION_TIME_CHANGED); 18547d40324272ae39af0872bf5cbf27e1800478021Mason Tang filter.addAction(Intent.ACTION_DATE_CHANGED); 18647d40324272ae39af0872bf5cbf27e1800478021Mason Tang filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 18747d40324272ae39af0872bf5cbf27e1800478021Mason Tang filter.addAction(Intent.ACTION_PROVIDER_CHANGED); 18847d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext.registerReceiver(mIntentReceiver, filter); 18947d40324272ae39af0872bf5cbf27e1800478021Mason Tang 19047d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext.getContentResolver().registerContentObserver( 19147d40324272ae39af0872bf5cbf27e1800478021Mason Tang Events.CONTENT_URI, true, mContentObserver); 192bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 193bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 19447d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 195d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung public void onDataSetChanged() { 196d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung } 197d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung 198d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung @Override 19947d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onDestroy() { 20047d40324272ae39af0872bf5cbf27e1800478021Mason Tang mCursor.close(); 20147d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext.unregisterReceiver(mIntentReceiver); 20247d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext.getContentResolver().unregisterContentObserver(mContentObserver); 203bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 204bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 205bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 20647d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 20747d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViews getLoadingView() { 20847d40324272ae39af0872bf5cbf27e1800478021Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 20947d40324272ae39af0872bf5cbf27e1800478021Mason Tang R.layout.appwidget_loading); 21047d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 211bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 212bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 21347d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 21447d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViews getViewAt(int position) { 21547d40324272ae39af0872bf5cbf27e1800478021Mason Tang // we use getCount here so that it doesn't return null when empty 21647d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (position < 0 || position >= getCount()) { 21747d40324272ae39af0872bf5cbf27e1800478021Mason Tang return null; 21847d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 219bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 22047d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (mModel.eventInfos.length > 0) { 22147d40324272ae39af0872bf5cbf27e1800478021Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 22247d40324272ae39af0872bf5cbf27e1800478021Mason Tang R.layout.appwidget_row); 223bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 22447d40324272ae39af0872bf5cbf27e1800478021Mason Tang EventInfo e = mModel.eventInfos[position]; 225bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 22647d40324272ae39af0872bf5cbf27e1800478021Mason Tang updateTextView(views, R.id.when, e.visibWhen, e.when); 22747d40324272ae39af0872bf5cbf27e1800478021Mason Tang updateTextView(views, R.id.where, e.visibWhere, e.where); 22847d40324272ae39af0872bf5cbf27e1800478021Mason Tang updateTextView(views, R.id.title, e.visibTitle, e.title); 229bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 23047d40324272ae39af0872bf5cbf27e1800478021Mason Tang PendingIntent launchIntent = 23147d40324272ae39af0872bf5cbf27e1800478021Mason Tang CalendarAppWidgetProvider.getLaunchPendingIntent( 23247d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext, e.start); 23347d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setOnClickPendingIntent(R.id.appwidget_row, launchIntent); 23447d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 23547d40324272ae39af0872bf5cbf27e1800478021Mason Tang } else { 23647d40324272ae39af0872bf5cbf27e1800478021Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 23747d40324272ae39af0872bf5cbf27e1800478021Mason Tang R.layout.appwidget_no_events); 23847d40324272ae39af0872bf5cbf27e1800478021Mason Tang PendingIntent launchIntent = 23947d40324272ae39af0872bf5cbf27e1800478021Mason Tang CalendarAppWidgetProvider.getLaunchPendingIntent( 24047d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext, 0); 24147d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setOnClickPendingIntent(R.id.appwidget_no_events, launchIntent); 24247d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 24347d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 244bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 245bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 24647d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 24747d40324272ae39af0872bf5cbf27e1800478021Mason Tang public int getViewTypeCount() { 24847d40324272ae39af0872bf5cbf27e1800478021Mason Tang return 3; 24947d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 250bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 25147d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 25247d40324272ae39af0872bf5cbf27e1800478021Mason Tang public int getCount() { 25347d40324272ae39af0872bf5cbf27e1800478021Mason Tang // if there are no events, we still return 1 to represent the "no 25447d40324272ae39af0872bf5cbf27e1800478021Mason Tang // events" view 25547d40324272ae39af0872bf5cbf27e1800478021Mason Tang return Math.max(1, mModel.eventInfos.length); 25647d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 257bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 25847d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 25947d40324272ae39af0872bf5cbf27e1800478021Mason Tang public long getItemId(int position) { 26047d40324272ae39af0872bf5cbf27e1800478021Mason Tang return position; 26147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 262bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 26347d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 26447d40324272ae39af0872bf5cbf27e1800478021Mason Tang public boolean hasStableIds() { 26547d40324272ae39af0872bf5cbf27e1800478021Mason Tang return true; 266bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 267bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 26847d40324272ae39af0872bf5cbf27e1800478021Mason Tang private void loadData() { 26947d40324272ae39af0872bf5cbf27e1800478021Mason Tang long now = System.currentTimeMillis(); 27047d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) Log.d(TAG, "Querying for widget events..."); 27147d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (mCursor != null) { 27247d40324272ae39af0872bf5cbf27e1800478021Mason Tang mCursor.close(); 27347d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 274bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 27547d40324272ae39af0872bf5cbf27e1800478021Mason Tang mCursor = getUpcomingInstancesCursor( 27647d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext.getContentResolver(), SEARCH_DURATION, now); 27747d40324272ae39af0872bf5cbf27e1800478021Mason Tang MarkedEvents markedEvents = buildMarkedEvents(mCursor, now); 27847d40324272ae39af0872bf5cbf27e1800478021Mason Tang mModel = buildAppWidgetModel(mContext, mCursor, markedEvents, now); 27947d40324272ae39af0872bf5cbf27e1800478021Mason Tang long triggerTime = calculateUpdateTime(mCursor, markedEvents); 28047d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Schedule an alarm to wake ourselves up for the next update. We also cancel 28147d40324272ae39af0872bf5cbf27e1800478021Mason Tang // all existing wake-ups because PendingIntents don't match against extras. 28247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 28347d40324272ae39af0872bf5cbf27e1800478021Mason Tang // If no next-update calculated, or bad trigger time in past, schedule 28447d40324272ae39af0872bf5cbf27e1800478021Mason Tang // update about six hours from now. 28547d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (triggerTime == -1 || triggerTime < now) { 28647d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) Log.w(TAG, "Encountered bad trigger time " + 28747d40324272ae39af0872bf5cbf27e1800478021Mason Tang formatDebugTime(triggerTime, now)); 28847d40324272ae39af0872bf5cbf27e1800478021Mason Tang triggerTime = now + UPDATE_NO_EVENTS; 28947d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 290bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 29147d40324272ae39af0872bf5cbf27e1800478021Mason Tang AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 29247d40324272ae39af0872bf5cbf27e1800478021Mason Tang PendingIntent pendingUpdate = CalendarAppWidgetProvider.getUpdateIntent(mContext); 293bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 29447d40324272ae39af0872bf5cbf27e1800478021Mason Tang am.cancel(pendingUpdate); 29547d40324272ae39af0872bf5cbf27e1800478021Mason Tang am.set(AlarmManager.RTC, triggerTime, pendingUpdate); 29647d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) Log.d(TAG, "Scheduled next update at " + formatDebugTime(triggerTime, now)); 29747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 298bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 29947d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 30047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Query across all calendars for upcoming event instances from now until 30147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * some time in the future. 30247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 30347d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Widen the time range that we query by one day on each end so that we can 30447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * catch all-day events. All-day events are stored starting at midnight in 30547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * UTC but should be included in the list of events starting at midnight 30647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * local time. This may fetch more events than we actually want, so we 30747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * filter them out later. 30847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 30947d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param resolver {@link ContentResolver} to use when querying 31047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * {@link Instances#CONTENT_URI}. 31147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param searchDuration Distance into the future to look for event 31247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * instances, in milliseconds. 31347d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param now Current system time to use for this update, possibly from 31447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * {@link System#currentTimeMillis()}. 31547d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 31647d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Cursor getUpcomingInstancesCursor(ContentResolver resolver, 31747d40324272ae39af0872bf5cbf27e1800478021Mason Tang long searchDuration, long now) { 31847d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Search for events from now until some time in the future 319bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 32047d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Add a day on either side to catch all-day events 32147d40324272ae39af0872bf5cbf27e1800478021Mason Tang long begin = now - DateUtils.DAY_IN_MILLIS; 32247d40324272ae39af0872bf5cbf27e1800478021Mason Tang long end = now + searchDuration + DateUtils.DAY_IN_MILLIS; 323bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 32447d40324272ae39af0872bf5cbf27e1800478021Mason Tang Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, 32547d40324272ae39af0872bf5cbf27e1800478021Mason Tang String.format("%d/%d", begin, end)); 326bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 32747d40324272ae39af0872bf5cbf27e1800478021Mason Tang Cursor cursor = resolver.query(uri, EVENT_PROJECTION, 32847d40324272ae39af0872bf5cbf27e1800478021Mason Tang EVENT_SELECTION, null, EVENT_SORT_ORDER); 329bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 33047d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Start managing the cursor ourselves 33147d40324272ae39af0872bf5cbf27e1800478021Mason Tang MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor); 33247d40324272ae39af0872bf5cbf27e1800478021Mason Tang cursor.close(); 333e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang 33447d40324272ae39af0872bf5cbf27e1800478021Mason Tang return matrixCursor; 335bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 336bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 33747d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 33847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Walk the given instances cursor and build a list of marked events to be 33947d40324272ae39af0872bf5cbf27e1800478021Mason Tang * used when updating the widget. This structure is also used to check if 34047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * updates are needed. 34147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 34247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param cursor Valid cursor across {@link Instances#CONTENT_URI}. 34347d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param watchEventIds Specific events to watch for, setting 34447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * {@link MarkedEvents#watchFound} if found during marking. 34547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param now Current system time to use for this update, possibly from 34647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * {@link System#currentTimeMillis()} 34747d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 34847d40324272ae39af0872bf5cbf27e1800478021Mason Tang @VisibleForTesting 34947d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected static MarkedEvents buildMarkedEvents(Cursor cursor, long now) { 35047d40324272ae39af0872bf5cbf27e1800478021Mason Tang MarkedEvents events = new MarkedEvents(); 35147d40324272ae39af0872bf5cbf27e1800478021Mason Tang final Time recycle = new Time(); 35247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 35347d40324272ae39af0872bf5cbf27e1800478021Mason Tang cursor.moveToPosition(-1); 35447d40324272ae39af0872bf5cbf27e1800478021Mason Tang while (cursor.moveToNext()) { 35547d40324272ae39af0872bf5cbf27e1800478021Mason Tang int row = cursor.getPosition(); 35647d40324272ae39af0872bf5cbf27e1800478021Mason Tang long eventId = cursor.getLong(INDEX_EVENT_ID); 35747d40324272ae39af0872bf5cbf27e1800478021Mason Tang long start = cursor.getLong(INDEX_BEGIN); 35847d40324272ae39af0872bf5cbf27e1800478021Mason Tang long end = cursor.getLong(INDEX_END); 35947d40324272ae39af0872bf5cbf27e1800478021Mason Tang 36047d40324272ae39af0872bf5cbf27e1800478021Mason Tang boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0; 36147d40324272ae39af0872bf5cbf27e1800478021Mason Tang 36247d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) { 36347d40324272ae39af0872bf5cbf27e1800478021Mason Tang Log.d(TAG, "Row #" + row + " allDay:" + allDay + " start:" + start 36447d40324272ae39af0872bf5cbf27e1800478021Mason Tang + " end:" + end + " eventId:" + eventId); 36547d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 366bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 36747d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Adjust all-day times into local timezone 36847d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (allDay) { 36947d40324272ae39af0872bf5cbf27e1800478021Mason Tang start = convertUtcToLocal(recycle, start); 37047d40324272ae39af0872bf5cbf27e1800478021Mason Tang end = convertUtcToLocal(recycle, end); 37147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 372bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 37347d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (end < now) { 37447d40324272ae39af0872bf5cbf27e1800478021Mason Tang // we might get some extra events when querying, in order to 37547d40324272ae39af0872bf5cbf27e1800478021Mason Tang // deal with all-day events 37647d40324272ae39af0872bf5cbf27e1800478021Mason Tang continue; 37747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 378bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 37947d40324272ae39af0872bf5cbf27e1800478021Mason Tang boolean inProgress = now < end && now > start; 380bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 38147d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Skip events that have already passed their flip times 38247d40324272ae39af0872bf5cbf27e1800478021Mason Tang long eventFlip = getEventFlip(cursor, start, end, allDay); 38347d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) Log.d(TAG, "Calculated flip time " + formatDebugTime(eventFlip, now)); 38447d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (eventFlip < now) { 38547d40324272ae39af0872bf5cbf27e1800478021Mason Tang continue; 38647d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 38747d40324272ae39af0872bf5cbf27e1800478021Mason Tang 38847d40324272ae39af0872bf5cbf27e1800478021Mason Tang// /* Scan through the events with the following logic: 38947d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * Rule #1 Show A) all the events that are in progress including 39047d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * all day events and B) the next upcoming event and any events 39147d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * with the same start time. 39247d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * 39347d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * Rule #2 If there are no events in progress, show A) the next 39447d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * upcoming event and B) any events with the same start time. 39547d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * 39647d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * Rule #3 If no events start at the same time at A in rule 2, 39747d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * show A) the next upcoming event and B) the following upcoming 39847d40324272ae39af0872bf5cbf27e1800478021Mason Tang// * event + any events with the same start time. 39947d40324272ae39af0872bf5cbf27e1800478021Mason Tang// */ 40047d40324272ae39af0872bf5cbf27e1800478021Mason Tang// if (inProgress) { 40147d40324272ae39af0872bf5cbf27e1800478021Mason Tang// // events for part A of Rule #1 40247d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.markedIds.add(row); 40347d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.inProgressCount++; 40447d40324272ae39af0872bf5cbf27e1800478021Mason Tang// if (events.firstTime == -1) { 40547d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.firstTime = start; 40647d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } 40747d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } else { 40847d40324272ae39af0872bf5cbf27e1800478021Mason Tang// if (events.primaryCount == 0) { 40947d40324272ae39af0872bf5cbf27e1800478021Mason Tang// // first upcoming event 41047d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.markedIds.add(row); 41147d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.primaryTime = start; 41247d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.primaryCount++; 41347d40324272ae39af0872bf5cbf27e1800478021Mason Tang// if (events.firstTime == -1) { 41447d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.firstTime = start; 41547d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } 41647d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } else if (events.primaryTime == start) { 41747d40324272ae39af0872bf5cbf27e1800478021Mason Tang// // any events with same start time as first upcoming event 41847d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.markedIds.add(row); 41947d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.primaryCount++; 42047d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } else if (events.markedIds.size() == 1) { 42147d40324272ae39af0872bf5cbf27e1800478021Mason Tang// // only one upcoming event, so we take the next upcoming 42247d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.markedIds.add(row); 42347d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.secondaryTime = start; 42447d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.secondaryCount++; 42547d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } else if (events.secondaryCount > 0 42647d40324272ae39af0872bf5cbf27e1800478021Mason Tang// && events.secondaryTime == start) { 42747d40324272ae39af0872bf5cbf27e1800478021Mason Tang// // any events with same start time as next upcoming 42847d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.markedIds.add(row); 42947d40324272ae39af0872bf5cbf27e1800478021Mason Tang// events.secondaryCount++; 43047d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } else { 43147d40324272ae39af0872bf5cbf27e1800478021Mason Tang// // looks like we're done 43247d40324272ae39af0872bf5cbf27e1800478021Mason Tang// break; 43347d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } 43447d40324272ae39af0872bf5cbf27e1800478021Mason Tang// } 435bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 43647d40324272ae39af0872bf5cbf27e1800478021Mason Tang events.markedIds.add(row); 43747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 43847d40324272ae39af0872bf5cbf27e1800478021Mason Tang return events; 439bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 440bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 44147d40324272ae39af0872bf5cbf27e1800478021Mason Tang @VisibleForTesting 44247d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected static CalendarAppWidgetModel buildAppWidgetModel( 44347d40324272ae39af0872bf5cbf27e1800478021Mason Tang Context context, Cursor cursor, MarkedEvents events, long currentTime) { 44447d40324272ae39af0872bf5cbf27e1800478021Mason Tang int eventCount = events.markedIds.size(); 44547d40324272ae39af0872bf5cbf27e1800478021Mason Tang CalendarAppWidgetModel model = new CalendarAppWidgetModel(eventCount); 44647d40324272ae39af0872bf5cbf27e1800478021Mason Tang Time time = new Time(); 44747d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.set(currentTime); 44847d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.monthDay++; 44947d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.hour = 0; 45047d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.minute = 0; 45147d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.second = 0; 45247d40324272ae39af0872bf5cbf27e1800478021Mason Tang long startOfNextDay = time.normalize(true); 45347d40324272ae39af0872bf5cbf27e1800478021Mason Tang 45447d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.set(currentTime); 45547d40324272ae39af0872bf5cbf27e1800478021Mason Tang 45647d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Calendar header 45747d40324272ae39af0872bf5cbf27e1800478021Mason Tang String dayOfWeek = DateUtils.getDayOfWeekString( 45847d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.weekDay + 1, DateUtils.LENGTH_MEDIUM).toUpperCase(); 45947d40324272ae39af0872bf5cbf27e1800478021Mason Tang 46047d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.dayOfWeek = dayOfWeek; 46147d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.dayOfMonth = Integer.toString(time.monthDay); 46247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 46347d40324272ae39af0872bf5cbf27e1800478021Mason Tang int i = 0; 46447d40324272ae39af0872bf5cbf27e1800478021Mason Tang for (Integer id : events.markedIds) { 46547d40324272ae39af0872bf5cbf27e1800478021Mason Tang populateEvent(context, cursor, id, model, time, i, true, 46647d40324272ae39af0872bf5cbf27e1800478021Mason Tang startOfNextDay, currentTime); 46747d40324272ae39af0872bf5cbf27e1800478021Mason Tang i++; 46847d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 469bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 47047d40324272ae39af0872bf5cbf27e1800478021Mason Tang return model; 471bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 472bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 47347d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 47447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Figure out the next time we should push widget updates, usually the time 47547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * calculated by {@link #getEventFlip(Cursor, long, long, boolean)}. 47647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 47747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param cursor Valid cursor on {@link Instances#CONTENT_URI} 47847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param events {@link MarkedEvents} parsed from the cursor 47947d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 48047d40324272ae39af0872bf5cbf27e1800478021Mason Tang private long calculateUpdateTime(Cursor cursor, MarkedEvents events) { 48147d40324272ae39af0872bf5cbf27e1800478021Mason Tang long result = -1; 48247d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (!events.markedIds.isEmpty()) { 48347d40324272ae39af0872bf5cbf27e1800478021Mason Tang cursor.moveToPosition(events.markedIds.get(0)); 48447d40324272ae39af0872bf5cbf27e1800478021Mason Tang long start = cursor.getLong(INDEX_BEGIN); 48547d40324272ae39af0872bf5cbf27e1800478021Mason Tang long end = cursor.getLong(INDEX_END); 48647d40324272ae39af0872bf5cbf27e1800478021Mason Tang boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0; 48747d40324272ae39af0872bf5cbf27e1800478021Mason Tang 48847d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Adjust all-day times into local timezone 48947d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (allDay) { 49047d40324272ae39af0872bf5cbf27e1800478021Mason Tang final Time recycle = new Time(); 49147d40324272ae39af0872bf5cbf27e1800478021Mason Tang start = convertUtcToLocal(recycle, start); 49247d40324272ae39af0872bf5cbf27e1800478021Mason Tang end = convertUtcToLocal(recycle, end); 493bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 494bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 49547d40324272ae39af0872bf5cbf27e1800478021Mason Tang result = getEventFlip(cursor, start, end, allDay); 496bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 49747d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Make sure an update happens at midnight or earlier 49847d40324272ae39af0872bf5cbf27e1800478021Mason Tang long midnight = getNextMidnightTimeMillis(); 49947d40324272ae39af0872bf5cbf27e1800478021Mason Tang result = Math.min(midnight, result); 500bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 50147d40324272ae39af0872bf5cbf27e1800478021Mason Tang return result; 502bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 503bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 50447d40324272ae39af0872bf5cbf27e1800478021Mason Tang private static long getNextMidnightTimeMillis() { 50547d40324272ae39af0872bf5cbf27e1800478021Mason Tang Time time = new Time(); 50647d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.setToNow(); 50747d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.monthDay++; 50847d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.hour = 0; 50947d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.minute = 0; 51047d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.second = 0; 51147d40324272ae39af0872bf5cbf27e1800478021Mason Tang long midnight = time.normalize(true); 51247d40324272ae39af0872bf5cbf27e1800478021Mason Tang return midnight; 513bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 514bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 515bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 51647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Format given time for debugging output. 51747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 51847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param unixTime Target time to report. 51947d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param now Current system time from {@link System#currentTimeMillis()} 52047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * for calculating time difference. 521bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 52247d40324272ae39af0872bf5cbf27e1800478021Mason Tang static private String formatDebugTime(long unixTime, long now) { 52347d40324272ae39af0872bf5cbf27e1800478021Mason Tang Time time = new Time(); 52447d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.set(unixTime); 52547d40324272ae39af0872bf5cbf27e1800478021Mason Tang 52647d40324272ae39af0872bf5cbf27e1800478021Mason Tang long delta = unixTime - now; 52747d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (delta > DateUtils.MINUTE_IN_MILLIS) { 52847d40324272ae39af0872bf5cbf27e1800478021Mason Tang delta /= DateUtils.MINUTE_IN_MILLIS; 52947d40324272ae39af0872bf5cbf27e1800478021Mason Tang return String.format("[%d] %s (%+d mins)", unixTime, 53047d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.format("%H:%M:%S"), delta); 53147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } else { 53247d40324272ae39af0872bf5cbf27e1800478021Mason Tang delta /= DateUtils.SECOND_IN_MILLIS; 53347d40324272ae39af0872bf5cbf27e1800478021Mason Tang return String.format("[%d] %s (%+d secs)", unixTime, 53447d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.format("%H:%M:%S"), delta); 53547d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 53647d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 537bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 538bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 53947d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Convert given UTC time into current local time. 54047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 54147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param recycle Time object to recycle, otherwise null. 54247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param utcTime Time to convert, in UTC. 543bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 54447d40324272ae39af0872bf5cbf27e1800478021Mason Tang static private long convertUtcToLocal(Time recycle, long utcTime) { 54547d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (recycle == null) { 54647d40324272ae39af0872bf5cbf27e1800478021Mason Tang recycle = new Time(); 54747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 54847d40324272ae39af0872bf5cbf27e1800478021Mason Tang recycle.timezone = Time.TIMEZONE_UTC; 54947d40324272ae39af0872bf5cbf27e1800478021Mason Tang recycle.set(utcTime); 55047d40324272ae39af0872bf5cbf27e1800478021Mason Tang recycle.timezone = TimeZone.getDefault().getID(); 55147d40324272ae39af0872bf5cbf27e1800478021Mason Tang return recycle.normalize(true); 55247d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 553bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 554bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 55547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Calculate flipping point for the given event; when we should hide this 55647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * event and show the next one. This is defined as the end time of the 55747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * event. 55847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 55947d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param start Event start time in local timezone. 56047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param end Event end time in local timezone. 561bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 56247d40324272ae39af0872bf5cbf27e1800478021Mason Tang static private long getEventFlip(Cursor cursor, long start, long end, boolean allDay) { 56347d40324272ae39af0872bf5cbf27e1800478021Mason Tang return end; 56447d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 565bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 56647d40324272ae39af0872bf5cbf27e1800478021Mason Tang static void updateTextView(RemoteViews views, int id, int visibility, String string) { 56747d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setViewVisibility(id, visibility); 56847d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (visibility == View.VISIBLE) { 56947d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setTextViewText(id, string); 57047d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 57147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 572bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 573bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang /** 57447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * Pulls the information for a single event from the cursor and populates 57547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * the corresponding model object with the data. 57647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 57747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param context a Context to use for accessing resources 57847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param cursor the cursor to retrieve the data from 57947d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param rowId the ID of the row to retrieve 58047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param model the model object to populate 58147d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param recycle a Time instance to recycle 58247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param eventIndex which event index in the model to populate 58347d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param showTitleLocation whether or not to show the title and location 58447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param startOfNextDay the beginning of the next day 58547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param currentTime the current time 586bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 58747d40324272ae39af0872bf5cbf27e1800478021Mason Tang static private void populateEvent(Context context, Cursor cursor, int rowId, 58847d40324272ae39af0872bf5cbf27e1800478021Mason Tang CalendarAppWidgetModel model, Time recycle, int eventIndex, 58947d40324272ae39af0872bf5cbf27e1800478021Mason Tang boolean showTitleLocation, long startOfNextDay, long currentTime) { 59047d40324272ae39af0872bf5cbf27e1800478021Mason Tang cursor.moveToPosition(rowId); 591bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 59247d40324272ae39af0872bf5cbf27e1800478021Mason Tang // When 59347d40324272ae39af0872bf5cbf27e1800478021Mason Tang boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0; 594bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long start = cursor.getLong(INDEX_BEGIN); 595bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang long end = cursor.getLong(INDEX_END); 596bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang if (allDay) { 597bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang start = convertUtcToLocal(recycle, start); 598bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang end = convertUtcToLocal(recycle, end); 599bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 600bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 60147d40324272ae39af0872bf5cbf27e1800478021Mason Tang boolean eventIsInProgress = start <= currentTime && end > currentTime; 60247d40324272ae39af0872bf5cbf27e1800478021Mason Tang boolean eventIsToday = start < startOfNextDay; 60347d40324272ae39af0872bf5cbf27e1800478021Mason Tang boolean eventIsTomorrow = !eventIsToday && !eventIsInProgress 60447d40324272ae39af0872bf5cbf27e1800478021Mason Tang && (start < (startOfNextDay + DateUtils.DAY_IN_MILLIS)); 60547d40324272ae39af0872bf5cbf27e1800478021Mason Tang 60647d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Compute a human-readable string for the start time of the event 60747d40324272ae39af0872bf5cbf27e1800478021Mason Tang String whenString; 60847d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (eventIsInProgress && allDay) { 60947d40324272ae39af0872bf5cbf27e1800478021Mason Tang // All day events for the current day display as just "Today" 61047d40324272ae39af0872bf5cbf27e1800478021Mason Tang whenString = context.getString(R.string.today); 61147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } else if (eventIsTomorrow && allDay) { 61247d40324272ae39af0872bf5cbf27e1800478021Mason Tang // All day events for the next day display as just "Tomorrow" 61347d40324272ae39af0872bf5cbf27e1800478021Mason Tang whenString = context.getString(R.string.tomorrow); 61447d40324272ae39af0872bf5cbf27e1800478021Mason Tang } else { 61547d40324272ae39af0872bf5cbf27e1800478021Mason Tang int flags = DateUtils.FORMAT_ABBREV_ALL; 61647d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (allDay) { 61747d40324272ae39af0872bf5cbf27e1800478021Mason Tang flags |= DateUtils.FORMAT_UTC; 61847d40324272ae39af0872bf5cbf27e1800478021Mason Tang } else { 61947d40324272ae39af0872bf5cbf27e1800478021Mason Tang flags |= DateUtils.FORMAT_SHOW_TIME; 62047d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (DateFormat.is24HourFormat(context)) { 62147d40324272ae39af0872bf5cbf27e1800478021Mason Tang flags |= DateUtils.FORMAT_24HOUR; 62247d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 62347d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 62447d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Show day of the week if not today or tomorrow 62547d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (!eventIsTomorrow && !eventIsToday) { 62647d40324272ae39af0872bf5cbf27e1800478021Mason Tang flags |= DateUtils.FORMAT_SHOW_WEEKDAY; 62747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 62847d40324272ae39af0872bf5cbf27e1800478021Mason Tang whenString = DateUtils.formatDateRange(context, start, start, flags); 62947d40324272ae39af0872bf5cbf27e1800478021Mason Tang // TODO better i18n formatting 63047d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (eventIsTomorrow) { 63147d40324272ae39af0872bf5cbf27e1800478021Mason Tang whenString += (", "); 63247d40324272ae39af0872bf5cbf27e1800478021Mason Tang whenString += context.getString(R.string.tomorrow); 63347d40324272ae39af0872bf5cbf27e1800478021Mason Tang } else if (eventIsInProgress) { 63447d40324272ae39af0872bf5cbf27e1800478021Mason Tang whenString += " ("; 63547d40324272ae39af0872bf5cbf27e1800478021Mason Tang whenString += context.getString(R.string.in_progress); 63647d40324272ae39af0872bf5cbf27e1800478021Mason Tang whenString += ")"; 63747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 638bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 639bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 64047d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.eventInfos[eventIndex].start = start; 64147d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.eventInfos[eventIndex].when = whenString; 64247d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.eventInfos[eventIndex].visibWhen = View.VISIBLE; 643bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 64447d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (showTitleLocation) { 64547d40324272ae39af0872bf5cbf27e1800478021Mason Tang // What 64647d40324272ae39af0872bf5cbf27e1800478021Mason Tang String titleString = cursor.getString(INDEX_TITLE); 64747d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (TextUtils.isEmpty(titleString)) { 64847d40324272ae39af0872bf5cbf27e1800478021Mason Tang titleString = context.getString(R.string.no_title_label); 649bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 65047d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.eventInfos[eventIndex].title = titleString; 65147d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.eventInfos[eventIndex].visibTitle = View.VISIBLE; 65247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 65347d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Where 65447d40324272ae39af0872bf5cbf27e1800478021Mason Tang String whereString = cursor.getString(INDEX_EVENT_LOCATION); 65547d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (!TextUtils.isEmpty(whereString)) { 65647d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.eventInfos[eventIndex].visibWhere = View.VISIBLE; 65747d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.eventInfos[eventIndex].where = whereString; 658bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } else { 65947d40324272ae39af0872bf5cbf27e1800478021Mason Tang model.eventInfos[eventIndex].visibWhere = View.GONE; 660bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 66147d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (LOGD) Log.d(TAG, " Title:" + titleString + " Where:" + whereString); 662bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 663bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 664bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 665bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang} 666