CalendarAppWidgetService.java revision aeae98bf8f8c5c544f65f5b6b7f29cfd31366ad5
1bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang/*
2bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Copyright (C) 2009 The Android Open Source Project
3bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang *
4bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Licensed under the Apache License, Version 2.0 (the "License");
5bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * you may not use this file except in compliance with the License.
6bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * You may obtain a copy of the License at
7bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang *
8bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang *      http://www.apache.org/licenses/LICENSE-2.0
9bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang *
10bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Unless required by applicable law or agreed to in writing, software
11bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * distributed under the License is distributed on an "AS IS" BASIS,
12bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * See the License for the specific language governing permissions and
14bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * limitations under the License.
15bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */
16bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
179a3cb14e28536e4133dddbe952f47189fe344ec1Mason Tangpackage com.android.calendar.widget;
18bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
194143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.R;
204143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.Utils;
214143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.DayInfo;
224143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo;
234143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.RowInfo;
244143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa
25a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErikimport android.app.AlarmManager;
26a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErikimport android.app.PendingIntent;
27c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.appwidget.AppWidgetManager;
28c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.BroadcastReceiver;
29bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.ContentResolver;
30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context;
31c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.CursorLoader;
32bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent;
33c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.Loader;
34a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawaimport android.content.res.Resources;
35bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor;
3647d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.MatrixCursor;
37bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri;
38c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.os.Handler;
39a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Attendees;
40a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Calendars;
41a7c0390d9c5dd4ff730de505682687fae5f5ced0RoboErikimport android.provider.CalendarContract.Instances;
42bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils;
43bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time;
44bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log;
45bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View;
46bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews;
4747d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.widget.RemoteViewsService;
4847d40324272ae39af0872bf5cbf27e1800478021Mason Tang
49bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
5047d40324272ae39af0872bf5cbf27e1800478021Mason Tangpublic class CalendarAppWidgetService extends RemoteViewsService {
510c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    private static final String TAG = "CalendarWidget";
52bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int EVENT_MIN_COUNT = 20;
543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int EVENT_MAX_COUNT = 503;
55c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    // Minimum delay between queries on the database for widget updates in ms
56c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik    static final int WIDGET_UPDATE_THROTTLE = 500;
5747d40324272ae39af0872bf5cbf27e1800478021Mason Tang
58bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, "
59bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, "
603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT;
61bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
6242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik    private static final String EVENT_SELECTION = Calendars.VISIBLE + "=1";
6342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik    private static final String EVENT_SELECTION_HIDE_DECLINED = Calendars.VISIBLE + "=1 AND "
64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
65bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
66bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final String[] EVENT_PROJECTION = new String[] {
67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.ALL_DAY,
68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.BEGIN,
69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.END,
70bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.TITLE,
71bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.EVENT_LOCATION,
72bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        Instances.EVENT_ID,
733ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Instances.START_DAY,
74a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        Instances.END_DAY,
7542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        Instances.CALENDAR_COLOR,
7642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        Instances.SELF_ATTENDEE_STATUS,
77bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    };
78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_ALL_DAY = 0;
80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_BEGIN = 1;
81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_END = 2;
82bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_TITLE = 3;
83bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_EVENT_LOCATION = 4;
84bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    static final int INDEX_EVENT_ID = 5;
853ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int INDEX_START_DAY = 6;
863ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int INDEX_END_DAY = 7;
87a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa    static final int INDEX_COLOR = 8;
8842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik    static final int INDEX_SELF_ATTENDEE_STATUS = 9;
89bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static final int MAX_DAYS = 7;
913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
923ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS;
93bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
940c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    /**
950c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa     * Update interval used when no next-update calculated, or bad trigger time in past.
960c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa     * Unit: milliseconds.
970c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa     */
980c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa    private static final long UPDATE_TIME_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6;
99bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
10047d40324272ae39af0872bf5cbf27e1800478021Mason Tang    @Override
10147d40324272ae39af0872bf5cbf27e1800478021Mason Tang    public RemoteViewsFactory onGetViewFactory(Intent intent) {
10247d40324272ae39af0872bf5cbf27e1800478021Mason Tang        return new CalendarFactory(getApplicationContext(), intent);
103bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
104bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
105f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik    public static class CalendarFactory extends BroadcastReceiver implements
106c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            RemoteViewsService.RemoteViewsFactory, Loader.OnLoadCompleteListener<Cursor> {
107f9df037f350fad73659307ba05f230d2db69051aMason Tang        private static final boolean LOGD = false;
10842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        private static final int DECLINED_EVENT_ALPHA = 0x66000000;
1090c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
1100c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        // Suppress unnecessary logging about update time. Need to be static as this object is
1110c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        // re-instanciated frequently.
1120c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        // TODO: It seems loadData() is called via onCreate() four times, which should mean
1130c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        // unnecessary CalendarFactory object is created and dropped. It is not efficient.
1140c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa        private static long sLastUpdateTime = UPDATE_TIME_NO_EVENTS;
1150c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa
11647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        private Context mContext;
117a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa        private Resources mResources;
118f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        private static CalendarAppWidgetModel mModel;
119f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        private static Cursor mCursor;
120f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        private static volatile Integer mLock = new Integer(0);
121f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        private int mLastLock;
122c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private CursorLoader mLoader;
123c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private Handler mHandler = new Handler();
124c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private int mAppWidgetId;
12542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        private int mDeclinedColor;
12642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        private int mStandardColor;
127c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
128c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private Runnable mTimezoneChanged = new Runnable() {
129c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            @Override
130c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            public void run() {
131c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                if (mLoader != null) {
132c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    mLoader.forceLoad();
133c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                }
134c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
135c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        };
136c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
137c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private Runnable mUpdateLoader = new Runnable() {
138c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            @Override
139c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            public void run() {
140c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                if (mLoader != null) {
141c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    Uri uri = createLoaderUri();
142c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    mLoader.setUri(uri);
14342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    String selection = Utils.getHideDeclinedEvents(mContext) ?
14442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                            EVENT_SELECTION_HIDE_DECLINED : EVENT_SELECTION;
14542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    mLoader.setSelection(selection);
146f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    synchronized (mLock) {
147f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        mLastLock = ++mLock;
148f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    }
149c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    mLoader.forceLoad();
150c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                }
151c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
152c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        };
15347d40324272ae39af0872bf5cbf27e1800478021Mason Tang
15447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected CalendarFactory(Context context, Intent intent) {
15547d40324272ae39af0872bf5cbf27e1800478021Mason Tang            mContext = context;
156a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa            mResources = context.getResources();
157c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mAppWidgetId = intent.getIntExtra(
158c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                    AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
15942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik
16095d6fe0b672284a19699edc4727c366e3b0ccec7Peter Ng            mDeclinedColor = mResources.getColor(R.color.appwidget_item_declined_color);
16195d6fe0b672284a19699edc4727c366e3b0ccec7Peter Ng            mStandardColor = mResources.getColor(R.color.appwidget_item_standard_color);
162bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
163bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
164f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        public CalendarFactory() {
165f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            // This is being created as part of onReceive
166f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
167f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik        }
168f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
16947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
17047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onCreate() {
171c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            initLoader();
172bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
173bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
17447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
175d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        public void onDataSetChanged() {
176d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        }
177d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung
178d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung        @Override
17947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public void onDestroy() {
180673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson            if (mCursor != null) {
181673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson                mCursor.close();
182673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson            }
183673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson            if (mLoader != null) {
184673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson                mLoader.reset();
185673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson            }
186bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
187bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
18847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
18947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getLoadingView() {
19047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            RemoteViews views = new RemoteViews(mContext.getPackageName(),
19147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                    R.layout.appwidget_loading);
19247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return views;
193bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
194bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
19547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
19647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public RemoteViews getViewAt(int position) {
19747d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // we use getCount here so that it doesn't return null when empty
19847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (position < 0 || position >= getCount()) {
19947d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return null;
20047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
201bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
202254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik            if (mModel == null) {
203254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik                RemoteViews views = new RemoteViews(mContext.getPackageName(),
204254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik                        R.layout.appwidget_loading);
20507d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan                final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0,
20607d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan                        0, 0);
207254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik                views.setOnClickFillInIntent(R.id.appwidget_loading, intent);
208254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik                return views;
209254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik
210254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik            }
211254b13ee080e3ce69cabf7207a9bfb95c965a5daRoboErik            if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) {
2123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
2133ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        R.layout.appwidget_no_events);
21407d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan                final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(mContext, 0,
21507d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan                        0, 0);
2164143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                views.setOnClickFillInIntent(R.id.appwidget_no_events, intent);
2173ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return views;
2183ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
2193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2203ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            RowInfo rowInfo = mModel.mRowInfos.get(position);
2213ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (rowInfo.mType == RowInfo.TYPE_DAY) {
2223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RemoteViews views = new RemoteViews(mContext.getPackageName(),
2233ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        R.layout.appwidget_day);
2243ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex);
2253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel);
2263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return views;
2273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            } else {
22842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                RemoteViews views;
229a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                final EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex);
23042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                if (eventInfo.allDay) {
23142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views = new RemoteViews(mContext.getPackageName(),
23242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                            R.layout.widget_all_day_item);
23342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                } else {
23442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
23542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                }
2364acb2fd087308dea146b8b10f5278c59df387680RoboErik                int displayColor = Utils.getDisplayColorFromColor(eventInfo.color);
237a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa
238a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa                final long now = System.currentTimeMillis();
239a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa                if (!eventInfo.allDay && eventInfo.start <= now && now <= eventInfo.end) {
24042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.widget_row, "setBackgroundColor",
241a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa                            mResources.getColor(R.color.appwidget_row_in_progress));
242ffaeace621183dfe8770471a30b2f1138aac5f86Daisuke Miyakawa                } else {
24342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.widget_row, "setBackgroundColor", 0);
244a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa                }
245a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa
246a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                updateTextView(views, R.id.when, eventInfo.visibWhen, eventInfo.when);
247a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                updateTextView(views, R.id.where, eventInfo.visibWhere, eventInfo.where);
248a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                updateTextView(views, R.id.title, eventInfo.visibTitle, eventInfo.title);
249bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
25042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                views.setViewVisibility(R.id.agenda_item_color, View.VISIBLE);
25142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik
25242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                int selfAttendeeStatus = eventInfo.selfAttendeeStatus;
25342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED) {
25442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.title, "setTextColor", mDeclinedColor);
25542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.when, "setTextColor", mDeclinedColor);
25642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.where, "setTextColor", mDeclinedColor);
25742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    // views.setInt(R.id.agenda_item_color, "setDrawStyle",
25842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    // ColorChipView.DRAW_CROSS_HATCHED);
25942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.agenda_item_color, "setImageResource",
260aeae98bf8f8c5c544f65f5b6b7f29cfd31366ad5Michael Chan                            R.drawable.widget_chip_responded_bg);
26142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    // 40% opacity
26242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.agenda_item_color, "setColorFilter",
2634acb2fd087308dea146b8b10f5278c59df387680RoboErik                            (displayColor & 0x00FFFFFF) | DECLINED_EVENT_ALPHA);
26442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                } else {
26542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.title, "setTextColor", mStandardColor);
26642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.when, "setTextColor", mStandardColor);
26742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    views.setInt(R.id.where, "setTextColor", mStandardColor);
26842dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    if (selfAttendeeStatus == Attendees.ATTENDEE_STATUS_INVITED) {
26942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                        views.setInt(R.id.agenda_item_color, "setImageResource",
27042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                                R.drawable.widget_chip_not_responded_bg);
27142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    } else {
27242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                        views.setInt(R.id.agenda_item_color, "setImageResource",
27342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                                R.drawable.widget_chip_responded_bg);
27442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    }
2754acb2fd087308dea146b8b10f5278c59df387680RoboErik                    views.setInt(R.id.agenda_item_color, "setColorFilter", displayColor);
27642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                }
277bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
2789da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                long start = eventInfo.start;
2799da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                long end = eventInfo.end;
2804143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa                // An element in ListView.
2819da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                if (eventInfo.allDay) {
2829da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                    String tz = Utils.getTimeZone(mContext, null);
2839da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                    Time recycle = new Time();
2849da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                    start = Utils.convertAlldayLocalToUTC(recycle, start, tz);
2859da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                    end = Utils.convertAlldayLocalToUTC(recycle, end, tz);
2869da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                }
287bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan                final Intent fillInIntent = CalendarAppWidgetProvider.getLaunchFillInIntent(
28807d9fee87bc02849c0dd7b66326a320d53cf5479Michael Chan                        mContext, eventInfo.id, start, end);
28942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                views.setOnClickFillInIntent(R.id.widget_row, fillInIntent);
29047d40324272ae39af0872bf5cbf27e1800478021Mason Tang                return views;
29147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
292bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
293bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
29447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
29547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getViewTypeCount() {
2963ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return 4;
29747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
298bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
29947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
30047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public int getCount() {
30147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // if there are no events, we still return 1 to represent the "no
30247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // events" view
3034299eb1c1d81042de46a8d3972fde8719820d06cRoboErik            if (mModel == null) {
3044299eb1c1d81042de46a8d3972fde8719820d06cRoboErik                return 1;
3054299eb1c1d81042de46a8d3972fde8719820d06cRoboErik            }
3063ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return Math.max(1, mModel.mRowInfos.size());
30747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
308bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
30947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
31047d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public long getItemId(int position) {
311673304efa38aa2a3e65e0820e5c70d3a4889c34fIsaac Katzenelson            if (mModel == null ||  mModel.mRowInfos.isEmpty()) {
312713011baae993d4ca07a8267e3a0f9945753c2ffRoboErik                return 0;
313713011baae993d4ca07a8267e3a0f9945753c2ffRoboErik            }
314c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            RowInfo rowInfo = mModel.mRowInfos.get(position);
315c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (rowInfo.mType == RowInfo.TYPE_DAY) {
316c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                return rowInfo.mIndex;
317c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            }
318c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex);
319c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            long prime = 31;
320c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            long result = 1;
321c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            result = prime * result + (int) (eventInfo.id ^ (eventInfo.id >>> 32));
322c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            result = prime * result + (int) (eventInfo.start ^ (eventInfo.start >>> 32));
323c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            return result;
32447d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
325bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
32647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        @Override
32747d40324272ae39af0872bf5cbf27e1800478021Mason Tang        public boolean hasStableIds() {
32847d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return true;
329bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
330bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
33147d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
332c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * Query across all calendars for upcoming event instances from now
333c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * until some time in the future. Widen the time range that we query by
334c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * one day on each end so that we can catch all-day events. All-day
335c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * events are stored starting at midnight in UTC but should be included
336c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * in the list of events starting at midnight local time. This may fetch
337c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * more events than we actually want, so we filter them out later.
33847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *
33947d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param resolver {@link ContentResolver} to use when querying
34047d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link Instances#CONTENT_URI}.
34147d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param searchDuration Distance into the future to look for event
34247d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            instances, in milliseconds.
34347d40324272ae39af0872bf5cbf27e1800478021Mason Tang         * @param now Current system time to use for this update, possibly from
34447d40324272ae39af0872bf5cbf27e1800478021Mason Tang         *            {@link System#currentTimeMillis()}.
34547d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
346c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        public void initLoader() {
347c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            if (LOGD)
348c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik                Log.d(TAG, "Querying for widget events...");
349c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
35047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Search for events from now until some time in the future
351c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            Uri uri = createLoaderUri();
35242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            String selection = Utils.getHideDeclinedEvents(mContext) ? EVENT_SELECTION_HIDE_DECLINED
35342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    : EVENT_SELECTION;
35442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            mLoader = new CursorLoader(mContext, uri, EVENT_PROJECTION, selection, null,
35542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    EVENT_SORT_ORDER);
356c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mLoader.setUpdateThrottle(WIDGET_UPDATE_THROTTLE);
357f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            synchronized (mLock) {
358f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                mLastLock = ++mLock;
359f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            }
360c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mLoader.startLoading();
361c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            mLoader.registerListener(mAppWidgetId, this);
362c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
363c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
364bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
365c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        /**
366c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * @return The uri for the loader
367c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         */
368c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        private Uri createLoaderUri() {
369c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            long now = System.currentTimeMillis();
37047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            // Add a day on either side to catch all-day events
37147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            long begin = now - DateUtils.DAY_IN_MILLIS;
372c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            long end = now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS;
373bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
374e0cb5ba884c52e9d36875fb4a9ebdf40a81cb642Michael Chan            Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, Long.toString(begin) + "/" + end);
375c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik            return uri;
37621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik        }
37721a183875fbbfa54f5a2a87779888a5fb7d1af44Erik
3783f888688c0f2644ad3de032d5d1cf623a7b092fdAndy McFadden        /* @VisibleForTesting */
37947d40324272ae39af0872bf5cbf27e1800478021Mason Tang        protected static CalendarAppWidgetModel buildAppWidgetModel(
38021a183875fbbfa54f5a2a87779888a5fb7d1af44Erik                Context context, Cursor cursor, String timeZone) {
381ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            CalendarAppWidgetModel model = new CalendarAppWidgetModel(context, timeZone);
38221a183875fbbfa54f5a2a87779888a5fb7d1af44Erik            model.buildFromCursor(cursor, timeZone);
38347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            return model;
384bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
385bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
38647d40324272ae39af0872bf5cbf27e1800478021Mason Tang        /**
3870c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa         * Calculates and returns the next time we should push widget updates.
38847d40324272ae39af0872bf5cbf27e1800478021Mason Tang         */
389ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan        private long calculateUpdateTime(CalendarAppWidgetModel model, long now, String timeZone) {
3900c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            // Make sure an update happens at midnight or earlier
391ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            long minUpdateTime = getNextMidnightTimeMillis(timeZone);
3920c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            for (EventInfo event : model.mEventInfos) {
3930c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                final long start;
3940c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                final long end;
3959da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                start = event.start;
3969da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                end = event.end;
397bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
3980c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                // We want to update widget when we enter/exit time range of an event.
3990c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                if (now < start) {
4000c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                    minUpdateTime = Math.min(minUpdateTime, start);
4010c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                } else if (now < end) {
4020c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                    minUpdateTime = Math.min(minUpdateTime, end);
4030c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa                }
404bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            }
4050c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa            return minUpdateTime;
406bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
407bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
408ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan        private static long getNextMidnightTimeMillis(String timezone) {
40947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            Time time = new Time();
41047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.setToNow();
41147d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.monthDay++;
41247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.hour = 0;
41347d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.minute = 0;
41447d40324272ae39af0872bf5cbf27e1800478021Mason Tang            time.second = 0;
415ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            long midnightDeviceTz = time.normalize(true);
416ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan
417ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.timezone = timezone;
418ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.setToNow();
419ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.monthDay++;
420ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.hour = 0;
421ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.minute = 0;
422ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            time.second = 0;
423ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            long midnightHomeTz = time.normalize(true);
424ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan
425ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            return Math.min(midnightDeviceTz, midnightHomeTz);
426bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
427bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
42847d40324272ae39af0872bf5cbf27e1800478021Mason Tang        static void updateTextView(RemoteViews views, int id, int visibility, String string) {
42947d40324272ae39af0872bf5cbf27e1800478021Mason Tang            views.setViewVisibility(id, visibility);
43047d40324272ae39af0872bf5cbf27e1800478021Mason Tang            if (visibility == View.VISIBLE) {
43147d40324272ae39af0872bf5cbf27e1800478021Mason Tang                views.setTextViewText(id, string);
43247d40324272ae39af0872bf5cbf27e1800478021Mason Tang            }
43347d40324272ae39af0872bf5cbf27e1800478021Mason Tang        }
434c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
435c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        /*
436c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * (non-Javadoc)
437c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * @see
438c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * android.content.Loader.OnLoadCompleteListener#onLoadComplete(android
439c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         * .content.Loader, java.lang.Object)
440c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik         */
441c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        @Override
442c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
44382a8afab75ee998fcc90a4bcbc62f4912bc582adRoboErik            if (cursor == null) {
44482a8afab75ee998fcc90a4bcbc62f4912bc582adRoboErik                return;
44582a8afab75ee998fcc90a4bcbc62f4912bc582adRoboErik            }
446f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            // If a newer update has happened since we started clean up and
447f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            // return
448f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            synchronized (mLock) {
449f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                if (mLastLock != mLock) {
450f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    cursor.close();
451f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    return;
452f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                }
453f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // Copy it to a local static cursor.
454f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
455f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                cursor.close();
456c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
457f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                final long now = System.currentTimeMillis();
458f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                if (mCursor != null) {
459f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    mCursor.close();
460f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                }
461f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                mCursor = matrixCursor;
462f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                String tz = Utils.getTimeZone(mContext, mTimezoneChanged);
463f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                mModel = buildAppWidgetModel(mContext, mCursor, tz);
464f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
465f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // Schedule an alarm to wake ourselves up for the next update.
466f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // We also cancel
467f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // all existing wake-ups because PendingIntents don't match
468f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // against extras.
469f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                long triggerTime = calculateUpdateTime(mModel, now, tz);
470f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
471f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // If no next-update calculated, or bad trigger time in past,
472f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // schedule
473f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                // update about six hours from now.
474f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                if (triggerTime < now) {
475f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    Log.w(TAG, "Encountered bad trigger time " + formatDebugTime(triggerTime, now));
476f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    triggerTime = now + UPDATE_TIME_NO_EVENTS;
477f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                }
478c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
479f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                final AlarmManager alertManager = (AlarmManager) mContext
480f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        .getSystemService(Context.ALARM_SERVICE);
481f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                final PendingIntent pendingUpdate = CalendarAppWidgetProvider
482f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        .getUpdateIntent(mContext);
483f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
484f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                alertManager.cancel(pendingUpdate);
485f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                alertManager.set(AlarmManager.RTC, triggerTime, pendingUpdate);
486f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                Time time = new Time(Utils.getTimeZone(mContext, null));
487f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                time.setToNow();
488f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik
489f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                if (time.normalize(true) != sLastUpdateTime) {
490f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    Time time2 = new Time(Utils.getTimeZone(mContext, null));
491f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    time2.set(sLastUpdateTime);
492f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    time2.normalize(true);
493f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    if (time.year != time2.year || time.yearDay != time2.yearDay) {
494f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        final Intent updateIntent = new Intent(
495f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                                Utils.getWidgetUpdateAction(mContext));
496f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                        mContext.sendBroadcast(updateIntent);
497f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    }
498a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik
499f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    sLastUpdateTime = time.toMillis(true);
500f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                }
501a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik
502f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
503f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                if (mAppWidgetId == -1) {
504f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    int[] ids = widgetManager.getAppWidgetIds(CalendarAppWidgetProvider
505f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                            .getComponentName(mContext));
506a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik
507f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.events_list);
508f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                } else {
509f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                    widgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId, R.id.events_list);
510a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik                }
511a38da7d3d03e1a1151d199dbac0a00ff2ff75a89RoboErik            }
512c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
513c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik
514c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        @Override
515c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        public void onReceive(Context context, Intent intent) {
516f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            if (LOGD)
517f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                Log.d(TAG, "AppWidgetService received an intent. It was " + intent.toString());
518f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            mContext = context;
519f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            if (mLoader == null) {
520f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                mAppWidgetId = -1;
521f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                initLoader();
522f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            } else {
523f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                mHandler.removeCallbacks(mUpdateLoader);
524f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik                mHandler.post(mUpdateLoader);
525f1416205f0bd6d40ed20c49752fdf8da63258611RoboErik            }
526c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik        }
5273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
528bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
5293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
5303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * Format given time for debugging output.
5313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *
5323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param unixTime Target time to report.
5333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * @param now Current system time from {@link System#currentTimeMillis()}
5343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     *            for calculating time difference.
5353ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     */
5363ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static String formatDebugTime(long unixTime, long now) {
5373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        Time time = new Time();
5383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        time.set(unixTime);
5393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
5403ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        long delta = unixTime - now;
5413ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        if (delta > DateUtils.MINUTE_IN_MILLIS) {
5423ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            delta /= DateUtils.MINUTE_IN_MILLIS;
5433ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return String.format("[%d] %s (%+d mins)", unixTime,
5443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    time.format("%H:%M:%S"), delta);
5453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        } else {
5463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            delta /= DateUtils.SECOND_IN_MILLIS;
5473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return String.format("[%d] %s (%+d secs)", unixTime,
5483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    time.format("%H:%M:%S"), delta);
549bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
550bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
551bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang}
552