CalendarAppWidgetService.java revision 4143cfabb36f0c3adcad410a6eed29bc2b89d6e3
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.google.common.annotations.VisibleForTesting;
204143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa
214143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.R;
224143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.Utils;
234143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.DayInfo;
244143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo;
254143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.RowInfo;
264143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa
27bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.AlarmManager;
28bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.app.PendingIntent;
29bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.appwidget.AppWidgetManager;
30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.ContentResolver;
31bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context;
32bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent;
33bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor;
3447d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.MatrixCursor;
35bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri;
364143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport android.os.Bundle;
374143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport android.provider.Calendar;
38bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Attendees;
39bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Calendars;
40bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Instances;
41bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils;
42bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time;
43bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log;
44bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View;
45bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews;
4647d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.widget.RemoteViewsService;
4747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
48bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
4947d40324272ae39af0872bf5cbf27e1800478021Mason Tangpublic class CalendarAppWidgetService extends RemoteViewsService {
50bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final String TAG = "CalendarAppWidgetService";
51bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final boolean LOGD = false;
52bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int EVENT_MIN_COUNT = 20;
543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int EVENT_MAX_COUNT = 503;
5647d40324272ae39af0872bf5cbf27e1800478021Mason Tang
57bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, "
58bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, "
593ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT;
60bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
61bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    // TODO can't use parameter here because provider is dropping them
62bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final String EVENT_SELECTION = Calendars.SELECTED + "=1 AND "
63bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
65bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final String[] EVENT_PROJECTION = new String[] {
66bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.ALL_DAY,
67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.BEGIN,
68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.END,
69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.TITLE,
70bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.EVENT_LOCATION,
71bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.EVENT_ID,
723ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Instances.START_DAY,
733ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Instances.END_DAY
74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    };
75bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
76bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_ALL_DAY = 0;
77bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_BEGIN = 1;
78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_END = 2;
79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_TITLE = 3;
80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_EVENT_LOCATION = 4;
81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_EVENT_ID = 5;
823ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int INDEX_START_DAY = 6;
833ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int INDEX_END_DAY = 7;
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
10047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static final String TAG = CalendarFactory.class.getSimpleName();
101bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
102f9df037f350fad73659307ba05f230d2db69051aMason Tang        private static final boolean LOGD = false;
10347d40324272ae39af0872bf5cbf27e1800478021Mason Tang
10447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private final int mAppWidgetId;
10547d40324272ae39af0872bf5cbf27e1800478021Mason Tang
10647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Context mContext;
10747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
10847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private CalendarAppWidgetModel mModel;
10947d40324272ae39af0872bf5cbf27e1800478021Mason Tang
11047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Cursor mCursor;
11147d40324272ae39af0872bf5cbf27e1800478021Mason Tang
11247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected CalendarFactory(Context context, Intent intent) {
11347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mContext = context;
11447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
11547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    AppWidgetManager.INVALID_APPWIDGET_ID);
116bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
117bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
11847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
11947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onCreate() {
12047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            loadData();
121bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
122bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
12347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
124d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        public void onDataSetChanged() {
1250054262c76ccc4b20dc6596e161df8a99d0c81c7Mason Tang            loadData();
126d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        }
127d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung
128d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        @Override
12947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onDestroy() {
13047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCursor.close();
131bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
132bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
13347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
13447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getLoadingView() {
13547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            RemoteViews views = new RemoteViews(mContext.getPackageName(),
13647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    R.layout.appwidget_loading);
13747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return views;
138bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
139bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
14047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
14147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getViewAt(int position) {
14247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // we use getCount here so that it doesn't return null when empty
14347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (position < 0 || position >= getCount()) {
14447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return null;
14547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
146bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
1473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) {
1483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
1493ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        R.layout.appwidget_no_events);
1504143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                final Intent intent =  CalendarAppWidgetProvider.getLaunchFillInIntent(0);
1514143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                views.setOnClickFillInIntent(R.id.appwidget_no_events, intent);
1523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return views;
1533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
1543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
1553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            RowInfo rowInfo = mModel.mRowInfos.get(position);
1563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (rowInfo.mType == RowInfo.TYPE_DAY) {
1573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
1583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        R.layout.appwidget_day);
1593ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex);
1603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel);
1613ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return views;
1623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            } else {
16347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
16447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        R.layout.appwidget_row);
165bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
1663ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                EventInfo e = mModel.mEventInfos.get(rowInfo.mIndex);
167bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
16847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.when, e.visibWhen, e.when);
16947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.where, e.visibWhere, e.where);
17047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.title, e.visibTitle, e.title);
171bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
1724143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                // An element in ListView.
1734143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                final Intent fillInIntent =
1744143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                        CalendarAppWidgetProvider.getLaunchFillInIntent(e.start);
1754143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                views.setOnClickFillInIntent(R.id.appwidget_row, fillInIntent);
17647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return views;
17747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
178bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
179bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
18047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
18147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getViewTypeCount() {
1823ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return 4;
18347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
184bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
18547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
18647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getCount() {
18747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // if there are no events, we still return 1 to represent the "no
18847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // events" view
1893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return Math.max(1, mModel.mRowInfos.size());
19047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
191bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
19247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
19347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public long getItemId(int position) {
19447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return position;
19547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
196bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
19747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
19847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public boolean hasStableIds() {
19947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return true;
200bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
201bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
20247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private void loadData() {
20347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long now = System.currentTimeMillis();
20447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (LOGD) Log.d(TAG, "Querying for widget events...");
20547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (mCursor != null) {
20647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                mCursor.close();
20747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
208bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
20947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCursor = getUpcomingInstancesCursor(
21047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    mContext.getContentResolver(), SEARCH_DURATION, now);
2113ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mModel = buildAppWidgetModel(mContext, mCursor);
2123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            long triggerTime = calculateUpdateTime(mModel);
21347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Schedule an alarm to wake ourselves up for the next update.  We also cancel
21447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // all existing wake-ups because PendingIntents don't match against extras.
21547d40324272ae39af0872bf5cbf27e1800478021Mason Tang
21647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // If no next-update calculated, or bad trigger time in past, schedule
21747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // update about six hours from now.
21847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (triggerTime == -1 || triggerTime < now) {
21947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (LOGD) Log.w(TAG, "Encountered bad trigger time " +
22047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        formatDebugTime(triggerTime, now));
22147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                triggerTime = now + UPDATE_NO_EVENTS;
22247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
223bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
22447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
22547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            PendingIntent pendingUpdate = CalendarAppWidgetProvider.getUpdateIntent(mContext);
226bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
22747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            am.cancel(pendingUpdate);
22847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            am.set(AlarmManager.RTC, triggerTime, pendingUpdate);
22947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (LOGD) Log.d(TAG, "Scheduled next update at " + formatDebugTime(triggerTime, now));
23047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
231bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
23247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
23347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Query across all calendars for upcoming event instances from now until
23447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * some time in the future.
23547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
23647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Widen the time range that we query by one day on each end so that we can
23747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * catch all-day events. All-day events are stored starting at midnight in
23847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * UTC but should be included in the list of events starting at midnight
23947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * local time. This may fetch more events than we actually want, so we
24047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * filter them out later.
24147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
24247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param resolver {@link ContentResolver} to use when querying
24347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link Instances#CONTENT_URI}.
24447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param searchDuration Distance into the future to look for event
24547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            instances, in milliseconds.
24647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param now Current system time to use for this update, possibly from
24747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link System#currentTimeMillis()}.
24847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
24947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Cursor getUpcomingInstancesCursor(ContentResolver resolver,
25047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long searchDuration, long now) {
25147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Search for events from now until some time in the future
252bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
25347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Add a day on either side to catch all-day events
25447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long begin = now - DateUtils.DAY_IN_MILLIS;
25547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long end = now + searchDuration + DateUtils.DAY_IN_MILLIS;
256bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
25747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI,
25847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    String.format("%d/%d", begin, end));
259bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
26047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Cursor cursor = resolver.query(uri, EVENT_PROJECTION,
26147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    EVENT_SELECTION, null, EVENT_SORT_ORDER);
262bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
26347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Start managing the cursor ourselves
26447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
26547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            cursor.close();
266e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang
26747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return matrixCursor;
268bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
269bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
27047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @VisibleForTesting
27147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected static CalendarAppWidgetModel buildAppWidgetModel(
2723ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                Context context, Cursor cursor) {
2733ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            CalendarAppWidgetModel model = new CalendarAppWidgetModel(context);
2743ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            model.buildFromCursor(cursor);
27547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return model;
276bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
277bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
27847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
27947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Figure out the next time we should push widget updates, usually the time
28047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * calculated by {@link #getEventFlip(Cursor, long, long, boolean)}.
28147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
28247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param cursor Valid cursor on {@link Instances#CONTENT_URI}
28347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param events {@link MarkedEvents} parsed from the cursor
28447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
2853ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        private long calculateUpdateTime(CalendarAppWidgetModel model) {
28647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long result = -1;
2873ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (!model.mEventInfos.isEmpty()) {
2883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                EventInfo firstEvent = model.mEventInfos.get(0);
2893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                long start = firstEvent.start;
2903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                long end = firstEvent.end;
2913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                boolean allDay = firstEvent.allDay;
29247d40324272ae39af0872bf5cbf27e1800478021Mason Tang
29347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Adjust all-day times into local timezone
29447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (allDay) {
29547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    final Time recycle = new Time();
2963ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    start = Utils.convertUtcToLocal(recycle, start);
2973ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    end = Utils.convertUtcToLocal(recycle, end);
298bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                }
299bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
3003ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                result = getEventFlip(start, end, allDay);
301bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
30247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Make sure an update happens at midnight or earlier
30347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long midnight = getNextMidnightTimeMillis();
30447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                result = Math.min(midnight, result);
305bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
30647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return result;
307bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
308bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
30947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static long getNextMidnightTimeMillis() {
31047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Time time = new Time();
31147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.setToNow();
31247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.monthDay++;
31347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.hour = 0;
31447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.minute = 0;
31547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.second = 0;
31647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long midnight = time.normalize(true);
31747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return midnight;
318bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
319bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
32047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static void updateTextView(RemoteViews views, int id, int visibility, String string) {
32147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            views.setViewVisibility(id, visibility);
32247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (visibility == View.VISIBLE) {
32347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                views.setTextViewText(id, string);
32447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
32547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
3263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
327bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
3283ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
3293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * Format given time for debugging output.
3303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *
3313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param unixTime Target time to report.
3323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param now Current system time from {@link System#currentTimeMillis()}
3333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *            for calculating time difference.
3343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     */
3353ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static String formatDebugTime(long unixTime, long now) {
3363ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Time time = new Time();
3373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        time.set(unixTime);
3383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        long delta = unixTime - now;
3403ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        if (delta > DateUtils.MINUTE_IN_MILLIS) {
3413ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            delta /= DateUtils.MINUTE_IN_MILLIS;
3423ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return String.format("[%d] %s (%+d mins)", unixTime,
3433ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    time.format("%H:%M:%S"), delta);
3443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        } else {
3453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            delta /= DateUtils.SECOND_IN_MILLIS;
3463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return String.format("[%d] %s (%+d secs)", unixTime,
3473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    time.format("%H:%M:%S"), delta);
348bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
349bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
3503ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
3523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * Calculate flipping point for the given event; when we should hide this
3533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * event and show the next one. This is defined as the end time of the
3543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * event.
3553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *
3563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param start Event start time in local timezone.
3573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param end Event end time in local timezone.
3583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param allDay whether or not the event is all-day
3593ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     */
3603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static long getEventFlip(long start, long end, boolean allDay) {
3613ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        return end;
3623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
363bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang}
364