CalendarAppWidgetService.java revision 3ea333d41c04fd5f3a5d45f540c17894874429e8
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;
22bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.ContentResolver;
23bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context;
24bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent;
25bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor;
2647d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.MatrixCursor;
27bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri;
28bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Attendees;
29bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Calendars;
30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Instances;
31bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils;
32bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time;
33bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log;
34bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View;
35bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews;
3647d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.widget.RemoteViewsService;
3747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
3847d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.google.common.annotations.VisibleForTesting;
3947d40324272ae39af0872bf5cbf27e1800478021Mason Tang
4047d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.calendar.R;
4147d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.calendar.Utils;
423ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport com.android.calendar.widget.CalendarAppWidgetModel.DayInfo;
4347d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo;
443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport com.android.calendar.widget.CalendarAppWidgetModel.RowInfo;
45bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
46bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
4747d40324272ae39af0872bf5cbf27e1800478021Mason Tangpublic class CalendarAppWidgetService extends RemoteViewsService {
48bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final String TAG = "CalendarAppWidgetService";
49bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final boolean LOGD = false;
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,
713ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Instances.END_DAY
72bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    };
73bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_ALL_DAY = 0;
75bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_BEGIN = 1;
76bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_END = 2;
77bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_TITLE = 3;
78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_EVENT_LOCATION = 4;
79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_EVENT_ID = 5;
803ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int INDEX_START_DAY = 6;
813ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int INDEX_END_DAY = 7;
82bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
833ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int MAX_DAYS = 7;
843ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
853ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS;
86bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
87bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    // If no next-update calculated, or bad trigger time in past, schedule
88bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    // update about six hours from now.
89bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final long UPDATE_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6;
90bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
9147d40324272ae39af0872bf5cbf27e1800478021Mason Tang    @Override
9247d40324272ae39af0872bf5cbf27e1800478021Mason Tang    public RemoteViewsFactory onGetViewFactory(Intent intent) {
9347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        return new CalendarFactory(getApplicationContext(), intent);
94bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
95bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
9647d40324272ae39af0872bf5cbf27e1800478021Mason Tang    protected static class CalendarFactory implements RemoteViewsService.RemoteViewsFactory {
9747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
9847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static final String TAG = CalendarFactory.class.getSimpleName();
99bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
100f9df037f350fad73659307ba05f230d2db69051aMason Tang        private static final boolean LOGD = false;
10147d40324272ae39af0872bf5cbf27e1800478021Mason Tang
10247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private final int mAppWidgetId;
10347d40324272ae39af0872bf5cbf27e1800478021Mason Tang
10447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Context mContext;
10547d40324272ae39af0872bf5cbf27e1800478021Mason Tang
10647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private CalendarAppWidgetModel mModel;
10747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
10847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Cursor mCursor;
10947d40324272ae39af0872bf5cbf27e1800478021Mason Tang
11047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected CalendarFactory(Context context, Intent intent) {
11147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mContext = context;
11247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
11347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    AppWidgetManager.INVALID_APPWIDGET_ID);
114bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
115bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
11647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
11747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onCreate() {
11847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            loadData();
119bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
120bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
12147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
122d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        public void onDataSetChanged() {
1230054262c76ccc4b20dc6596e161df8a99d0c81c7Mason Tang            loadData();
124d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        }
125d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung
126d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        @Override
12747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onDestroy() {
12847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCursor.close();
129bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
130bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
13147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
13247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getLoadingView() {
13347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            RemoteViews views = new RemoteViews(mContext.getPackageName(),
13447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    R.layout.appwidget_loading);
13547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return views;
136bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
137bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
13847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
13947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getViewAt(int position) {
14047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // we use getCount here so that it doesn't return null when empty
14147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (position < 0 || position >= getCount()) {
14247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return null;
14347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
144bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
1453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) {
1463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
1473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        R.layout.appwidget_no_events);
1483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                PendingIntent launchIntent =
1493ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    CalendarAppWidgetProvider.getLaunchPendingIntent(
1503ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                            mContext, 0);
1513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                views.setOnClickPendingIntent(R.id.appwidget_no_events, launchIntent);
1523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return views;
1533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
1543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
1553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
1563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            RowInfo rowInfo = mModel.mRowInfos.get(position);
1573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (rowInfo.mType == RowInfo.TYPE_DAY) {
1583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
1593ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        R.layout.appwidget_day);
1603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex);
1613ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel);
1623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return views;
1633ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            } else {
16447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
16547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        R.layout.appwidget_row);
166bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
1673ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                EventInfo e = mModel.mEventInfos.get(rowInfo.mIndex);
168bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
16947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.when, e.visibWhen, e.when);
17047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.where, e.visibWhere, e.where);
17147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.title, e.visibTitle, e.title);
172bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
17347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                PendingIntent launchIntent =
17447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    CalendarAppWidgetProvider.getLaunchPendingIntent(
17547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                            mContext, e.start);
17647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                views.setOnClickPendingIntent(R.id.appwidget_row, launchIntent);
17747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return views;
17847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
179bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
180bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
18147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
18247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getViewTypeCount() {
1833ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return 4;
18447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
185bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
18647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
18747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getCount() {
18847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // if there are no events, we still return 1 to represent the "no
18947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // events" view
1903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return Math.max(1, mModel.mRowInfos.size());
19147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
192bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
19347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
19447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public long getItemId(int position) {
19547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return position;
19647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
197bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
19847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
19947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public boolean hasStableIds() {
20047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return true;
201bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
202bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
20347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private void loadData() {
20447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long now = System.currentTimeMillis();
20547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (LOGD) Log.d(TAG, "Querying for widget events...");
20647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (mCursor != null) {
20747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                mCursor.close();
20847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
209bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
21047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCursor = getUpcomingInstancesCursor(
21147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    mContext.getContentResolver(), SEARCH_DURATION, now);
2123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mModel = buildAppWidgetModel(mContext, mCursor);
2133ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            long triggerTime = calculateUpdateTime(mModel);
21447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Schedule an alarm to wake ourselves up for the next update.  We also cancel
21547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // all existing wake-ups because PendingIntents don't match against extras.
21647d40324272ae39af0872bf5cbf27e1800478021Mason Tang
21747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // If no next-update calculated, or bad trigger time in past, schedule
21847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // update about six hours from now.
21947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (triggerTime == -1 || triggerTime < now) {
22047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (LOGD) Log.w(TAG, "Encountered bad trigger time " +
22147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        formatDebugTime(triggerTime, now));
22247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                triggerTime = now + UPDATE_NO_EVENTS;
22347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
224bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
22547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
22647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            PendingIntent pendingUpdate = CalendarAppWidgetProvider.getUpdateIntent(mContext);
227bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
22847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            am.cancel(pendingUpdate);
22947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            am.set(AlarmManager.RTC, triggerTime, pendingUpdate);
23047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (LOGD) Log.d(TAG, "Scheduled next update at " + formatDebugTime(triggerTime, now));
23147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
232bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
23347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
23447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Query across all calendars for upcoming event instances from now until
23547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * some time in the future.
23647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
23747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Widen the time range that we query by one day on each end so that we can
23847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * catch all-day events. All-day events are stored starting at midnight in
23947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * UTC but should be included in the list of events starting at midnight
24047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * local time. This may fetch more events than we actually want, so we
24147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * filter them out later.
24247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
24347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param resolver {@link ContentResolver} to use when querying
24447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link Instances#CONTENT_URI}.
24547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param searchDuration Distance into the future to look for event
24647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            instances, in milliseconds.
24747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param now Current system time to use for this update, possibly from
24847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link System#currentTimeMillis()}.
24947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
25047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Cursor getUpcomingInstancesCursor(ContentResolver resolver,
25147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long searchDuration, long now) {
25247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Search for events from now until some time in the future
253bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
25447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Add a day on either side to catch all-day events
25547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long begin = now - DateUtils.DAY_IN_MILLIS;
25647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long end = now + searchDuration + DateUtils.DAY_IN_MILLIS;
257bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
25847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI,
25947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    String.format("%d/%d", begin, end));
260bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
26147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Cursor cursor = resolver.query(uri, EVENT_PROJECTION,
26247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    EVENT_SELECTION, null, EVENT_SORT_ORDER);
263bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
26447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Start managing the cursor ourselves
26547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
26647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            cursor.close();
267e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang
26847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return matrixCursor;
269bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
270bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
27147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @VisibleForTesting
27247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected static CalendarAppWidgetModel buildAppWidgetModel(
2733ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                Context context, Cursor cursor) {
2743ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            CalendarAppWidgetModel model = new CalendarAppWidgetModel(context);
2753ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            model.buildFromCursor(cursor);
27647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return model;
277bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
278bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
27947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
28047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Figure out the next time we should push widget updates, usually the time
28147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * calculated by {@link #getEventFlip(Cursor, long, long, boolean)}.
28247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
28347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param cursor Valid cursor on {@link Instances#CONTENT_URI}
28447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param events {@link MarkedEvents} parsed from the cursor
28547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
2863ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private long calculateUpdateTime(CalendarAppWidgetModel model) {
28747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long result = -1;
2883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (!model.mEventInfos.isEmpty()) {
2893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                EventInfo firstEvent = model.mEventInfos.get(0);
2903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                long start = firstEvent.start;
2913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                long end = firstEvent.end;
2923ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                boolean allDay = firstEvent.allDay;
29347d40324272ae39af0872bf5cbf27e1800478021Mason Tang
29447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Adjust all-day times into local timezone
29547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (allDay) {
29647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    final Time recycle = new Time();
2973ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    start = Utils.convertUtcToLocal(recycle, start);
2983ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    end = Utils.convertUtcToLocal(recycle, end);
299bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                }
300bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
3013ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                result = getEventFlip(start, end, allDay);
302bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
30347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Make sure an update happens at midnight or earlier
30447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long midnight = getNextMidnightTimeMillis();
30547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                result = Math.min(midnight, result);
306bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
30747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return result;
308bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
309bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
31047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static long getNextMidnightTimeMillis() {
31147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Time time = new Time();
31247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.setToNow();
31347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.monthDay++;
31447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.hour = 0;
31547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.minute = 0;
31647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.second = 0;
31747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long midnight = time.normalize(true);
31847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return midnight;
319bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
320bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
32147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static void updateTextView(RemoteViews views, int id, int visibility, String string) {
32247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            views.setViewVisibility(id, visibility);
32347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (visibility == View.VISIBLE) {
32447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                views.setTextViewText(id, string);
32547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
32647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
3273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
328bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
3293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
3303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * Format given time for debugging output.
3313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *
3323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param unixTime Target time to report.
3333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param now Current system time from {@link System#currentTimeMillis()}
3343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *            for calculating time difference.
3353ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     */
3363ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static String formatDebugTime(long unixTime, long now) {
3373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Time time = new Time();
3383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        time.set(unixTime);
3393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3403ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        long delta = unixTime - now;
3413ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        if (delta > DateUtils.MINUTE_IN_MILLIS) {
3423ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            delta /= DateUtils.MINUTE_IN_MILLIS;
3433ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return String.format("[%d] %s (%+d mins)", unixTime,
3443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    time.format("%H:%M:%S"), delta);
3453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        } else {
3463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            delta /= DateUtils.SECOND_IN_MILLIS;
3473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return String.format("[%d] %s (%+d secs)", unixTime,
3483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    time.format("%H:%M:%S"), delta);
349bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
350bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
3513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
3533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * Calculate flipping point for the given event; when we should hide this
3543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * event and show the next one. This is defined as the end time of the
3553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * event.
3563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *
3573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param start Event start time in local timezone.
3583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param end Event end time in local timezone.
3593ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param allDay whether or not the event is all-day
3603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     */
3613ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static long getEventFlip(long start, long end, boolean allDay) {
3623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        return end;
3633ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
364bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang}
365