CalendarAppWidgetService.java revision d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5
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
195d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        public void onDataSetChanged() {
196d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        }
197d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung
198d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        @Override
19947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onDestroy() {
20047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCursor.close();
20147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mContext.unregisterReceiver(mIntentReceiver);
20247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mContext.getContentResolver().unregisterContentObserver(mContentObserver);
203bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
204bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
205bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
20647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
20747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getLoadingView() {
20847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            RemoteViews views = new RemoteViews(mContext.getPackageName(),
20947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    R.layout.appwidget_loading);
21047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return views;
211bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
212bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
21347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
21447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getViewAt(int position) {
21547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // we use getCount here so that it doesn't return null when empty
21647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (position < 0 || position >= getCount()) {
21747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return null;
21847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
219bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
22047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (mModel.eventInfos.length > 0) {
22147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
22247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        R.layout.appwidget_row);
223bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
22447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                EventInfo e = mModel.eventInfos[position];
225bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
22647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.when, e.visibWhen, e.when);
22747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.where, e.visibWhere, e.where);
22847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                updateTextView(views, R.id.title, e.visibTitle, e.title);
229bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
23047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                PendingIntent launchIntent =
23147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    CalendarAppWidgetProvider.getLaunchPendingIntent(
23247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                            mContext, e.start);
23347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                views.setOnClickPendingIntent(R.id.appwidget_row, launchIntent);
23447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return views;
23547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            } else {
23647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
23747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        R.layout.appwidget_no_events);
23847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                PendingIntent launchIntent =
23947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    CalendarAppWidgetProvider.getLaunchPendingIntent(
24047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                            mContext, 0);
24147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                views.setOnClickPendingIntent(R.id.appwidget_no_events, launchIntent);
24247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return views;
24347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
244bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
245bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
24647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
24747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getViewTypeCount() {
24847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return 3;
24947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
250bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
25147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
25247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getCount() {
25347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // if there are no events, we still return 1 to represent the "no
25447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // events" view
25547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return Math.max(1, mModel.eventInfos.length);
25647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
257bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
25847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
25947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public long getItemId(int position) {
26047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return position;
26147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
262bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
26347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
26447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public boolean hasStableIds() {
26547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return true;
266bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
267bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
26847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private void loadData() {
26947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long now = System.currentTimeMillis();
27047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (LOGD) Log.d(TAG, "Querying for widget events...");
27147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (mCursor != null) {
27247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                mCursor.close();
27347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
274bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
27547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mCursor = getUpcomingInstancesCursor(
27647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    mContext.getContentResolver(), SEARCH_DURATION, now);
27747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            MarkedEvents markedEvents = buildMarkedEvents(mCursor, now);
27847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mModel = buildAppWidgetModel(mContext, mCursor, markedEvents, now);
27947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long triggerTime = calculateUpdateTime(mCursor, markedEvents);
28047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Schedule an alarm to wake ourselves up for the next update.  We also cancel
28147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // all existing wake-ups because PendingIntents don't match against extras.
28247d40324272ae39af0872bf5cbf27e1800478021Mason Tang
28347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // If no next-update calculated, or bad trigger time in past, schedule
28447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // update about six hours from now.
28547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (triggerTime == -1 || triggerTime < now) {
28647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (LOGD) Log.w(TAG, "Encountered bad trigger time " +
28747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        formatDebugTime(triggerTime, now));
28847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                triggerTime = now + UPDATE_NO_EVENTS;
28947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
290bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
29147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
29247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            PendingIntent pendingUpdate = CalendarAppWidgetProvider.getUpdateIntent(mContext);
293bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
29447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            am.cancel(pendingUpdate);
29547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            am.set(AlarmManager.RTC, triggerTime, pendingUpdate);
29647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (LOGD) Log.d(TAG, "Scheduled next update at " + formatDebugTime(triggerTime, now));
29747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
298bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
29947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
30047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Query across all calendars for upcoming event instances from now until
30147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * some time in the future.
30247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
30347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Widen the time range that we query by one day on each end so that we can
30447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * catch all-day events. All-day events are stored starting at midnight in
30547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * UTC but should be included in the list of events starting at midnight
30647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * local time. This may fetch more events than we actually want, so we
30747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * filter them out later.
30847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
30947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param resolver {@link ContentResolver} to use when querying
31047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link Instances#CONTENT_URI}.
31147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param searchDuration Distance into the future to look for event
31247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            instances, in milliseconds.
31347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param now Current system time to use for this update, possibly from
31447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link System#currentTimeMillis()}.
31547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
31647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Cursor getUpcomingInstancesCursor(ContentResolver resolver,
31747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long searchDuration, long now) {
31847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Search for events from now until some time in the future
319bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
32047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Add a day on either side to catch all-day events
32147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long begin = now - DateUtils.DAY_IN_MILLIS;
32247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long end = now + searchDuration + DateUtils.DAY_IN_MILLIS;
323bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
32447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI,
32547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    String.format("%d/%d", begin, end));
326bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
32747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Cursor cursor = resolver.query(uri, EVENT_PROJECTION,
32847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    EVENT_SELECTION, null, EVENT_SORT_ORDER);
329bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
33047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Start managing the cursor ourselves
33147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
33247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            cursor.close();
333e700c16ec2464cbba86f91f8f757ae59cbed34b0Mason Tang
33447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return matrixCursor;
335bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
336bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
33747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
33847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Walk the given instances cursor and build a list of marked events to be
33947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * used when updating the widget. This structure is also used to check if
34047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * updates are needed.
34147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
34247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param cursor Valid cursor across {@link Instances#CONTENT_URI}.
34347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param watchEventIds Specific events to watch for, setting
34447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link MarkedEvents#watchFound} if found during marking.
34547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param now Current system time to use for this update, possibly from
34647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link System#currentTimeMillis()}
34747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
34847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @VisibleForTesting
34947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected static MarkedEvents buildMarkedEvents(Cursor cursor, long now) {
35047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            MarkedEvents events = new MarkedEvents();
35147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            final Time recycle = new Time();
35247d40324272ae39af0872bf5cbf27e1800478021Mason Tang
35347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            cursor.moveToPosition(-1);
35447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            while (cursor.moveToNext()) {
35547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                int row = cursor.getPosition();
35647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long eventId = cursor.getLong(INDEX_EVENT_ID);
35747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long start = cursor.getLong(INDEX_BEGIN);
35847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long end = cursor.getLong(INDEX_END);
35947d40324272ae39af0872bf5cbf27e1800478021Mason Tang
36047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
36147d40324272ae39af0872bf5cbf27e1800478021Mason Tang
36247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (LOGD) {
36347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    Log.d(TAG, "Row #" + row + " allDay:" + allDay + " start:" + start
36447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                            + " end:" + end + " eventId:" + eventId);
36547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                }
366bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
36747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Adjust all-day times into local timezone
36847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (allDay) {
36947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    start = convertUtcToLocal(recycle, start);
37047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    end = convertUtcToLocal(recycle, end);
37147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                }
372bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
37347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (end < now) {
37447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    // we might get some extra events when querying, in order to
37547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    // deal with all-day events
37647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    continue;
37747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                }
378bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
37947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                boolean inProgress = now < end && now > start;
380bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
38147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Skip events that have already passed their flip times
38247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long eventFlip = getEventFlip(cursor, start, end, allDay);
38347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (LOGD) Log.d(TAG, "Calculated flip time " + formatDebugTime(eventFlip, now));
38447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (eventFlip < now) {
38547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    continue;
38647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                }
38747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
38847d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                /* Scan through the events with the following logic:
38947d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *   Rule #1 Show A) all the events that are in progress including
39047d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *     all day events and B) the next upcoming event and any events
39147d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *     with the same start time.
39247d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *
39347d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *   Rule #2 If there are no events in progress, show A) the next
39447d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *     upcoming event and B) any events with the same start time.
39547d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *
39647d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *   Rule #3 If no events start at the same time at A in rule 2,
39747d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *     show A) the next upcoming event and B) the following upcoming
39847d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 *     event + any events with the same start time.
39947d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                 */
40047d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                if (inProgress) {
40147d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    // events for part A of Rule #1
40247d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    events.markedIds.add(row);
40347d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    events.inProgressCount++;
40447d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    if (events.firstTime == -1) {
40547d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.firstTime = start;
40647d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    }
40747d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                } else {
40847d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    if (events.primaryCount == 0) {
40947d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        // first upcoming event
41047d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.markedIds.add(row);
41147d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.primaryTime = start;
41247d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.primaryCount++;
41347d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        if (events.firstTime == -1) {
41447d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                            events.firstTime = start;
41547d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        }
41647d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    } else if (events.primaryTime == start) {
41747d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        // any events with same start time as first upcoming event
41847d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.markedIds.add(row);
41947d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.primaryCount++;
42047d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    } else if (events.markedIds.size() == 1) {
42147d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        // only one upcoming event, so we take the next upcoming
42247d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.markedIds.add(row);
42347d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.secondaryTime = start;
42447d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.secondaryCount++;
42547d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    } else if (events.secondaryCount > 0
42647d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                            && events.secondaryTime == start) {
42747d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        // any events with same start time as next upcoming
42847d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.markedIds.add(row);
42947d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        events.secondaryCount++;
43047d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    } else {
43147d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        // looks like we're done
43247d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                        break;
43347d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                    }
43447d40324272ae39af0872bf5cbf27e1800478021Mason Tang//                }
435bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
43647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                events.markedIds.add(row);
43747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
43847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return events;
439bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
440bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
44147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @VisibleForTesting
44247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected static CalendarAppWidgetModel buildAppWidgetModel(
44347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                Context context, Cursor cursor, MarkedEvents events, long currentTime) {
44447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            int eventCount = events.markedIds.size();
44547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            CalendarAppWidgetModel model = new CalendarAppWidgetModel(eventCount);
44647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Time time = new Time();
44747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.set(currentTime);
44847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.monthDay++;
44947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.hour = 0;
45047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.minute = 0;
45147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.second = 0;
45247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long startOfNextDay = time.normalize(true);
45347d40324272ae39af0872bf5cbf27e1800478021Mason Tang
45447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.set(currentTime);
45547d40324272ae39af0872bf5cbf27e1800478021Mason Tang
45647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Calendar header
45747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            String dayOfWeek = DateUtils.getDayOfWeekString(
45847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    time.weekDay + 1, DateUtils.LENGTH_MEDIUM).toUpperCase();
45947d40324272ae39af0872bf5cbf27e1800478021Mason Tang
46047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            model.dayOfWeek = dayOfWeek;
46147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            model.dayOfMonth = Integer.toString(time.monthDay);
46247d40324272ae39af0872bf5cbf27e1800478021Mason Tang
46347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            int i = 0;
46447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            for (Integer id : events.markedIds) {
46547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                populateEvent(context, cursor, id, model, time, i, true,
46647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        startOfNextDay, currentTime);
46747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                i++;
46847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
469bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
47047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return model;
471bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
472bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
47347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
47447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Figure out the next time we should push widget updates, usually the time
47547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * calculated by {@link #getEventFlip(Cursor, long, long, boolean)}.
47647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
47747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param cursor Valid cursor on {@link Instances#CONTENT_URI}
47847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param events {@link MarkedEvents} parsed from the cursor
47947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
48047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private long calculateUpdateTime(Cursor cursor, MarkedEvents events) {
48147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long result = -1;
48247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (!events.markedIds.isEmpty()) {
48347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                cursor.moveToPosition(events.markedIds.get(0));
48447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long start = cursor.getLong(INDEX_BEGIN);
48547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long end = cursor.getLong(INDEX_END);
48647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
48747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
48847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Adjust all-day times into local timezone
48947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (allDay) {
49047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    final Time recycle = new Time();
49147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    start = convertUtcToLocal(recycle, start);
49247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    end = convertUtcToLocal(recycle, end);
493bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                }
494bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
49547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                result = getEventFlip(cursor, start, end, allDay);
496bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
49747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Make sure an update happens at midnight or earlier
49847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                long midnight = getNextMidnightTimeMillis();
49947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                result = Math.min(midnight, result);
500bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
50147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return result;
502bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
503bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
50447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private static long getNextMidnightTimeMillis() {
50547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Time time = new Time();
50647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.setToNow();
50747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.monthDay++;
50847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.hour = 0;
50947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.minute = 0;
51047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.second = 0;
51147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long midnight = time.normalize(true);
51247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return midnight;
513bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
514bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
515bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        /**
51647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Format given time for debugging output.
51747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
51847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param unixTime Target time to report.
51947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param now Current system time from {@link System#currentTimeMillis()}
52047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            for calculating time difference.
521bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang         */
52247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static private String formatDebugTime(long unixTime, long now) {
52347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Time time = new Time();
52447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.set(unixTime);
52547d40324272ae39af0872bf5cbf27e1800478021Mason Tang
52647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long delta = unixTime - now;
52747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (delta > DateUtils.MINUTE_IN_MILLIS) {
52847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                delta /= DateUtils.MINUTE_IN_MILLIS;
52947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return String.format("[%d] %s (%+d mins)", unixTime,
53047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        time.format("%H:%M:%S"), delta);
53147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            } else {
53247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                delta /= DateUtils.SECOND_IN_MILLIS;
53347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return String.format("[%d] %s (%+d secs)", unixTime,
53447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        time.format("%H:%M:%S"), delta);
53547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
53647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
537bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
538bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        /**
53947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Convert given UTC time into current local time.
54047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
54147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param recycle Time object to recycle, otherwise null.
54247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param utcTime Time to convert, in UTC.
543bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang         */
54447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static private long convertUtcToLocal(Time recycle, long utcTime) {
54547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (recycle == null) {
54647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                recycle = new Time();
54747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
54847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            recycle.timezone = Time.TIMEZONE_UTC;
54947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            recycle.set(utcTime);
55047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            recycle.timezone = TimeZone.getDefault().getID();
55147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return recycle.normalize(true);
55247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
553bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
554bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        /**
55547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Calculate flipping point for the given event; when we should hide this
55647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * event and show the next one. This is defined as the end time of the
55747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * event.
55847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
55947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param start Event start time in local timezone.
56047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param end Event end time in local timezone.
561bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang         */
56247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static private long getEventFlip(Cursor cursor, long start, long end, boolean allDay) {
56347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return end;
56447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
565bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
56647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static void updateTextView(RemoteViews views, int id, int visibility, String string) {
56747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            views.setViewVisibility(id, visibility);
56847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (visibility == View.VISIBLE) {
56947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                views.setTextViewText(id, string);
57047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
57147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
572bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
573bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        /**
57447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * Pulls the information for a single event from the cursor and populates
57547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * the corresponding model object with the data.
57647d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
57747d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param context a Context to use for accessing resources
57847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param cursor the cursor to retrieve the data from
57947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param rowId the ID of the row to retrieve
58047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param model the model object to populate
58147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param recycle a Time instance to recycle
58247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param eventIndex which event index in the model to populate
58347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param showTitleLocation whether or not to show the title and location
58447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param startOfNextDay the beginning of the next day
58547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param currentTime the current time
586bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang         */
58747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static private void populateEvent(Context context, Cursor cursor, int rowId,
58847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                CalendarAppWidgetModel model, Time recycle, int eventIndex,
58947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                boolean showTitleLocation, long startOfNextDay, long currentTime) {
59047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            cursor.moveToPosition(rowId);
591bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
59247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // When
59347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
594bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            long start = cursor.getLong(INDEX_BEGIN);
595bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            long end = cursor.getLong(INDEX_END);
596bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            if (allDay) {
597bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                start = convertUtcToLocal(recycle, start);
598bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                end = convertUtcToLocal(recycle, end);
599bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
600bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
60147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            boolean eventIsInProgress = start <= currentTime && end > currentTime;
60247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            boolean eventIsToday = start < startOfNextDay;
60347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            boolean eventIsTomorrow = !eventIsToday && !eventIsInProgress
60447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    && (start < (startOfNextDay + DateUtils.DAY_IN_MILLIS));
60547d40324272ae39af0872bf5cbf27e1800478021Mason Tang
60647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Compute a human-readable string for the start time of the event
60747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            String whenString;
60847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (eventIsInProgress && allDay) {
60947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // All day events for the current day display as just "Today"
61047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                whenString = context.getString(R.string.today);
61147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            } else if (eventIsTomorrow && allDay) {
61247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // All day events for the next day display as just "Tomorrow"
61347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                whenString = context.getString(R.string.tomorrow);
61447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            } else {
61547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                int flags = DateUtils.FORMAT_ABBREV_ALL;
61647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (allDay) {
61747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    flags |= DateUtils.FORMAT_UTC;
61847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                } else {
61947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    flags |= DateUtils.FORMAT_SHOW_TIME;
62047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    if (DateFormat.is24HourFormat(context)) {
62147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                        flags |= DateUtils.FORMAT_24HOUR;
62247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    }
62347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                }
62447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Show day of the week if not today or tomorrow
62547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (!eventIsTomorrow && !eventIsToday) {
62647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
62747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                }
62847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                whenString = DateUtils.formatDateRange(context, start, start, flags);
62947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // TODO better i18n formatting
63047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (eventIsTomorrow) {
63147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    whenString += (", ");
63247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    whenString += context.getString(R.string.tomorrow);
63347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                } else if (eventIsInProgress) {
63447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    whenString += " (";
63547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    whenString += context.getString(R.string.in_progress);
63647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    whenString += ")";
63747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                }
638bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
639bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
64047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            model.eventInfos[eventIndex].start = start;
64147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            model.eventInfos[eventIndex].when = whenString;
64247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            model.eventInfos[eventIndex].visibWhen = View.VISIBLE;
643bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
64447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (showTitleLocation) {
64547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // What
64647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                String titleString = cursor.getString(INDEX_TITLE);
64747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (TextUtils.isEmpty(titleString)) {
64847d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    titleString = context.getString(R.string.no_title_label);
649bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                }
65047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                model.eventInfos[eventIndex].title = titleString;
65147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                model.eventInfos[eventIndex].visibTitle = View.VISIBLE;
65247d40324272ae39af0872bf5cbf27e1800478021Mason Tang
65347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                // Where
65447d40324272ae39af0872bf5cbf27e1800478021Mason Tang                String whereString = cursor.getString(INDEX_EVENT_LOCATION);
65547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (!TextUtils.isEmpty(whereString)) {
65647d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    model.eventInfos[eventIndex].visibWhere = View.VISIBLE;
65747d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    model.eventInfos[eventIndex].where = whereString;
658bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                } else {
65947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    model.eventInfos[eventIndex].visibWhere = View.GONE;
660bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                }
66147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                if (LOGD) Log.d(TAG, " Title:" + titleString + " Where:" + whereString);
662bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
663bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
664bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
665bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang}
666