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
19a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErikimport android.app.AlarmManager;
20a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErikimport android.app.PendingIntent;
21c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.appwidget.AppWidgetManager;
22c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.BroadcastReceiver;
23bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context;
24c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.CursorLoader;
25bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent;
26c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.Loader;
27a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawaimport android.content.res.Resources;
28bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor;
2947d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.MatrixCursor;
30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri;
31c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.os.Handler;
32a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Attendees;
33a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Calendars;
34a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Instances;
35bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils;
36bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time;
37bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log;
38bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View;
39bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews;
4047d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.widget.RemoteViewsService;
4147d40324272ae39af0872bf5cbf27e1800478021Mason Tang
429e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.R;
439e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.Utils;
449e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.widget.CalendarAppWidgetModel.DayInfo;
459e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo;
469e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport com.android.calendar.widget.CalendarAppWidgetModel.RowInfo;
479e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chan
489e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport java.util.concurrent.ExecutorService;
499e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport java.util.concurrent.Executors;
509e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chanimport java.util.concurrent.atomic.AtomicInteger;
519e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chan
52bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
5347d40324272ae39af0872bf5cbf27e1800478021Mason Tangpublic class CalendarAppWidgetService extends RemoteViewsService {
540c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    private static final String TAG = "CalendarWidget";
55bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int EVENT_MIN_COUNT = 20;
579e196e46b238e6bca1ef86c7d673807b78ab7016Michael Chan    static final int EVENT_MAX_COUNT = 100;
58c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    // Minimum delay between queries on the database for widget updates in ms
59c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    static final int WIDGET_UPDATE_THROTTLE = 500;
6047d40324272ae39af0872bf5cbf27e1800478021Mason Tang
61bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, "
62bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, "
633ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT;
64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
65e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik    private static final String EVENT_SELECTION = Calendars.VISIBLE + "=1";
6642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik    private static final String EVENT_SELECTION_HIDE_DECLINED = Calendars.VISIBLE + "=1 AND "
67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final String[] EVENT_PROJECTION = new String[] {
70bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.ALL_DAY,
71bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.BEGIN,
72bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.END,
73bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.TITLE,
74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.EVENT_LOCATION,
75bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.EVENT_ID,
763ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Instances.START_DAY,
77a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        Instances.END_DAY,
78693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan        Instances.DISPLAY_COLOR, // If SDK < 16, set to Instances.CALENDAR_COLOR.
7942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        Instances.SELF_ATTENDEE_STATUS,
80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    };
81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
82bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_ALL_DAY = 0;
83bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_BEGIN = 1;
84bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_END = 2;
85bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_TITLE = 3;
86bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_EVENT_LOCATION = 4;
87bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_EVENT_ID = 5;
883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int INDEX_START_DAY = 6;
893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int INDEX_END_DAY = 7;
90a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa    static final int INDEX_COLOR = 8;
9142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik    static final int INDEX_SELF_ATTENDEE_STATUS = 9;
92bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
93693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan    static {
94fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting        if (!Utils.isJellybeanOrLater()) {
95693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan            EVENT_PROJECTION[INDEX_COLOR] = Instances.CALENDAR_COLOR;
96693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan        }
97693ca60d79650af15ab226f7ce6958f61fb7de39Michael Chan    }
983ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int MAX_DAYS = 7;
993ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
1003ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS;
101bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
1020c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    /**
1030c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa     * Update interval used when no next-update calculated, or bad trigger time in past.
1040c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa     * Unit: milliseconds.
1050c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa     */
1060c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    private static final long UPDATE_TIME_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6;
107bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
10847d40324272ae39af0872bf5cbf27e1800478021Mason Tang    @Override
10947d40324272ae39af0872bf5cbf27e1800478021Mason Tang    public RemoteViewsFactory onGetViewFactory(Intent intent) {
11047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        return new CalendarFactory(getApplicationContext(), intent);
111bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
112bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
113f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik    public static class CalendarFactory extends BroadcastReceiver implements
114c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            RemoteViewsService.RemoteViewsFactory, Loader.OnLoadCompleteListener<Cursor> {
115f9df037f350fad73659307ba05f230d2db69051aMason Tang        private static final boolean LOGD = false;
1160c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
1170c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        // Suppress unnecessary logging about update time. Need to be static as this object is
1180c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        // re-instanciated frequently.
1190c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        // TODO: It seems loadData() is called via onCreate() four times, which should mean
1200c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        // unnecessary CalendarFactory object is created and dropped. It is not efficient.
1210c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        private static long sLastUpdateTime = UPDATE_TIME_NO_EVENTS;
1220c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
12347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Context mContext;
124a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa        private Resources mResources;
125f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        private static CalendarAppWidgetModel mModel;
126f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan        private static Object mLock = new Object();
127f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan        private static volatile int mSerialNum = 0;
128f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan        private int mLastSerialNum = -1;
129c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private CursorLoader mLoader;
1300ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelson        private final Handler mHandler = new Handler();
1316e32890eade48d4edd70edcf29a25918b439f4aaSara Ting        private static final AtomicInteger currentVersion = new AtomicInteger(0);
1320ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelson        private final ExecutorService executor = Executors.newSingleThreadExecutor();
133c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private int mAppWidgetId;
13442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        private int mDeclinedColor;
13542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        private int mStandardColor;
136e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik        private int mAllDayColor;
137c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
1380ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelson        private final Runnable mTimezoneChanged = new Runnable() {
139c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            @Override
140c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            public void run() {
141c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                if (mLoader != null) {
142c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    mLoader.forceLoad();
143c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                }
144c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
145c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        };
146c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
1476e32890eade48d4edd70edcf29a25918b439f4aaSara Ting        private Runnable createUpdateLoaderRunnable(final String selection,
1486e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                final PendingResult result, final int version) {
1496e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            return new Runnable() {
1506e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                @Override
1516e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                public void run() {
1526e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    // If there is a newer load request in the queue, skip loading.
1536e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    if (mLoader != null && version >= currentVersion.get()) {
1546e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        Uri uri = createLoaderUri();
1556e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        mLoader.setUri(uri);
1566e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        mLoader.setSelection(selection);
1576e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        synchronized (mLock) {
158f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan                            mLastSerialNum = ++mSerialNum;
1596e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        }
1606e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        mLoader.forceLoad();
161f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    }
1626e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    result.finish();
163c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                }
1646e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            };
1656e32890eade48d4edd70edcf29a25918b439f4aaSara Ting        }
16647d40324272ae39af0872bf5cbf27e1800478021Mason Tang
16747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected CalendarFactory(Context context, Intent intent) {
16847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mContext = context;
169a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa            mResources = context.getResources();
170c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mAppWidgetId = intent.getIntExtra(
171c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
17242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik
17395d6fe0b672284a19699edc4727c366e3b0ccec7Peter Ng            mDeclinedColor = mResources.getColor(R.color.appwidget_item_declined_color);
17495d6fe0b672284a19699edc4727c366e3b0ccec7Peter Ng            mStandardColor = mResources.getColor(R.color.appwidget_item_standard_color);
175e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik            mAllDayColor = mResources.getColor(R.color.appwidget_item_allday_color);
176bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
177bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
178f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        public CalendarFactory() {
179f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            // This is being created as part of onReceive
180f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
181f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        }
182f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
18347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
18447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onCreate() {
1856e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            String selection = queryForSelection();
1866e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            initLoader(selection);
187bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
188bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
18947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
190d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        public void onDataSetChanged() {
191d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        }
192d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung
193d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        @Override
19447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onDestroy() {
195673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson            if (mLoader != null) {
196673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson                mLoader.reset();
197673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson            }
198bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
199bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
20047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
20147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getLoadingView() {
20247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            RemoteViews views = new RemoteViews(mContext.getPackageName(),
20347d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    R.layout.appwidget_loading);
20447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return views;
205bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
206bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
20747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
20847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getViewAt(int position) {
20947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // we use getCount here so that it doesn't return null when empty
21047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (position < 0 || position >= getCount()) {
21147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return null;
21247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
213bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
214254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik            if (mModel == null) {
215254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik                RemoteViews views = new RemoteViews(mContext.getPackageName(),
216254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik                        R.layout.appwidget_loading);
21707d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan                final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0,
2186bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson                        0, 0, false);
219254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik                views.setOnClickFillInIntent(R.id.appwidget_loading, intent);
220254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik                return views;
221254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik
222254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik            }
223254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik            if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) {
2243ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
2253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        R.layout.appwidget_no_events);
22607d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan                final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0,
2276bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson                        0, 0, false);
2284143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                views.setOnClickFillInIntent(R.id.appwidget_no_events, intent);
2293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return views;
2303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
2313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            RowInfo rowInfo = mModel.mRowInfos.get(position);
2333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (rowInfo.mType == RowInfo.TYPE_DAY) {
2343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
2353ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        R.layout.appwidget_day);
2363ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex);
2373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel);
2383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return views;
2393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            } else {
24042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                RemoteViews views;
241a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                final EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex);
24242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                if (eventInfo.allDay) {
24342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views = new RemoteViews(mContext.getPackageName(),
24442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                            R.layout.widget_all_day_item);
24542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                } else {
24642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
24742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                }
2484acb2fd087308dea146b8b10f5278c59df387680RoboErik                int displayColor = Utils.getDisplayColorFromColor(eventInfo.color);
249a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa
250a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa                final long now = System.currentTimeMillis();
251a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa                if (!eventInfo.allDay && eventInfo.start <= now && now <= eventInfo.end) {
25231412a0fea756e0da0bcbdf3cdffe4efae21cdbeIsaac Katzenelson                    views.setInt(R.id.widget_row, "setBackgroundResource",
25331412a0fea756e0da0bcbdf3cdffe4efae21cdbeIsaac Katzenelson                            R.drawable.agenda_item_bg_secondary);
254ffaeace621183dfe8770471a30b2f1138aac5f86Daisuke Miyakawa                } else {
25531412a0fea756e0da0bcbdf3cdffe4efae21cdbeIsaac Katzenelson                    views.setInt(R.id.widget_row, "setBackgroundResource",
25631412a0fea756e0da0bcbdf3cdffe4efae21cdbeIsaac Katzenelson                            R.drawable.agenda_item_bg_primary);
257a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa                }
258a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa
259e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                if (!eventInfo.allDay) {
260e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                    updateTextView(views, R.id.when, eventInfo.visibWhen, eventInfo.when);
261e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                    updateTextView(views, R.id.where, eventInfo.visibWhere, eventInfo.where);
262e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                }
263a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                updateTextView(views, R.id.title, eventInfo.visibTitle, eventInfo.title);
264bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
26542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                views.setViewVisibility(R.id.agenda_item_color, View.VISIBLE);
26642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik
26742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                int selfAttendeeStatus = eventInfo.selfAttendeeStatus;
268e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                if (eventInfo.allDay) {
269e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
270e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                        views.setInt(R.id.agenda_item_color, "setImageResource",
271e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                                R.drawable.widget_chip_not_responded_bg);
272e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                        views.setInt(R.id.title, "setTextColor", displayColor);
273e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                    } else {
274e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                        views.setInt(R.id.agenda_item_color, "setImageResource",
275e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                                R.drawable.widget_chip_responded_bg);
276e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                        views.setInt(R.id.title, "setTextColor", mAllDayColor);
277e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                    }
278e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
279e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                        // 40% opacity
280e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                        views.setInt(R.id.agenda_item_color, "setColorFilter",
281e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                                Utils.getDeclinedColorFromColor(displayColor));
282e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                    } else {
283e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                        views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor);
284e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                    }
285e8f077e1c047729809b1ceb4bddce806de8ab003RoboErik                } else if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
28642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.title, "setTextColor", mDeclinedColor);
28742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.when, "setTextColor", mDeclinedColor);
28842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.where, "setTextColor", mDeclinedColor);
28942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    // views.setInt(R.id.agenda_item_color, "setDrawStyle",
29042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    // ColorChipView.DRAW_CROSS_HATCHED);
29142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.agenda_item_color, "setImageResource",
292aeae98bf8f8c5c544f65f5b6b7f29cfd31366ad5Michael Chan                            R.drawable.widget_chip_responded_bg);
29342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    // 40% opacity
29442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.agenda_item_color, "setColorFilter",
295210642aa1f8bec18f7275cddb9a3b50fad3f4218RoboErik                            Utils.getDeclinedColorFromColor(displayColor));
29642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                } else {
29742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.title, "setTextColor", mStandardColor);
29842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.when, "setTextColor", mStandardColor);
29942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.where, "setTextColor", mStandardColor);
30042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
30142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                        views.setInt(R.id.agenda_item_color, "setImageResource",
30242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                                R.drawable.widget_chip_not_responded_bg);
30342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    } else {
30442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                        views.setInt(R.id.agenda_item_color, "setImageResource",
30542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                                R.drawable.widget_chip_responded_bg);
30642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    }
3074acb2fd087308dea146b8b10f5278c59df387680RoboErik                    views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor);
30842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                }
309bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
3109da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                long start = eventInfo.start;
3119da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                long end = eventInfo.end;
3124143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                // An element in ListView.
3139da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                if (eventInfo.allDay) {
3149da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                    String tz = Utils.getTimeZone(mContext, null);
3159da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                    Time recycle = new Time();
3169da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                    start = Utils.convertAlldayLocalToUTC(recycle, start, tz);
3179da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                    end = Utils.convertAlldayLocalToUTC(recycle, end, tz);
3189da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                }
319bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan                final Intent fillInIntent = CalendarAppWidgetProvider.getLaunchFillInIntent(
3206bcafcf4f28c2d1053547694bd60b3dd9c7fbaa1Isaac Katzenelson                        mContext, eventInfo.id, start, end, eventInfo.allDay);
32142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                views.setOnClickFillInIntent(R.id.widget_row, fillInIntent);
32247d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return views;
32347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
324bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
325bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
32647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
32747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getViewTypeCount() {
328da6bd6f971f74bf9b434ba84aaa092a2a1a76511Kevin Barry            return 5;
32947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
330bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
33147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
33247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getCount() {
33347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // if there are no events, we still return 1 to represent the "no
33447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // events" view
3354299eb1c1d81042de46a8d3972fde8719820d06cRoboErik            if (mModel == null) {
3364299eb1c1d81042de46a8d3972fde8719820d06cRoboErik                return 1;
3374299eb1c1d81042de46a8d3972fde8719820d06cRoboErik            }
3383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return Math.max(1, mModel.mRowInfos.size());
33947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
340bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
34147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
34247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public long getItemId(int position) {
3439de6fc8bc215d95ff236855b5be97acbc50d31e5Sara Ting            if (mModel == null ||  mModel.mRowInfos.isEmpty() || position >= getCount()) {
344713011baae993d4ca07a8267e3a0f9945753c2ffRoboErik                return 0;
345713011baae993d4ca07a8267e3a0f9945753c2ffRoboErik            }
346c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            RowInfo rowInfo = mModel.mRowInfos.get(position);
347c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (rowInfo.mType == RowInfo.TYPE_DAY) {
348c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                return rowInfo.mIndex;
349c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
350c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex);
351c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            long prime = 31;
352c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            long result = 1;
353c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            result = prime * result + (int) (eventInfo.id ^ (eventInfo.id >>> 32));
354c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            result = prime * result + (int) (eventInfo.start ^ (eventInfo.start >>> 32));
355c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            return result;
35647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
357bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
35847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
35947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public boolean hasStableIds() {
36047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return true;
361bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
362bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
36347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
364c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * Query across all calendars for upcoming event instances from now
365c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * until some time in the future. Widen the time range that we query by
366c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * one day on each end so that we can catch all-day events. All-day
367c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * events are stored starting at midnight in UTC but should be included
368c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * in the list of events starting at midnight local time. This may fetch
369c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * more events than we actually want, so we filter them out later.
37047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
3716e32890eade48d4edd70edcf29a25918b439f4aaSara Ting         * @param selection The selection string for the loader to filter the query with.
37247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
3736e32890eade48d4edd70edcf29a25918b439f4aaSara Ting        public void initLoader(String selection) {
374c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (LOGD)
375c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                Log.d(TAG, "Querying for widget events...");
376c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
37747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Search for events from now until some time in the future
378c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            Uri uri = createLoaderUri();
37942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            mLoader = new CursorLoader(mContext, uri, EVENT_PROJECTION, selection, null,
38042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    EVENT_SORT_ORDER);
381c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mLoader.setUpdateThrottle(WIDGET_UPDATE_THROTTLE);
382f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            synchronized (mLock) {
383f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan                mLastSerialNum = ++mSerialNum;
384f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            }
385c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mLoader.registerListener(mAppWidgetId, this);
386e3e1f555a89a2b4cb859ddae189f57aeb749a852RoboErik            mLoader.startLoading();
387c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
388c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
389bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
390c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        /**
3916e32890eade48d4edd70edcf29a25918b439f4aaSara Ting         * This gets the selection string for the loader.  This ends up doing a query in the
3926e32890eade48d4edd70edcf29a25918b439f4aaSara Ting         * shared preferences.
3936e32890eade48d4edd70edcf29a25918b439f4aaSara Ting         */
3946e32890eade48d4edd70edcf29a25918b439f4aaSara Ting        private String queryForSelection() {
3956e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            return Utils.getHideDeclinedEvents(mContext) ? EVENT_SELECTION_HIDE_DECLINED
3966e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    : EVENT_SELECTION;
3976e32890eade48d4edd70edcf29a25918b439f4aaSara Ting        }
3986e32890eade48d4edd70edcf29a25918b439f4aaSara Ting
3996e32890eade48d4edd70edcf29a25918b439f4aaSara Ting        /**
400c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * @return The uri for the loader
401c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         */
402c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private Uri createLoaderUri() {
403c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            long now = System.currentTimeMillis();
40447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Add a day on either side to catch all-day events
40547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long begin = now - DateUtils.DAY_IN_MILLIS;
406c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            long end = now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS;
407bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
408e0cb5ba884c52e9d36875fb4a9ebdf40a81cb642Michael Chan            Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, Long.toString(begin) + "/" + end);
409c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            return uri;
41021a183875fbbfa54f5a2a87779888a5fb7d1af44Erik        }
41121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik
4123f888688c0f2644ad3de032d5d1cf623a7b092fdAndy McFadden        /* @VisibleForTesting */
41347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected static CalendarAppWidgetModel buildAppWidgetModel(
41421a183875fbbfa54f5a2a87779888a5fb7d1af44Erik                Context context, Cursor cursor, String timeZone) {
415ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            CalendarAppWidgetModel model = new CalendarAppWidgetModel(context, timeZone);
41621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik            model.buildFromCursor(cursor, timeZone);
41747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return model;
418bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
419bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
42047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
4210c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa         * Calculates and returns the next time we should push widget updates.
42247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
423ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan        private long calculateUpdateTime(CalendarAppWidgetModel model, long now, String timeZone) {
4240c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            // Make sure an update happens at midnight or earlier
425ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            long minUpdateTime = getNextMidnightTimeMillis(timeZone);
4260c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            for (EventInfo event : model.mEventInfos) {
4270c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                final long start;
4280c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                final long end;
4299da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                start = event.start;
4309da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                end = event.end;
431bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
4320c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                // We want to update widget when we enter/exit time range of an event.
4330c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                if (now < start) {
4340c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                    minUpdateTime = Math.min(minUpdateTime, start);
4350c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                } else if (now < end) {
4360c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                    minUpdateTime = Math.min(minUpdateTime, end);
4370c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                }
438bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
4390c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            return minUpdateTime;
440bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
441bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
442ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan        private static long getNextMidnightTimeMillis(String timezone) {
44347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Time time = new Time();
44447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.setToNow();
44547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.monthDay++;
44647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.hour = 0;
44747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.minute = 0;
44847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.second = 0;
449ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            long midnightDeviceTz = time.normalize(true);
450ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan
451ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.timezone = timezone;
452ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.setToNow();
453ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.monthDay++;
454ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.hour = 0;
455ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.minute = 0;
456ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.second = 0;
457ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            long midnightHomeTz = time.normalize(true);
458ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan
459ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            return Math.min(midnightDeviceTz, midnightHomeTz);
460bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
461bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
46247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static void updateTextView(RemoteViews views, int id, int visibility, String string) {
46347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            views.setViewVisibility(id, visibility);
46447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (visibility == View.VISIBLE) {
46547d40324272ae39af0872bf5cbf27e1800478021Mason Tang                views.setTextViewText(id, string);
46647d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
46747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
468c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
469c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        /*
470c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * (non-Javadoc)
471c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * @see
472c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * android.content.Loader.OnLoadCompleteListener#onLoadComplete(android
473c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * .content.Loader, java.lang.Object)
474c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         */
475c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        @Override
476c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
477c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan            if (cursor == null) {
47882a8afab75ee998fcc90a4bcbc62f4912bc582adRoboErik                return;
47982a8afab75ee998fcc90a4bcbc62f4912bc582adRoboErik            }
480f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            // If a newer update has happened since we started clean up and
481f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            // return
482f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            synchronized (mLock) {
483c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan                if (cursor.isClosed()) {
484c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan                    Log.wtf(TAG, "Got a closed cursor from onLoadComplete");
485c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan                    return;
486c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan                }
487c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan
488f6bc9bcb4626a4c8c902c1a8c7de167ed1e48e16Michael Chan                if (mLastSerialNum != mSerialNum) {
489f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    return;
490f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                }
491c948d1a76bd3b1300b1063d3e93a95f440d84528Michael Chan
492c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                final long now = System.currentTimeMillis();
493c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                String tz = Utils.getTimeZone(mContext, mTimezoneChanged);
494c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting
495f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // Copy it to a local static cursor.
496f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
497c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                try {
498c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                    mModel = buildAppWidgetModel(mContext, matrixCursor, tz);
499c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                } finally {
500c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                    if (matrixCursor != null) {
501c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                        matrixCursor.close();
502c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                    }
503c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
504c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                    if (cursor != null) {
505c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                        cursor.close();
506c7d02ca7a6f4a5abbd5abb19690e65ce9cebe2dcSara Ting                    }
507f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                }
508f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
509f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // Schedule an alarm to wake ourselves up for the next update.
510f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // We also cancel
511f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // all existing wake-ups because PendingIntents don't match
512f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // against extras.
513f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                long triggerTime = calculateUpdateTime(mModel, now, tz);
514f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
515f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // If no next-update calculated, or bad trigger time in past,
516f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // schedule
517f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // update about six hours from now.
518f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                if (triggerTime < now) {
519f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    Log.w(TAG, "Encountered bad trigger time " + formatDebugTime(triggerTime, now));
520f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    triggerTime = now + UPDATE_TIME_NO_EVENTS;
521f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                }
522c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
523f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                final AlarmManager alertManager = (AlarmManager) mContext
524f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        .getSystemService(Context.ALARM_SERVICE);
525f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                final PendingIntent pendingUpdate = CalendarAppWidgetProvider
526f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        .getUpdateIntent(mContext);
527f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
528f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                alertManager.cancel(pendingUpdate);
529f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                alertManager.set(AlarmManager.RTC, triggerTime, pendingUpdate);
530f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                Time time = new Time(Utils.getTimeZone(mContext, null));
531f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                time.setToNow();
532f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
533f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                if (time.normalize(true) != sLastUpdateTime) {
534f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    Time time2 = new Time(Utils.getTimeZone(mContext, null));
535f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    time2.set(sLastUpdateTime);
536f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    time2.normalize(true);
537f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    if (time.year != time2.year || time.yearDay != time2.yearDay) {
538f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        final Intent updateIntent = new Intent(
539f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                                Utils.getWidgetUpdateAction(mContext));
540f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        mContext.sendBroadcast(updateIntent);
541f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    }
542a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik
543f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    sLastUpdateTime = time.toMillis(true);
544f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                }
545a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik
546f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
547f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                if (mAppWidgetId == -1) {
548f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    int[] ids = widgetManager.getAppWidgetIds(CalendarAppWidgetProvider
549f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                            .getComponentName(mContext));
550a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik
551f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.events_list);
552f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                } else {
553f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    widgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId, R.id.events_list);
554a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik                }
555a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik            }
556c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
557c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
558c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        @Override
559c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        public void onReceive(Context context, Intent intent) {
560f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            if (LOGD)
561f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                Log.d(TAG, "AppWidgetService received an intent. It was " + intent.toString());
562f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            mContext = context;
5636e32890eade48d4edd70edcf29a25918b439f4aaSara Ting
5646e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            // We cannot do any queries from the UI thread, so push the 'selection' query
5656e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            // to a background thread.  However the implementation of the latter query
5666e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            // (cursor loading) uses CursorLoader which must be initiated from the UI thread,
5676e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            // so there is some convoluted handshaking here.
5686e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            //
5696e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            // Note that as currently implemented, this must run in a single threaded executor
5706e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            // or else the loads may be run out of order.
571b91a6c39d91d3af86ab7462a498542b3a3e88336Sara Ting            //
572b91a6c39d91d3af86ab7462a498542b3a3e88336Sara Ting            // TODO: Remove use of mHandler and CursorLoader, and do all the work synchronously
573b91a6c39d91d3af86ab7462a498542b3a3e88336Sara Ting            // in the background thread.  All the handshaking going on here between the UI and
574b91a6c39d91d3af86ab7462a498542b3a3e88336Sara Ting            // background thread with using goAsync, mHandler, and CursorLoader is confusing.
5756e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            final PendingResult result = goAsync();
5766e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            executor.submit(new Runnable() {
5776e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                @Override
5786e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                public void run() {
5796e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    // We always complete queryForSelection() even if the load task ends up being
5806e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    // canceled because of a more recent one.  Optimizing this to allow
5816e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    // canceling would require keeping track of all the PendingResults
5826e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    // (from goAsync) to abort them.  Defer this until it becomes a problem.
5836e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    final String selection = queryForSelection();
5846e32890eade48d4edd70edcf29a25918b439f4aaSara Ting
5856e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    if (mLoader == null) {
5866e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        mAppWidgetId = -1;
5876e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        mHandler.post(new Runnable() {
5886e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                            @Override
5896e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                            public void run() {
5906e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                                initLoader(selection);
5916e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                                result.finish();
5926e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                            }
5936e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        });
5946e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    } else {
5956e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                        mHandler.post(createUpdateLoaderRunnable(selection, result,
5966e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                                currentVersion.incrementAndGet()));
5976e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                    }
5986e32890eade48d4edd70edcf29a25918b439f4aaSara Ting                }
5996e32890eade48d4edd70edcf29a25918b439f4aaSara Ting            });
600c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
6013ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
602bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
6033ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
6043ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * Format given time for debugging output.
6053ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *
6063ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param unixTime Target time to report.
6073ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param now Current system time from {@link System#currentTimeMillis()}
6083ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *            for calculating time difference.
6093ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     */
6103ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static String formatDebugTime(long unixTime, long now) {
6113ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Time time = new Time();
6123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        time.set(unixTime);
6133ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
6143ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        long delta = unixTime - now;
6153ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        if (delta > DateUtils.MINUTE_IN_MILLIS) {
6163ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            delta /= DateUtils.MINUTE_IN_MILLIS;
6173ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return String.format("[%d] %s (%+d mins)", unixTime,
6183ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    time.format("%H:%M:%S"), delta);
6193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        } else {
6203ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            delta /= DateUtils.SECOND_IN_MILLIS;
6213ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return String.format("[%d] %s (%+d secs)", unixTime,
6223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    time.format("%H:%M:%S"), delta);
623bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
624bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
625bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang}
626