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 int selfAttendeeStatus; 83 84 long id; 85 long start; 86 long end; 87 boolean allDay; 88 int color; 89 90 public EventInfo() { 91 visibWhen = View.GONE; 92 visibWhere = View.GONE; 93 visibTitle = View.GONE; 94 } 95 96 @Override 97 public String toString() { 98 StringBuilder builder = new StringBuilder(); 99 builder.append("EventInfo [visibTitle="); 100 builder.append(visibTitle); 101 builder.append(", title="); 102 builder.append(title); 103 builder.append(", visibWhen="); 104 builder.append(visibWhen); 105 builder.append(", id="); 106 builder.append(id); 107 builder.append(", when="); 108 builder.append(when); 109 builder.append(", visibWhere="); 110 builder.append(visibWhere); 111 builder.append(", where="); 112 builder.append(where); 113 builder.append(", color="); 114 builder.append(String.format("0x%x", color)); 115 builder.append(", selfAttendeeStatus="); 116 builder.append(selfAttendeeStatus); 117 builder.append("]"); 118 return builder.toString(); 119 } 120 121 @Override 122 public int hashCode() { 123 final int prime = 31; 124 int result = 1; 125 result = prime * result + (allDay ? 1231 : 1237); 126 result = prime * result + (int) (id ^ (id >>> 32)); 127 result = prime * result + (int) (end ^ (end >>> 32)); 128 result = prime * result + (int) (start ^ (start >>> 32)); 129 result = prime * result + ((title == null) ? 0 : title.hashCode()); 130 result = prime * result + visibTitle; 131 result = prime * result + visibWhen; 132 result = prime * result + visibWhere; 133 result = prime * result + ((when == null) ? 0 : when.hashCode()); 134 result = prime * result + ((where == null) ? 0 : where.hashCode()); 135 result = prime * result + color; 136 result = prime * result + selfAttendeeStatus; 137 return result; 138 } 139 140 @Override 141 public boolean equals(Object obj) { 142 if (this == obj) 143 return true; 144 if (obj == null) 145 return false; 146 if (getClass() != obj.getClass()) 147 return false; 148 EventInfo other = (EventInfo) obj; 149 if (id != other.id) 150 return false; 151 if (allDay != other.allDay) 152 return false; 153 if (end != other.end) 154 return false; 155 if (start != other.start) 156 return false; 157 if (title == null) { 158 if (other.title != null) 159 return false; 160 } else if (!title.equals(other.title)) 161 return false; 162 if (visibTitle != other.visibTitle) 163 return false; 164 if (visibWhen != other.visibWhen) 165 return false; 166 if (visibWhere != other.visibWhere) 167 return false; 168 if (when == null) { 169 if (other.when != null) 170 return false; 171 } else if (!when.equals(other.when)) { 172 return false; 173 } 174 if (where == null) { 175 if (other.where != null) 176 return false; 177 } else if (!where.equals(other.where)) { 178 return false; 179 } 180 if (color != other.color) { 181 return false; 182 } 183 if (selfAttendeeStatus != other.selfAttendeeStatus) { 184 return false; 185 } 186 return true; 187 } 188 } 189 190 /** 191 * {@link DayInfo} is a class that represents a day header in the widget. It 192 * contains all of the data necessary to display that day header, including 193 * the properly localized string. 194 */ 195 static class DayInfo { 196 197 /** The Julian day */ 198 final int mJulianDay; 199 200 /** The string representation of this day header, to be displayed */ 201 final String mDayLabel; 202 203 DayInfo(int julianDay, String label) { 204 mJulianDay = julianDay; 205 mDayLabel = label; 206 } 207 208 @Override 209 public String toString() { 210 return mDayLabel; 211 } 212 213 @Override 214 public int hashCode() { 215 final int prime = 31; 216 int result = 1; 217 result = prime * result + ((mDayLabel == null) ? 0 : mDayLabel.hashCode()); 218 result = prime * result + mJulianDay; 219 return result; 220 } 221 222 @Override 223 public boolean equals(Object obj) { 224 if (this == obj) 225 return true; 226 if (obj == null) 227 return false; 228 if (getClass() != obj.getClass()) 229 return false; 230 DayInfo other = (DayInfo) obj; 231 if (mDayLabel == null) { 232 if (other.mDayLabel != null) 233 return false; 234 } else if (!mDayLabel.equals(other.mDayLabel)) 235 return false; 236 if (mJulianDay != other.mJulianDay) 237 return false; 238 return true; 239 } 240 241 } 242 243 final List<RowInfo> mRowInfos; 244 final List<EventInfo> mEventInfos; 245 final List<DayInfo> mDayInfos; 246 final Context mContext; 247 final long mNow; 248 final int mTodayJulianDay; 249 final int mMaxJulianDay; 250 251 public CalendarAppWidgetModel(Context context, String timeZone) { 252 mNow = System.currentTimeMillis(); 253 Time time = new Time(timeZone); 254 time.setToNow(); // This is needed for gmtoff to be set 255 mTodayJulianDay = Time.getJulianDay(mNow, time.gmtoff); 256 mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1; 257 mEventInfos = new ArrayList<EventInfo>(50); 258 mRowInfos = new ArrayList<RowInfo>(50); 259 mDayInfos = new ArrayList<DayInfo>(8); 260 mContext = context; 261 } 262 263 public void buildFromCursor(Cursor cursor, String timeZone) { 264 final Time recycle = new Time(timeZone); 265 final ArrayList<LinkedList<RowInfo>> mBuckets = 266 new ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS); 267 for (int i = 0; i < CalendarAppWidgetService.MAX_DAYS; i++) { 268 mBuckets.add(new LinkedList<RowInfo>()); 269 } 270 recycle.setToNow(); 271 mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone()); 272 if (mShowTZ) { 273 mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(recycle.isDst != 0, 274 TimeZone.SHORT); 275 } 276 277 cursor.moveToPosition(-1); 278 String tz = Utils.getTimeZone(mContext, null); 279 while (cursor.moveToNext()) { 280 final int rowId = cursor.getPosition(); 281 final long eventId = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID); 282 final boolean allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) != 0; 283 long start = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN); 284 long end = cursor.getLong(CalendarAppWidgetService.INDEX_END); 285 final String title = cursor.getString(CalendarAppWidgetService.INDEX_TITLE); 286 final String location = 287 cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION); 288 // we don't compute these ourselves because it seems to produce the 289 // wrong endDay for all day events 290 final int startDay = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY); 291 final int endDay = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY); 292 final int color = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR); 293 final int selfStatus = cursor 294 .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS); 295 296 // Adjust all-day times into local timezone 297 if (allDay) { 298 start = Utils.convertAlldayUtcToLocal(recycle, start, tz); 299 end = Utils.convertAlldayUtcToLocal(recycle, end, tz); 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(eventId, allDay, start, end, startDay, endDay, title, 315 location, color, selfStatus)); 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(long eventId, boolean allDay, long start, long end, 355 int startDay, int endDay, String title, String location, int color, int selfStatus) { 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 int flags = DateUtils.FORMAT_ABBREV_ALL; 362 visibWhen = View.VISIBLE; 363 if (allDay) { 364 flags |= DateUtils.FORMAT_SHOW_DATE; 365 whenString.append(Utils.formatDateRange(mContext, start, end, flags)); 366 } else { 367 flags |= DateUtils.FORMAT_SHOW_TIME; 368 if (DateFormat.is24HourFormat(mContext)) { 369 flags |= DateUtils.FORMAT_24HOUR; 370 } 371 if (endDay > startDay) { 372 flags |= DateUtils.FORMAT_SHOW_DATE; 373 } 374 whenString.append(Utils.formatDateRange(mContext, start, end, flags)); 375 376 if (mShowTZ) { 377 whenString.append(" ").append(mHomeTZName); 378 } 379 } 380 eventInfo.id = eventId; 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 eventInfo.selfAttendeeStatus = selfStatus; 388 389 // What 390 if (TextUtils.isEmpty(title)) { 391 eventInfo.title = mContext.getString(R.string.no_title_label); 392 } else { 393 eventInfo.title = title; 394 } 395 eventInfo.visibTitle = View.VISIBLE; 396 397 // Where 398 if (!TextUtils.isEmpty(location)) { 399 eventInfo.visibWhere = View.VISIBLE; 400 eventInfo.where = location; 401 } else { 402 eventInfo.visibWhere = View.GONE; 403 } 404 return eventInfo; 405 } 406 407 private DayInfo populateDayInfo(int julianDay, Time recycle) { 408 long millis = recycle.setJulianDay(julianDay); 409 int flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE; 410 411 String label; 412 if (julianDay == mTodayJulianDay + 1) { 413 label = mContext.getString(R.string.agenda_tomorrow, 414 Utils.formatDateRange(mContext, millis, millis, flags).toString()); 415 } else { 416 flags |= DateUtils.FORMAT_SHOW_WEEKDAY; 417 label = Utils.formatDateRange(mContext, millis, millis, flags); 418 } 419 return new DayInfo(julianDay, label); 420 } 421 422 @Override 423 public String toString() { 424 StringBuilder builder = new StringBuilder(); 425 builder.append("\nCalendarAppWidgetModel [eventInfos="); 426 builder.append(mEventInfos); 427 builder.append("]"); 428 return builder.toString(); 429 } 430}