1bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang/*
2bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Copyright (C) 2010 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
1921a183875fbbfa54f5a2a87779888a5fb7d1af44Erikimport com.android.calendar.R;
2021a183875fbbfa54f5a2a87779888a5fb7d1af44Erikimport com.android.calendar.Utils;
2121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik
223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport android.content.Context;
233ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport android.database.Cursor;
243ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport android.text.TextUtils;
253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport android.text.format.DateFormat;
263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport android.text.format.DateUtils;
273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport android.text.format.Time;
283ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport android.util.Log;
29bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View;
30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport java.util.ArrayList;
323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport java.util.LinkedList;
333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport java.util.List;
343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tangimport java.util.TimeZone;
35bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
36bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangclass CalendarAppWidgetModel {
373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    private static final String TAG = CalendarAppWidgetModel.class.getSimpleName();
383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    private static final boolean LOGD = false;
393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
4021a183875fbbfa54f5a2a87779888a5fb7d1af44Erik    private String mHomeTZName;
4121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik    private boolean mShowTZ;
423ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
433ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * {@link RowInfo} is a class that represents a single row in the widget. It
443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * is actually only a pointer to either a {@link DayInfo} or an
453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * {@link EventInfo} instance, since a row in the widget might be either a
463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * day header or an event.
47bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang     */
483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static class RowInfo {
493ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        static final int TYPE_DAY = 0;
503ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        static final int TYPE_MEETING = 1;
513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        /**
533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang         *  mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang         */
553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        final int mType;
563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        /**
583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang         * If mType is TYPE_DAY, then mData is the index into day infos.
593ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang         * Otherwise mType is TYPE_MEETING and mData is the index into event
603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang         * infos.
613ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang         */
623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        final int mIndex;
63bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
643ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        RowInfo(int type, int index) {
653ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mType = type;
663ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mIndex = index;
67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
703ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
713ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * {@link EventInfo} is a class that represents an event in the widget. It
723ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * contains all of the data necessary to display that event, including the
733ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * properly localized strings and visibility settings.
743ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     */
753ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static class EventInfo {
76bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        int visibWhen; // Visibility value for When textview (View.GONE or View.VISIBLE)
77bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        String when;
78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        int visibWhere; // Visibility value for Where textview (View.GONE or View.VISIBLE)
79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        String where;
80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        int visibTitle; // Visibility value for Title textview (View.GONE or View.VISIBLE)
81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        String title;
8242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        int selfAttendeeStatus;
83bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
84bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan        long id;
8547d40324272ae39af0872bf5cbf27e1800478021Mason Tang        long start;
863ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        long end;
873ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        boolean allDay;
88a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        int color;
8947d40324272ae39af0872bf5cbf27e1800478021Mason Tang
90bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        public EventInfo() {
91bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            visibWhen = View.GONE;
92bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            visibWhere = View.GONE;
93bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            visibTitle = View.GONE;
94bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
95bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
96bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        @Override
97bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        public String toString() {
98bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            StringBuilder builder = new StringBuilder();
99bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append("EventInfo [visibTitle=");
100bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(visibTitle);
101bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(", title=");
102bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(title);
103bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(", visibWhen=");
104bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(visibWhen);
105bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan            builder.append(", id=");
106bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan            builder.append(id);
107bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(", when=");
108bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(when);
109bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(", visibWhere=");
110bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(visibWhere);
111bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(", where=");
112bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append(where);
113a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            builder.append(", color=");
114a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            builder.append(String.format("0x%x", color));
11542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            builder.append(", selfAttendeeStatus=");
11642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            builder.append(selfAttendeeStatus);
117bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            builder.append("]");
118bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            return builder.toString();
119bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
120bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
121bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        @Override
122bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        public int hashCode() {
123bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            final int prime = 31;
124bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            int result = 1;
1253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            result = prime * result + (allDay ? 1231 : 1237);
126bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan            result = prime * result + (int) (id ^ (id >>> 32));
1273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            result = prime * result + (int) (end ^ (end >>> 32));
1283ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            result = prime * result + (int) (start ^ (start >>> 32));
129bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            result = prime * result + ((title == null) ? 0 : title.hashCode());
130bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            result = prime * result + visibTitle;
131bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            result = prime * result + visibWhen;
132bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            result = prime * result + visibWhere;
133bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            result = prime * result + ((when == null) ? 0 : when.hashCode());
134bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            result = prime * result + ((where == null) ? 0 : where.hashCode());
135a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            result = prime * result + color;
13642dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            result = prime * result + selfAttendeeStatus;
137bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            return result;
138bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
139bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
140bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        @Override
141bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        public boolean equals(Object obj) {
1423ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (this == obj)
143bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return true;
1443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (obj == null)
145bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return false;
1463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (getClass() != obj.getClass())
147bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return false;
148bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            EventInfo other = (EventInfo) obj;
149bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan            if (id != other.id)
150bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan                return false;
1513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (allDay != other.allDay)
1523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return false;
1533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (end != other.end)
1543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return false;
1553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (start != other.start)
1563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return false;
157bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            if (title == null) {
1583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                if (other.title != null)
159bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                    return false;
1603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            } else if (!title.equals(other.title))
161bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return false;
1623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (visibTitle != other.visibTitle)
163bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return false;
1643ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (visibWhen != other.visibWhen)
165bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return false;
1663ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (visibWhere != other.visibWhere)
167bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return false;
168bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            if (when == null) {
1693ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                if (other.when != null)
170bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                    return false;
17142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            } else if (!when.equals(other.when)) {
172bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return false;
17342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            }
174bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            if (where == null) {
1753ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                if (other.where != null)
176bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                    return false;
177a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            } else if (!where.equals(other.where)) {
178bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang                return false;
17942dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            }
18042dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            if (color != other.color) {
18142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                return false;
18242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            }
18342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            if (selfAttendeeStatus != other.selfAttendeeStatus) {
184a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                return false;
185a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            }
186bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang            return true;
187bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
1883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
1893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
1903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    /**
1913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * {@link DayInfo} is a class that represents a day header in the widget. It
1923ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * contains all of the data necessary to display that day header, including
1933ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     * the properly localized string.
1943ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang     */
1953ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    static class DayInfo {
1963ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
1973ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        /** The Julian day */
1983ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        final int mJulianDay;
1993ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2003ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        /** The string representation of this day header, to be displayed */
2013ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        final String mDayLabel;
2023ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2033ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        DayInfo(int julianDay, String label) {
2043ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mJulianDay = julianDay;
2053ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mDayLabel = label;
2063ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
2073ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2083ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        @Override
2093ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        public String toString() {
2103ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return mDayLabel;
2113ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
2123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2133ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        @Override
2143ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        public int hashCode() {
2153ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            final int prime = 31;
2163ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            int result = 1;
2173ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            result = prime * result + ((mDayLabel == null) ? 0 : mDayLabel.hashCode());
2183ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            result = prime * result + mJulianDay;
2193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return result;
2203ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
2213ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        @Override
2233ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        public boolean equals(Object obj) {
2243ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (this == obj)
2253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return true;
2263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (obj == null)
2273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return false;
2283ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (getClass() != obj.getClass())
2293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return false;
2303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            DayInfo other = (DayInfo) obj;
2313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (mDayLabel == null) {
2323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                if (other.mDayLabel != null)
2333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    return false;
2343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            } else if (!mDayLabel.equals(other.mDayLabel))
2353ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return false;
2363ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (mJulianDay != other.mJulianDay)
2373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                return false;
2383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            return true;
2393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
2403ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2413ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
2423ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2433ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    final List<RowInfo> mRowInfos;
2443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    final List<EventInfo> mEventInfos;
2453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    final List<DayInfo> mDayInfos;
2463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    final Context mContext;
2473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    final long mNow;
2483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    final int mTodayJulianDay;
2493ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    final int mMaxJulianDay;
2503ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
251ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan    public CalendarAppWidgetModel(Context context, String timeZone) {
2523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        mNow = System.currentTimeMillis();
253ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan        Time time = new Time(timeZone);
2548b71d11235e7ebc02f62435d2c6583edb43c26a2Michael Chan        time.setToNow(); // This is needed for gmtoff to be set
255ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan        mTodayJulianDay = Time.getJulianDay(mNow, time.gmtoff);
2563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1;
2573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        mEventInfos = new ArrayList<EventInfo>(50);
2583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        mRowInfos = new ArrayList<RowInfo>(50);
2593ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        mDayInfos = new ArrayList<DayInfo>(8);
2603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        mContext = context;
2613ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
2623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
26321a183875fbbfa54f5a2a87779888a5fb7d1af44Erik    public void buildFromCursor(Cursor cursor, String timeZone) {
264a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        final Time recycle = new Time(timeZone);
2653ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        final ArrayList<LinkedList<RowInfo>> mBuckets =
266a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                new ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS);
2673ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        for (int i = 0; i < CalendarAppWidgetService.MAX_DAYS; i++) {
2683ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            mBuckets.add(new LinkedList<RowInfo>());
2693ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
27021a183875fbbfa54f5a2a87779888a5fb7d1af44Erik        recycle.setToNow();
27121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik        mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone());
27221a183875fbbfa54f5a2a87779888a5fb7d1af44Erik        if (mShowTZ) {
27321a183875fbbfa54f5a2a87779888a5fb7d1af44Erik            mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(recycle.isDst != 0,
27421a183875fbbfa54f5a2a87779888a5fb7d1af44Erik                    TimeZone.SHORT);
27521a183875fbbfa54f5a2a87779888a5fb7d1af44Erik        }
2763ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2773ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        cursor.moveToPosition(-1);
2789da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik        String tz = Utils.getTimeZone(mContext, null);
2793ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        while (cursor.moveToNext()) {
280a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            final int rowId = cursor.getPosition();
281a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            final long eventId = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID);
282a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            final boolean allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) != 0;
2833ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            long start = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN);
2843ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            long end = cursor.getLong(CalendarAppWidgetService.INDEX_END);
285a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            final String title = cursor.getString(CalendarAppWidgetService.INDEX_TITLE);
286a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            final String location =
287a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa                    cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION);
2883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            // we don't compute these ourselves because it seems to produce the
2893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            // wrong endDay for all day events
290a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            final int startDay = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY);
291a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            final int endDay = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY);
292a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa            final int color = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR);
29342dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            final int selfStatus = cursor
29442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS);
2953ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
2963ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            // Adjust all-day times into local timezone
2973ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (allDay) {
2989da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                start = Utils.convertAlldayUtcToLocal(recycle, start, tz);
2999da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik                end = Utils.convertAlldayUtcToLocal(recycle, end, tz);
3003ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
301bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
3023ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (LOGD) {
3033ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                Log.d(TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start
3043ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                        + " end:" + end + " eventId:" + eventId);
3053ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
3063ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3073ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            // we might get some extra events when querying, in order to
3083ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            // deal with all-day events
3093ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (end < mNow) {
3103ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                continue;
3113ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
3123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3133ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            int i = mEventInfos.size();
31442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            mEventInfos.add(populateEventInfo(eventId, allDay, start, end, startDay, endDay, title,
31542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik                    location, color, selfStatus));
3163ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            // populate the day buckets that this event falls into
3173ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            int from = Math.max(startDay, mTodayJulianDay);
3183ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            int to = Math.min(endDay, mMaxJulianDay);
3193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            for (int day = from; day <= to; day++) {
3203ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                LinkedList<RowInfo> bucket = mBuckets.get(day - mTodayJulianDay);
3213ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                RowInfo rowInfo = new RowInfo(RowInfo.TYPE_MEETING, i);
3223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                if (allDay) {
3233ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    bucket.addFirst(rowInfo);
3243ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                } else {
3253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                    bucket.add(rowInfo);
3263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                }
3273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
3283ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
3293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        int day = mTodayJulianDay;
3313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        int count = 0;
3323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        for (LinkedList<RowInfo> bucket : mBuckets) {
3333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (!bucket.isEmpty()) {
33450088e364aaa3074476e1300a270aafb0653df47Daisuke Miyakawa                // We don't show day header in today
33550088e364aaa3074476e1300a270aafb0653df47Daisuke Miyakawa                if (day != mTodayJulianDay) {
33650088e364aaa3074476e1300a270aafb0653df47Daisuke Miyakawa                    final DayInfo dayInfo = populateDayInfo(day, recycle);
33750088e364aaa3074476e1300a270aafb0653df47Daisuke Miyakawa                    // Add the day header
338bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan                    final int dayIndex = mDayInfos.size();
33950088e364aaa3074476e1300a270aafb0653df47Daisuke Miyakawa                    mDayInfos.add(dayInfo);
34050088e364aaa3074476e1300a270aafb0653df47Daisuke Miyakawa                    mRowInfos.add(new RowInfo(RowInfo.TYPE_DAY, dayIndex));
34150088e364aaa3074476e1300a270aafb0653df47Daisuke Miyakawa                }
34250088e364aaa3074476e1300a270aafb0653df47Daisuke Miyakawa
3433ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                // Add the event row infos
3443ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                mRowInfos.addAll(bucket);
3453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                count += bucket.size();
3463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
3473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            day++;
3483ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
3493ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                break;
3503ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
3513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
3523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
3533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
354bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan    private EventInfo populateEventInfo(long eventId, boolean allDay, long start, long end,
35542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            int startDay, int endDay, String title, String location, int color, int selfStatus) {
3563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        EventInfo eventInfo = new EventInfo();
3573ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3583ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        // Compute a human-readable string for the start time of the event
35921a183875fbbfa54f5a2a87779888a5fb7d1af44Erik        StringBuilder whenString = new StringBuilder();
3603ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        int visibWhen;
36142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        int flags = DateUtils.FORMAT_ABBREV_ALL;
36242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        visibWhen = View.VISIBLE;
3633ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        if (allDay) {
36442dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            flags |= DateUtils.FORMAT_SHOW_DATE;
36542dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik            whenString.append(Utils.formatDateRange(mContext, start, end, flags));
3663ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        } else {
3673ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            flags |= DateUtils.FORMAT_SHOW_TIME;
3683ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (DateFormat.is24HourFormat(mContext)) {
3693ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                flags |= DateUtils.FORMAT_24HOUR;
3703ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
3713ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            if (endDay > startDay) {
3723ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang                flags |= DateUtils.FORMAT_SHOW_DATE;
37321a183875fbbfa54f5a2a87779888a5fb7d1af44Erik            }
37477cf2c757257fdb17320be030001b7c8eff8253dDaisuke Miyakawa            whenString.append(Utils.formatDateRange(mContext, start, end, flags));
37577cf2c757257fdb17320be030001b7c8eff8253dDaisuke Miyakawa
37621a183875fbbfa54f5a2a87779888a5fb7d1af44Erik            if (mShowTZ) {
37721a183875fbbfa54f5a2a87779888a5fb7d1af44Erik                whenString.append(" ").append(mHomeTZName);
3783ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            }
379bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        }
380bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan        eventInfo.id = eventId;
3813ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        eventInfo.start = start;
3823ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        eventInfo.end = end;
3833ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        eventInfo.allDay = allDay;
38421a183875fbbfa54f5a2a87779888a5fb7d1af44Erik        eventInfo.when = whenString.toString();
3853ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        eventInfo.visibWhen = visibWhen;
386a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa        eventInfo.color = color;
38742dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik        eventInfo.selfAttendeeStatus = selfStatus;
3883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        // What
3903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        if (TextUtils.isEmpty(title)) {
3913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            eventInfo.title = mContext.getString(R.string.no_title_label);
3923ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        } else {
3933ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            eventInfo.title = title;
3943ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
3953ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        eventInfo.visibTitle = View.VISIBLE;
3963ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
3973ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        // Where
3983ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        if (!TextUtils.isEmpty(location)) {
3993ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            eventInfo.visibWhere = View.VISIBLE;
4003ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            eventInfo.where = location;
4013ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        } else {
4023ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang            eventInfo.visibWhere = View.GONE;
4033ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
4043ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        return eventInfo;
4053ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    }
4063ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
4073ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang    private DayInfo populateDayInfo(int julianDay, Time recycle) {
4083ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        long millis = recycle.setJulianDay(julianDay);
409937b49cfe0d0e715c06c60369e83ec6fec5e74faRoboErik        int flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE;
4103ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang
4113ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        String label;
4123ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        if (julianDay == mTodayJulianDay + 1) {
413937b49cfe0d0e715c06c60369e83ec6fec5e74faRoboErik            label = mContext.getString(R.string.agenda_tomorrow,
414937b49cfe0d0e715c06c60369e83ec6fec5e74faRoboErik                    Utils.formatDateRange(mContext, millis, millis, flags).toString());
4153ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        } else {
416937b49cfe0d0e715c06c60369e83ec6fec5e74faRoboErik            flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
417ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan            label = Utils.formatDateRange(mContext, millis, millis, flags);
4183ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        }
4193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        return new DayInfo(julianDay, label);
420bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
421bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang
422bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    @Override
423bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    public String toString() {
424bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        StringBuilder builder = new StringBuilder();
425bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        builder.append("\nCalendarAppWidgetModel [eventInfos=");
4263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang        builder.append(mEventInfos);
427bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        builder.append("]");
428bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang        return builder.toString();
429bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang    }
430bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang}