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 19a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErikimport android.app.AlarmManager; 20a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErikimport android.app.PendingIntent; 21c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.appwidget.AppWidgetManager; 22c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.BroadcastReceiver; 23bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context; 24c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.CursorLoader; 25bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent; 26c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.Loader; 27a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawaimport android.content.res.Resources; 28bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor; 2947d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.MatrixCursor; 30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri; 31c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.os.Handler; 32a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Attendees; 33a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Calendars; 34a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Instances; 35bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils; 36bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time; 37bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log; 38bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View; 39bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews; 4047d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.widget.RemoteViewsService; 4147d40324272ae39af0872bf5cbf27e1800478021Mason Tang 429e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.R; 439e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.Utils; 449e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.widget.CalendarAppWidgetModel.DayInfo; 459e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo; 469e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.widget.CalendarAppWidgetModel.RowInfo; 479e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chan 489e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport java.util.concurrent.ExecutorService; 499e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport java.util.concurrent.Executors; 509e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport java.util.concurrent.atomic.AtomicInteger; 519e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chan 52bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 5347d40324272ae39af0872bf5cbf27e1800478021Mason Tangpublic class CalendarAppWidgetService extends RemoteViewsService { 540c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa private static final String TAG = "CalendarWidget"; 55bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int EVENT_MIN_COUNT = 20; 579e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chan static final int EVENT_MAX_COUNT = 100; 58c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // Minimum delay between queries on the database for widget updates in ms 59c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik static final int WIDGET_UPDATE_THROTTLE = 500; 6047d40324272ae39af0872bf5cbf27e1800478021Mason Tang 61bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, " 62bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, " 633ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT; 64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 65e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik private static final String EVENT_SELECTION = Calendars.VISIBLE + "=1"; 6642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik private static final String EVENT_SELECTION_HIDE_DECLINED = Calendars.VISIBLE + "=1 AND " 67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED; 68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final String[] EVENT_PROJECTION = new String[] { 70bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.ALL_DAY, 71bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.BEGIN, 72bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.END, 73bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.TITLE, 74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_LOCATION, 75bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_ID, 763ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang Instances.START_DAY, 77a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa Instances.END_DAY, 78693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan Instances.DISPLAY_COLOR, // If SDK < 16, set to Instances.CALENDAR_COLOR. 7942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik Instances.SELF_ATTENDEE_STATUS, 80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang }; 81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 82bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_ALL_DAY = 0; 83bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_BEGIN = 1; 84bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_END = 2; 85bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_TITLE = 3; 86bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_LOCATION = 4; 87bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_ID = 5; 883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int INDEX_START_DAY = 6; 893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int INDEX_END_DAY = 7; 90a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa static final int INDEX_COLOR = 8; 9142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik static final int INDEX_SELF_ATTENDEE_STATUS = 9; 92bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 93693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan static { 94fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting if (!Utils.isJellybeanOrLater()) { 95693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan EVENT_PROJECTION[INDEX_COLOR] = Instances.CALENDAR_COLOR; 96693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan } 97693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan } 983ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int MAX_DAYS = 7; 993ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 1003ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS; 101bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 1020c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa /** 1030c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa * Update interval used when no next-update calculated, or bad trigger time in past. 1040c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa * Unit: milliseconds. 1050c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa */ 1060c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa private static final long UPDATE_TIME_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6; 107bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 10847d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 10947d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViewsFactory onGetViewFactory(Intent intent) { 11047d40324272ae39af0872bf5cbf27e1800478021Mason Tang return new CalendarFactory(getApplicationContext(), intent); 111bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 112bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 113f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik public static class CalendarFactory extends BroadcastReceiver implements 114c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik RemoteViewsService.RemoteViewsFactory, Loader.OnLoadCompleteListener<Cursor> { 115f9df037f350fad73659307ba05f230d2db69051aMason Tang private static final boolean LOGD = false; 1160c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa 1170c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // Suppress unnecessary logging about update time. Need to be static as this object is 1180c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // re-instanciated frequently. 1190c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // TODO: It seems loadData() is called via onCreate() four times, which should mean 1200c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // unnecessary CalendarFactory object is created and dropped. It is not efficient. 1210c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa private static long sLastUpdateTime = UPDATE_TIME_NO_EVENTS; 1220c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa 12347d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Context mContext; 124a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa private Resources mResources; 125f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik private static CalendarAppWidgetModel mModel; 126f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan private static Object mLock = new Object(); 127f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan private static volatile int mSerialNum = 0; 128f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan private int mLastSerialNum = -1; 129c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private CursorLoader mLoader; 1300ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelson private final Handler mHandler = new Handler(); 1316e32890eade48d4edd70edcf29a25918b439f4aaSara Ting private static final AtomicInteger currentVersion = new AtomicInteger(0); 1320ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelson private final ExecutorService executor = Executors.newSingleThreadExecutor(); 133c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private int mAppWidgetId; 13442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik private int mDeclinedColor; 13542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik private int mStandardColor; 136e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik private int mAllDayColor; 137c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 1380ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelson private final Runnable mTimezoneChanged = new Runnable() { 139c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik @Override 140c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik public void run() { 141c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (mLoader != null) { 142c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.forceLoad(); 143c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 144c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 145c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik }; 146c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 1476e32890eade48d4edd70edcf29a25918b439f4aaSara Ting private Runnable createUpdateLoaderRunnable(final String selection, 1486e32890eade48d4edd70edcf29a25918b439f4aaSara Ting final PendingResult result, final int version) { 1496e32890eade48d4edd70edcf29a25918b439f4aaSara Ting return new Runnable() { 1506e32890eade48d4edd70edcf29a25918b439f4aaSara Ting @Override 1516e32890eade48d4edd70edcf29a25918b439f4aaSara Ting public void run() { 1526e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // If there is a newer load request in the queue, skip loading. 1536e32890eade48d4edd70edcf29a25918b439f4aaSara Ting if (mLoader != null && version >= currentVersion.get()) { 1546e32890eade48d4edd70edcf29a25918b439f4aaSara Ting Uri uri = createLoaderUri(); 1556e32890eade48d4edd70edcf29a25918b439f4aaSara Ting mLoader.setUri(uri); 1566e32890eade48d4edd70edcf29a25918b439f4aaSara Ting mLoader.setSelection(selection); 1576e32890eade48d4edd70edcf29a25918b439f4aaSara Ting synchronized (mLock) { 158f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan mLastSerialNum = ++mSerialNum; 1596e32890eade48d4edd70edcf29a25918b439f4aaSara Ting } 1606e32890eade48d4edd70edcf29a25918b439f4aaSara Ting mLoader.forceLoad(); 161f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } 1626e32890eade48d4edd70edcf29a25918b439f4aaSara Ting result.finish(); 163c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 1646e32890eade48d4edd70edcf29a25918b439f4aaSara Ting }; 1656e32890eade48d4edd70edcf29a25918b439f4aaSara Ting } 16647d40324272ae39af0872bf5cbf27e1800478021Mason Tang 16747d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected CalendarFactory(Context context, Intent intent) { 16847d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext = context; 169a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa mResources = context.getResources(); 170c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mAppWidgetId = intent.getIntExtra( 171c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); 17242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik 17395d6fe0b672284a19699edc4727c366e3b0ccec7Peter Ng mDeclinedColor = mResources.getColor(R.color.appwidget_item_declined_color); 17495d6fe0b672284a19699edc4727c366e3b0ccec7Peter Ng mStandardColor = mResources.getColor(R.color.appwidget_item_standard_color); 175e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik mAllDayColor = mResources.getColor(R.color.appwidget_item_allday_color); 176bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 177bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 178f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik public CalendarFactory() { 179f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // This is being created as part of onReceive 180f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik 181f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } 182f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik 18347d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 18447d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onCreate() { 1856e32890eade48d4edd70edcf29a25918b439f4aaSara Ting String selection = queryForSelection(); 1866e32890eade48d4edd70edcf29a25918b439f4aaSara Ting initLoader(selection); 187bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 188bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 18947d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 190d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung public void onDataSetChanged() { 191d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung } 192d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung 193d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung @Override 19447d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onDestroy() { 195673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson if (mLoader != null) { 196673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson mLoader.reset(); 197673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson } 198bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 199bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 20047d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 20147d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViews getLoadingView() { 20247d40324272ae39af0872bf5cbf27e1800478021Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 20347d40324272ae39af0872bf5cbf27e1800478021Mason Tang R.layout.appwidget_loading); 20447d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 205bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 206bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 20747d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 20847d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViews getViewAt(int position) { 20947d40324272ae39af0872bf5cbf27e1800478021Mason Tang // we use getCount here so that it doesn't return null when empty 21047d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (position < 0 || position >= getCount()) { 21147d40324272ae39af0872bf5cbf27e1800478021Mason Tang return null; 21247d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 213bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 214254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik if (mModel == null) { 215254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik RemoteViews views = new RemoteViews(mContext.getPackageName(), 216254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik R.layout.appwidget_loading); 21707d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0, 2186bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson 0, 0, false); 219254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik views.setOnClickFillInIntent(R.id.appwidget_loading, intent); 220254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik return views; 221254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik 222254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik } 223254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) { 2243ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 2253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang R.layout.appwidget_no_events); 22607d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0, 2276bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson 0, 0, false); 2284143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa views.setOnClickFillInIntent(R.id.appwidget_no_events, intent); 2293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return views; 2303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 2313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 2323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RowInfo rowInfo = mModel.mRowInfos.get(position); 2333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (rowInfo.mType == RowInfo.TYPE_DAY) { 2343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 2353ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang R.layout.appwidget_day); 2363ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex); 2373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel); 2383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return views; 2393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } else { 24042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik RemoteViews views; 241a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa final EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex); 24242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik if (eventInfo.allDay) { 24342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views = new RemoteViews(mContext.getPackageName(), 24442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik R.layout.widget_all_day_item); 24542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik } else { 24642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views = new RemoteViews(mContext.getPackageName(), R.layout.widget_item); 24742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik } 2484acb2fd087308dea146b8b10f5278c59df387680RoboErik int displayColor = Utils.getDisplayColorFromColor(eventInfo.color); 249a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa 250a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa final long now = System.currentTimeMillis(); 251a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa if (!eventInfo.allDay && eventInfo.start <= now && now <= eventInfo.end) { 25231412a0fea756e0da0bcbdf3cdffe4efae21cdbeIsaac Katzenelson views.setInt(R.id.widget_row, "setBackgroundResource", 25331412a0fea756e0da0bcbdf3cdffe4efae21cdbeIsaac Katzenelson R.drawable.agenda_item_bg_secondary); 254ffaeace621183dfe8770471a30b2f1138aac5f86Daisuke Miyakawa } else { 25531412a0fea756e0da0bcbdf3cdffe4efae21cdbeIsaac Katzenelson views.setInt(R.id.widget_row, "setBackgroundResource", 25631412a0fea756e0da0bcbdf3cdffe4efae21cdbeIsaac Katzenelson R.drawable.agenda_item_bg_primary); 257a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa } 258a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa 259e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik if (!eventInfo.allDay) { 260e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik updateTextView(views, R.id.when, eventInfo.visibWhen, eventInfo.when); 261e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik updateTextView(views, R.id.where, eventInfo.visibWhere, eventInfo.where); 262e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik } 263a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa updateTextView(views, R.id.title, eventInfo.visibTitle, eventInfo.title); 264bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 26542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setViewVisibility(R.id.agenda_item_color, View.VISIBLE); 26642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik 26742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik int selfAttendeeStatus = eventInfo.selfAttendeeStatus; 268e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik if (eventInfo.allDay) { 269e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) { 270e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik views.setInt(R.id.agenda_item_color, "setImageResource", 271e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik R.drawable.widget_chip_not_responded_bg); 272e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik views.setInt(R.id.title, "setTextColor", displayColor); 273e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik } else { 274e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik views.setInt(R.id.agenda_item_color, "setImageResource", 275e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik R.drawable.widget_chip_responded_bg); 276e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik views.setInt(R.id.title, "setTextColor", mAllDayColor); 277e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik } 278e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) { 279e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik // 40% opacity 280e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik views.setInt(R.id.agenda_item_color, "setColorFilter", 281e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik Utils.getDeclinedColorFromColor(displayColor)); 282e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik } else { 283e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor); 284e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik } 285e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik } else if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) { 28642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.title, "setTextColor", mDeclinedColor); 28742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.when, "setTextColor", mDeclinedColor); 28842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.where, "setTextColor", mDeclinedColor); 28942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik // views.setInt(R.id.agenda_item_color, "setDrawStyle", 29042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik // ColorChipView.DRAW_CROSS_HATCHED); 29142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.agenda_item_color, "setImageResource", 292aeae98bf8f8c5c544f65f5b6b7f29cfd31366ad5Michael Chan R.drawable.widget_chip_responded_bg); 29342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik // 40% opacity 29442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.agenda_item_color, "setColorFilter", 295210642aa1f8bec18f7275cddb9a3b50fad3f4218RoboErik Utils.getDeclinedColorFromColor(displayColor)); 29642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik } else { 29742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.title, "setTextColor", mStandardColor); 29842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.when, "setTextColor", mStandardColor); 29942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.where, "setTextColor", mStandardColor); 30042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) { 30142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.agenda_item_color, "setImageResource", 30242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik R.drawable.widget_chip_not_responded_bg); 30342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik } else { 30442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setInt(R.id.agenda_item_color, "setImageResource", 30542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik R.drawable.widget_chip_responded_bg); 30642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik } 3074acb2fd087308dea146b8b10f5278c59df387680RoboErik views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor); 30842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik } 309bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 3109da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik long start = eventInfo.start; 3119da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik long end = eventInfo.end; 3124143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa // An element in ListView. 3139da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik if (eventInfo.allDay) { 3149da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik String tz = Utils.getTimeZone(mContext, null); 3159da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik Time recycle = new Time(); 3169da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik start = Utils.convertAlldayLocalToUTC(recycle, start, tz); 3179da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik end = Utils.convertAlldayLocalToUTC(recycle, end, tz); 3189da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik } 319bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan final Intent fillInIntent = CalendarAppWidgetProvider.getLaunchFillInIntent( 3206bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson mContext, eventInfo.id, start, end, eventInfo.allDay); 32142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik views.setOnClickFillInIntent(R.id.widget_row, fillInIntent); 32247d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 32347d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 324bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 325bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 32647d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 32747d40324272ae39af0872bf5cbf27e1800478021Mason Tang public int getViewTypeCount() { 328da6bd6f971f74bf9b434ba84aaa092a2a1a76511Kevin Barry return 5; 32947d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 330bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 33147d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 33247d40324272ae39af0872bf5cbf27e1800478021Mason Tang public int getCount() { 33347d40324272ae39af0872bf5cbf27e1800478021Mason Tang // if there are no events, we still return 1 to represent the "no 33447d40324272ae39af0872bf5cbf27e1800478021Mason Tang // events" view 3354299eb1c1d81042de46a8d3972fde8719820d06cRoboErik if (mModel == null) { 3364299eb1c1d81042de46a8d3972fde8719820d06cRoboErik return 1; 3374299eb1c1d81042de46a8d3972fde8719820d06cRoboErik } 3383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return Math.max(1, mModel.mRowInfos.size()); 33947d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 340bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 34147d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 34247d40324272ae39af0872bf5cbf27e1800478021Mason Tang public long getItemId(int position) { 3439de6fc8bc215d95ff236855b5be97acbc50d31e5Sara Ting if (mModel == null || mModel.mRowInfos.isEmpty() || position >= getCount()) { 344713011baae993d4ca07a8267e3a0f9945753c2ffRoboErik return 0; 345713011baae993d4ca07a8267e3a0f9945753c2ffRoboErik } 346c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik RowInfo rowInfo = mModel.mRowInfos.get(position); 347c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (rowInfo.mType == RowInfo.TYPE_DAY) { 348c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik return rowInfo.mIndex; 349c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 350c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex); 351c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long prime = 31; 352c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long result = 1; 353c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik result = prime * result + (int) (eventInfo.id ^ (eventInfo.id >>> 32)); 354c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik result = prime * result + (int) (eventInfo.start ^ (eventInfo.start >>> 32)); 355c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik return result; 35647d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 357bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 35847d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 35947d40324272ae39af0872bf5cbf27e1800478021Mason Tang public boolean hasStableIds() { 36047d40324272ae39af0872bf5cbf27e1800478021Mason Tang return true; 361bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 362bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 36347d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 364c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * Query across all calendars for upcoming event instances from now 365c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * until some time in the future. Widen the time range that we query by 366c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * one day on each end so that we can catch all-day events. All-day 367c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * events are stored starting at midnight in UTC but should be included 368c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * in the list of events starting at midnight local time. This may fetch 369c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * more events than we actually want, so we filter them out later. 37047d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 3716e32890eade48d4edd70edcf29a25918b439f4aaSara Ting * @param selection The selection string for the loader to filter the query with. 37247d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 3736e32890eade48d4edd70edcf29a25918b439f4aaSara Ting public void initLoader(String selection) { 374c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (LOGD) 375c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik Log.d(TAG, "Querying for widget events..."); 376c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 37747d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Search for events from now until some time in the future 378c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik Uri uri = createLoaderUri(); 37942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik mLoader = new CursorLoader(mContext, uri, EVENT_PROJECTION, selection, null, 38042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik EVENT_SORT_ORDER); 381c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.setUpdateThrottle(WIDGET_UPDATE_THROTTLE); 382f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik synchronized (mLock) { 383f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan mLastSerialNum = ++mSerialNum; 384f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } 385c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.registerListener(mAppWidgetId, this); 386e3e1f555a89a2b4cb859ddae189f57aeb749a852RoboErik mLoader.startLoading(); 387c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 388c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 389bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 390c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik /** 3916e32890eade48d4edd70edcf29a25918b439f4aaSara Ting * This gets the selection string for the loader. This ends up doing a query in the 3926e32890eade48d4edd70edcf29a25918b439f4aaSara Ting * shared preferences. 3936e32890eade48d4edd70edcf29a25918b439f4aaSara Ting */ 3946e32890eade48d4edd70edcf29a25918b439f4aaSara Ting private String queryForSelection() { 3956e32890eade48d4edd70edcf29a25918b439f4aaSara Ting return Utils.getHideDeclinedEvents(mContext) ? EVENT_SELECTION_HIDE_DECLINED 3966e32890eade48d4edd70edcf29a25918b439f4aaSara Ting : EVENT_SELECTION; 3976e32890eade48d4edd70edcf29a25918b439f4aaSara Ting } 3986e32890eade48d4edd70edcf29a25918b439f4aaSara Ting 3996e32890eade48d4edd70edcf29a25918b439f4aaSara Ting /** 400c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * @return The uri for the loader 401c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik */ 402c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private Uri createLoaderUri() { 403c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long now = System.currentTimeMillis(); 40447d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Add a day on either side to catch all-day events 40547d40324272ae39af0872bf5cbf27e1800478021Mason Tang long begin = now - DateUtils.DAY_IN_MILLIS; 406c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long end = now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS; 407bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 408e0cb5ba884c52e9d36875fb4a9ebdf40a81cb642Michael Chan Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, Long.toString(begin) + "/" + end); 409c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik return uri; 41021a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 41121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik 4123f888688c0f2644ad3de032d5d1cf623a7b092fdAndy McFadden /* @VisibleForTesting */ 41347d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected static CalendarAppWidgetModel buildAppWidgetModel( 41421a183875fbbfa54f5a2a87779888a5fb7d1af44Erik Context context, Cursor cursor, String timeZone) { 415ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan CalendarAppWidgetModel model = new CalendarAppWidgetModel(context, timeZone); 41621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik model.buildFromCursor(cursor, timeZone); 41747d40324272ae39af0872bf5cbf27e1800478021Mason Tang return model; 418bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 419bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 42047d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 4210c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa * Calculates and returns the next time we should push widget updates. 42247d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 423ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan private long calculateUpdateTime(CalendarAppWidgetModel model, long now, String timeZone) { 4240c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // Make sure an update happens at midnight or earlier 425ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan long minUpdateTime = getNextMidnightTimeMillis(timeZone); 4260c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa for (EventInfo event : model.mEventInfos) { 4270c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa final long start; 4280c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa final long end; 4299da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik start = event.start; 4309da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik end = event.end; 431bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 4320c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // We want to update widget when we enter/exit time range of an event. 4330c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa if (now < start) { 4340c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa minUpdateTime = Math.min(minUpdateTime, start); 4350c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa } else if (now < end) { 4360c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa minUpdateTime = Math.min(minUpdateTime, end); 4370c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa } 438bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 4390c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa return minUpdateTime; 440bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 441bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 442ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan private static long getNextMidnightTimeMillis(String timezone) { 44347d40324272ae39af0872bf5cbf27e1800478021Mason Tang Time time = new Time(); 44447d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.setToNow(); 44547d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.monthDay++; 44647d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.hour = 0; 44747d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.minute = 0; 44847d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.second = 0; 449ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan long midnightDeviceTz = time.normalize(true); 450ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan 451ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.timezone = timezone; 452ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.setToNow(); 453ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.monthDay++; 454ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.hour = 0; 455ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.minute = 0; 456ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.second = 0; 457ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan long midnightHomeTz = time.normalize(true); 458ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan 459ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan return Math.min(midnightDeviceTz, midnightHomeTz); 460bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 461bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 46247d40324272ae39af0872bf5cbf27e1800478021Mason Tang static void updateTextView(RemoteViews views, int id, int visibility, String string) { 46347d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setViewVisibility(id, visibility); 46447d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (visibility == View.VISIBLE) { 46547d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setTextViewText(id, string); 46647d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 46747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 468c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 469c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik /* 470c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * (non-Javadoc) 471c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * @see 472c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * android.content.Loader.OnLoadCompleteListener#onLoadComplete(android 473c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * .content.Loader, java.lang.Object) 474c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik */ 475c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik @Override 476c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) { 477c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan if (cursor == null) { 47882a8afab75ee998fcc90a4bcbc62f4912bc582adRoboErik return; 47982a8afab75ee998fcc90a4bcbc62f4912bc582adRoboErik } 480f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // If a newer update has happened since we started clean up and 481f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // return 482f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik synchronized (mLock) { 483c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan if (cursor.isClosed()) { 484c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan Log.wtf(TAG, "Got a closed cursor from onLoadComplete"); 485c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan return; 486c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan } 487c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan 488f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan if (mLastSerialNum != mSerialNum) { 489f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik return; 490f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } 491c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan 492c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting final long now = System.currentTimeMillis(); 493c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting String tz = Utils.getTimeZone(mContext, mTimezoneChanged); 494c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting 495f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // Copy it to a local static cursor. 496f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor); 497c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting try { 498c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting mModel = buildAppWidgetModel(mContext, matrixCursor, tz); 499c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting } finally { 500c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting if (matrixCursor != null) { 501c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting matrixCursor.close(); 502c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting } 503c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 504c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting if (cursor != null) { 505c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting cursor.close(); 506c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting } 507f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } 508f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik 509f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // Schedule an alarm to wake ourselves up for the next update. 510f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // We also cancel 511f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // all existing wake-ups because PendingIntents don't match 512f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // against extras. 513f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik long triggerTime = calculateUpdateTime(mModel, now, tz); 514f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik 515f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // If no next-update calculated, or bad trigger time in past, 516f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // schedule 517f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik // update about six hours from now. 518f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik if (triggerTime < now) { 519f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik Log.w(TAG, "Encountered bad trigger time " + formatDebugTime(triggerTime, now)); 520f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik triggerTime = now + UPDATE_TIME_NO_EVENTS; 521f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } 522c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 523f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik final AlarmManager alertManager = (AlarmManager) mContext 524f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik .getSystemService(Context.ALARM_SERVICE); 525f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik final PendingIntent pendingUpdate = CalendarAppWidgetProvider 526f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik .getUpdateIntent(mContext); 527f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik 528f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik alertManager.cancel(pendingUpdate); 529f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik alertManager.set(AlarmManager.RTC, triggerTime, pendingUpdate); 530f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik Time time = new Time(Utils.getTimeZone(mContext, null)); 531f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik time.setToNow(); 532f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik 533f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik if (time.normalize(true) != sLastUpdateTime) { 534f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik Time time2 = new Time(Utils.getTimeZone(mContext, null)); 535f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik time2.set(sLastUpdateTime); 536f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik time2.normalize(true); 537f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik if (time.year != time2.year || time.yearDay != time2.yearDay) { 538f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik final Intent updateIntent = new Intent( 539f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik Utils.getWidgetUpdateAction(mContext)); 540f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik mContext.sendBroadcast(updateIntent); 541f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } 542a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik 543f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik sLastUpdateTime = time.toMillis(true); 544f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } 545a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik 546f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext); 547f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik if (mAppWidgetId == -1) { 548f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik int[] ids = widgetManager.getAppWidgetIds(CalendarAppWidgetProvider 549f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik .getComponentName(mContext)); 550a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik 551f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.events_list); 552f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik } else { 553f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik widgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId, R.id.events_list); 554a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik } 555a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik } 556c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 557c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 558c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik @Override 559c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik public void onReceive(Context context, Intent intent) { 560f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik if (LOGD) 561f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik Log.d(TAG, "AppWidgetService received an intent. It was " + intent.toString()); 562f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik mContext = context; 5636e32890eade48d4edd70edcf29a25918b439f4aaSara Ting 5646e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // We cannot do any queries from the UI thread, so push the 'selection' query 5656e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // to a background thread. However the implementation of the latter query 5666e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // (cursor loading) uses CursorLoader which must be initiated from the UI thread, 5676e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // so there is some convoluted handshaking here. 5686e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // 5696e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // Note that as currently implemented, this must run in a single threaded executor 5706e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // or else the loads may be run out of order. 571b91a6c39d91d3af86ab7462a498542b3a3e88336Sara Ting // 572b91a6c39d91d3af86ab7462a498542b3a3e88336Sara Ting // TODO: Remove use of mHandler and CursorLoader, and do all the work synchronously 573b91a6c39d91d3af86ab7462a498542b3a3e88336Sara Ting // in the background thread. All the handshaking going on here between the UI and 574b91a6c39d91d3af86ab7462a498542b3a3e88336Sara Ting // background thread with using goAsync, mHandler, and CursorLoader is confusing. 5756e32890eade48d4edd70edcf29a25918b439f4aaSara Ting final PendingResult result = goAsync(); 5766e32890eade48d4edd70edcf29a25918b439f4aaSara Ting executor.submit(new Runnable() { 5776e32890eade48d4edd70edcf29a25918b439f4aaSara Ting @Override 5786e32890eade48d4edd70edcf29a25918b439f4aaSara Ting public void run() { 5796e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // We always complete queryForSelection() even if the load task ends up being 5806e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // canceled because of a more recent one. Optimizing this to allow 5816e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // canceling would require keeping track of all the PendingResults 5826e32890eade48d4edd70edcf29a25918b439f4aaSara Ting // (from goAsync) to abort them. Defer this until it becomes a problem. 5836e32890eade48d4edd70edcf29a25918b439f4aaSara Ting final String selection = queryForSelection(); 5846e32890eade48d4edd70edcf29a25918b439f4aaSara Ting 5856e32890eade48d4edd70edcf29a25918b439f4aaSara Ting if (mLoader == null) { 5866e32890eade48d4edd70edcf29a25918b439f4aaSara Ting mAppWidgetId = -1; 5876e32890eade48d4edd70edcf29a25918b439f4aaSara Ting mHandler.post(new Runnable() { 5886e32890eade48d4edd70edcf29a25918b439f4aaSara Ting @Override 5896e32890eade48d4edd70edcf29a25918b439f4aaSara Ting public void run() { 5906e32890eade48d4edd70edcf29a25918b439f4aaSara Ting initLoader(selection); 5916e32890eade48d4edd70edcf29a25918b439f4aaSara Ting result.finish(); 5926e32890eade48d4edd70edcf29a25918b439f4aaSara Ting } 5936e32890eade48d4edd70edcf29a25918b439f4aaSara Ting }); 5946e32890eade48d4edd70edcf29a25918b439f4aaSara Ting } else { 5956e32890eade48d4edd70edcf29a25918b439f4aaSara Ting mHandler.post(createUpdateLoaderRunnable(selection, result, 5966e32890eade48d4edd70edcf29a25918b439f4aaSara Ting currentVersion.incrementAndGet())); 5976e32890eade48d4edd70edcf29a25918b439f4aaSara Ting } 5986e32890eade48d4edd70edcf29a25918b439f4aaSara Ting } 5996e32890eade48d4edd70edcf29a25918b439f4aaSara Ting }); 600c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 6013ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 602bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 6033ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang /** 6043ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * Format given time for debugging output. 6053ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * 6063ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param unixTime Target time to report. 6073ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param now Current system time from {@link System#currentTimeMillis()} 6083ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * for calculating time difference. 6093ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang */ 6103ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static String formatDebugTime(long unixTime, long now) { 6113ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang Time time = new Time(); 6123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.set(unixTime); 6133ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 6143ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang long delta = unixTime - now; 6153ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (delta > DateUtils.MINUTE_IN_MILLIS) { 6163ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang delta /= DateUtils.MINUTE_IN_MILLIS; 6173ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return String.format("[%d] %s (%+d mins)", unixTime, 6183ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.format("%H:%M:%S"), delta); 6193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } else { 6203ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang delta /= DateUtils.SECOND_IN_MILLIS; 6213ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return String.format("[%d] %s (%+d secs)", unixTime, 6223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.format("%H:%M:%S"), delta); 623bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 624bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 625bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang} 626