CalendarAppWidgetModel.java revision 77cf2c757257fdb17320be030001b7c8eff8253d
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calendar.widget;
18
19import com.android.calendar.R;
20import com.android.calendar.Utils;
21
22import android.content.Context;
23import android.database.Cursor;
24import android.text.TextUtils;
25import android.text.format.DateFormat;
26import android.text.format.DateUtils;
27import android.text.format.Time;
28import android.util.Log;
29import android.view.View;
30
31import java.util.ArrayList;
32import java.util.LinkedList;
33import java.util.List;
34import java.util.TimeZone;
35
36class CalendarAppWidgetModel {
37    private static final String TAG = CalendarAppWidgetModel.class.getSimpleName();
38    private static final boolean LOGD = false;
39
40    private String mHomeTZName;
41    private boolean mShowTZ;
42    /**
43     * {@link RowInfo} is a class that represents a single row in the widget. It
44     * is actually only a pointer to either a {@link DayInfo} or an
45     * {@link EventInfo} instance, since a row in the widget might be either a
46     * day header or an event.
47     */
48    static class RowInfo {
49        static final int TYPE_DAY = 0;
50        static final int TYPE_MEETING = 1;
51
52        /**
53         *  mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
54         */
55        final int mType;
56
57        /**
58         * If mType is TYPE_DAY, then mData is the index into day infos.
59         * Otherwise mType is TYPE_MEETING and mData is the index into event
60         * infos.
61         */
62        final int mIndex;
63
64        RowInfo(int type, int index) {
65            mType = type;
66            mIndex = index;
67        }
68    }
69
70    /**
71     * {@link EventInfo} is a class that represents an event in the widget. It
72     * contains all of the data necessary to display that event, including the
73     * properly localized strings and visibility settings.
74     */
75    static class EventInfo {
76        int visibWhen; // Visibility value for When textview (View.GONE or View.VISIBLE)
77        String when;
78        int visibWhere; // Visibility value for Where textview (View.GONE or View.VISIBLE)
79        String where;
80        int visibTitle; // Visibility value for Title textview (View.GONE or View.VISIBLE)
81        String title;
82
83        long start;
84        long end;
85        boolean allDay;
86        int color;
87
88        public EventInfo() {
89            visibWhen = View.GONE;
90            visibWhere = View.GONE;
91            visibTitle = View.GONE;
92        }
93
94        @Override
95        public String toString() {
96            StringBuilder builder = new StringBuilder();
97            builder.append("EventInfo [visibTitle=");
98            builder.append(visibTitle);
99            builder.append(", title=");
100            builder.append(title);
101            builder.append(", visibWhen=");
102            builder.append(visibWhen);
103            builder.append(", when=");
104            builder.append(when);
105            builder.append(", visibWhere=");
106            builder.append(visibWhere);
107            builder.append(", where=");
108            builder.append(where);
109            builder.append(", color=");
110            builder.append(String.format("0x%x", color));
111            builder.append("]");
112            return builder.toString();
113        }
114
115        @Override
116        public int hashCode() {
117            final int prime = 31;
118            int result = 1;
119            result = prime * result + (allDay ? 1231 : 1237);
120            result = prime * result + (int) (end ^ (end >>> 32));
121            result = prime * result + (int) (start ^ (start >>> 32));
122            result = prime * result + ((title == null) ? 0 : title.hashCode());
123            result = prime * result + visibTitle;
124            result = prime * result + visibWhen;
125            result = prime * result + visibWhere;
126            result = prime * result + ((when == null) ? 0 : when.hashCode());
127            result = prime * result + ((where == null) ? 0 : where.hashCode());
128            result = prime * result + color;
129            return result;
130        }
131
132        @Override
133        public boolean equals(Object obj) {
134            if (this == obj)
135                return true;
136            if (obj == null)
137                return false;
138            if (getClass() != obj.getClass())
139                return false;
140            EventInfo other = (EventInfo) obj;
141            if (allDay != other.allDay)
142                return false;
143            if (end != other.end)
144                return false;
145            if (start != other.start)
146                return false;
147            if (title == null) {
148                if (other.title != null)
149                    return false;
150            } else if (!title.equals(other.title))
151                return false;
152            if (visibTitle != other.visibTitle)
153                return false;
154            if (visibWhen != other.visibWhen)
155                return false;
156            if (visibWhere != other.visibWhere)
157                return false;
158            if (when == null) {
159                if (other.when != null)
160                    return false;
161            } else if (!when.equals(other.when))
162                return false;
163            if (where == null) {
164                if (other.where != null)
165                    return false;
166            } else if (!where.equals(other.where)) {
167                return false;
168            } else if (color != other.color) {
169                return false;
170            }
171            return true;
172        }
173    }
174
175    /**
176     * {@link DayInfo} is a class that represents a day header in the widget. It
177     * contains all of the data necessary to display that day header, including
178     * the properly localized string.
179     */
180    static class DayInfo {
181
182        /** The Julian day */
183        final int mJulianDay;
184
185        /** The string representation of this day header, to be displayed */
186        final String mDayLabel;
187
188        DayInfo(int julianDay, String label) {
189            mJulianDay = julianDay;
190            mDayLabel = label;
191        }
192
193        @Override
194        public String toString() {
195            return mDayLabel;
196        }
197
198        @Override
199        public int hashCode() {
200            final int prime = 31;
201            int result = 1;
202            result = prime * result + ((mDayLabel == null) ? 0 : mDayLabel.hashCode());
203            result = prime * result + mJulianDay;
204            return result;
205        }
206
207        @Override
208        public boolean equals(Object obj) {
209            if (this == obj)
210                return true;
211            if (obj == null)
212                return false;
213            if (getClass() != obj.getClass())
214                return false;
215            DayInfo other = (DayInfo) obj;
216            if (mDayLabel == null) {
217                if (other.mDayLabel != null)
218                    return false;
219            } else if (!mDayLabel.equals(other.mDayLabel))
220                return false;
221            if (mJulianDay != other.mJulianDay)
222                return false;
223            return true;
224        }
225
226    }
227
228    String mDayOfWeek;
229    String mDayOfMonth;
230    final List<RowInfo> mRowInfos;
231    final List<EventInfo> mEventInfos;
232    final List<DayInfo> mDayInfos;
233    final Context mContext;
234    final long mNow;
235    final long mStartOfNextDay;
236    final int mTodayJulianDay;
237    final int mMaxJulianDay;
238
239    public CalendarAppWidgetModel(Context context) {
240        mNow = System.currentTimeMillis();
241        Time time = new Time();
242        time.set(mNow);
243        time.monthDay++;
244        time.hour = 0;
245        time.minute = 0;
246        time.second = 0;
247        mStartOfNextDay = time.normalize(true);
248
249        long localOffset = TimeZone.getDefault().getOffset(mNow) / 1000;
250        mTodayJulianDay = Time.getJulianDay(mNow, localOffset);
251        mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1;
252
253        // Calendar header
254        String dayOfWeek = DateUtils.getDayOfWeekString(
255                time.weekDay + 1, DateUtils.LENGTH_MEDIUM).toUpperCase();
256
257        mDayOfWeek = dayOfWeek;
258        mDayOfMonth = Integer.toString(time.monthDay);
259
260        mEventInfos = new ArrayList<EventInfo>(50);
261        mRowInfos = new ArrayList<RowInfo>(50);
262        mDayInfos = new ArrayList<DayInfo>(8);
263        mContext = context;
264    }
265
266    public void buildFromCursor(Cursor cursor, String timeZone) {
267        final Time recycle = new Time(timeZone);
268        final ArrayList<LinkedList<RowInfo>> mBuckets =
269                new ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS);
270        for (int i = 0; i < CalendarAppWidgetService.MAX_DAYS; i++) {
271            mBuckets.add(new LinkedList<RowInfo>());
272        }
273        recycle.setToNow();
274        mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone());
275        if (mShowTZ) {
276            mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(recycle.isDst != 0,
277                    TimeZone.SHORT);
278        }
279
280        cursor.moveToPosition(-1);
281        while (cursor.moveToNext()) {
282            final int rowId = cursor.getPosition();
283            final long eventId = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID);
284            final boolean allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) != 0;
285            long start = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN);
286            long end = cursor.getLong(CalendarAppWidgetService.INDEX_END);
287            final String title = cursor.getString(CalendarAppWidgetService.INDEX_TITLE);
288            final String location =
289                    cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION);
290            // we don't compute these ourselves because it seems to produce the
291            // wrong endDay for all day events
292            final int startDay = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY);
293            final int endDay = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY);
294            final int color = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR);
295
296            // Adjust all-day times into local timezone
297            if (allDay) {
298                start = Utils.convertUtcToLocal(recycle, start);
299                end = Utils.convertUtcToLocal(recycle, end);
300            }
301
302            if (LOGD) {
303                Log.d(TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start
304                        + " end:" + end + " eventId:" + eventId);
305            }
306
307            // we might get some extra events when querying, in order to
308            // deal with all-day events
309            if (end < mNow) {
310                continue;
311            }
312
313            int i = mEventInfos.size();
314            mEventInfos.add(populateEventInfo(
315                    allDay, start, end, startDay, endDay, title, location, color));
316            // populate the day buckets that this event falls into
317            int from = Math.max(startDay, mTodayJulianDay);
318            int to = Math.min(endDay, mMaxJulianDay);
319            for (int day = from; day <= to; day++) {
320                LinkedList<RowInfo> bucket = mBuckets.get(day - mTodayJulianDay);
321                RowInfo rowInfo = new RowInfo(RowInfo.TYPE_MEETING, i);
322                if (allDay) {
323                    bucket.addFirst(rowInfo);
324                } else {
325                    bucket.add(rowInfo);
326                }
327            }
328        }
329
330        int day = mTodayJulianDay;
331        int count = 0;
332        for (LinkedList<RowInfo> bucket : mBuckets) {
333            if (!bucket.isEmpty()) {
334                // We don't show day header in today
335                if (day != mTodayJulianDay) {
336                    final DayInfo dayInfo = populateDayInfo(day, recycle);
337                    // Add the day header
338                    final int dayIndex = mDayInfos.size();
339                    mDayInfos.add(dayInfo);
340                    mRowInfos.add(new RowInfo(RowInfo.TYPE_DAY, dayIndex));
341                }
342
343                // Add the event row infos
344                mRowInfos.addAll(bucket);
345                count += bucket.size();
346            }
347            day++;
348            if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
349                break;
350            }
351        }
352    }
353
354    private EventInfo populateEventInfo(boolean allDay, long start, long end,
355            int startDay, int endDay, String title, String location, int color) {
356        EventInfo eventInfo = new EventInfo();
357
358        // Compute a human-readable string for the start time of the event
359        StringBuilder whenString = new StringBuilder();
360        int visibWhen;
361        if (allDay) {
362            whenString.setLength(0);
363            visibWhen = View.GONE;
364        } else {
365            int flags = DateUtils.FORMAT_ABBREV_ALL;
366            flags |= DateUtils.FORMAT_SHOW_TIME;
367            if (DateFormat.is24HourFormat(mContext)) {
368                flags |= DateUtils.FORMAT_24HOUR;
369            }
370            if (endDay > startDay) {
371                flags |= DateUtils.FORMAT_SHOW_DATE;
372            }
373            whenString.append(Utils.formatDateRange(mContext, start, end, flags));
374
375            String tz = Utils.getTimeZone(mContext, null);
376            if (mShowTZ) {
377                whenString.append(" ").append(mHomeTZName);
378            }
379            visibWhen = View.VISIBLE;
380        }
381        eventInfo.start = start;
382        eventInfo.end = end;
383        eventInfo.allDay = allDay;
384        eventInfo.when = whenString.toString();
385        eventInfo.visibWhen = visibWhen;
386        eventInfo.color = color;
387
388        // What
389        if (TextUtils.isEmpty(title)) {
390            eventInfo.title = mContext.getString(R.string.no_title_label);
391        } else {
392            eventInfo.title = title;
393        }
394        eventInfo.visibTitle = View.VISIBLE;
395
396        // Where
397        if (!TextUtils.isEmpty(location)) {
398            eventInfo.visibWhere = View.VISIBLE;
399            eventInfo.where = location;
400        } else {
401            eventInfo.visibWhere = View.GONE;
402        }
403        return eventInfo;
404    }
405
406    private DayInfo populateDayInfo(int julianDay, Time recycle) {
407        long millis = recycle.setJulianDay(julianDay);
408        int flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_UTC;
409        flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
410
411        String label;
412        if (julianDay == mTodayJulianDay + 1) {
413            label = mContext.getString(R.string.tomorrow);
414        } else {
415            label = DateUtils.formatDateRange(mContext, millis, millis, flags);
416        }
417
418        flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_UTC;
419        flags |= DateUtils.FORMAT_SHOW_DATE;
420
421        label += ", ";
422        label += DateUtils.formatDateRange(mContext, millis, millis, flags);
423
424        return new DayInfo(julianDay, label);
425    }
426
427    @Override
428    public String toString() {
429        StringBuilder builder = new StringBuilder();
430        builder.append("\nCalendarAppWidgetModel [eventInfos=");
431        builder.append(mEventInfos);
432        builder.append(", dayOfMonth=");
433        builder.append(mDayOfMonth);
434        builder.append(", dayOfWeek=");
435        builder.append(mDayOfWeek);
436        builder.append("]");
437        return builder.toString();
438    }
439}