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