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