CalendarAppWidgetModel.java revision 0c715c837a7ecc2cfda3a62d952f8dc7e79a39f3
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 boolean eventIsInProgress = start <= mNow && end > mNow; 359 360 // Compute a human-readable string for the start time of the event 361 StringBuilder whenString = new StringBuilder(); 362 int visibWhen; 363 if (allDay) { 364 whenString.setLength(0); 365 visibWhen = View.GONE; 366 } else { 367 int flags = DateUtils.FORMAT_ABBREV_ALL; 368 flags |= DateUtils.FORMAT_SHOW_TIME; 369 if (DateFormat.is24HourFormat(mContext)) { 370 flags |= DateUtils.FORMAT_24HOUR; 371 } 372 if (endDay > startDay) { 373 flags |= DateUtils.FORMAT_SHOW_DATE; 374 whenString.append(Utils.formatDateRange(mContext, start, end, flags)); 375 } else { 376 whenString.append(Utils.formatDateRange(mContext, start, start, flags)); 377 } 378 String tz = Utils.getTimeZone(mContext, null); 379 if (mShowTZ) { 380 whenString.append(" ").append(mHomeTZName); 381 } 382 // TODO better i18n formatting 383 if (eventIsInProgress) { 384 whenString.append(" (").append(mContext.getString(R.string.in_progress)) 385 .append(")"); 386 } 387 visibWhen = View.VISIBLE; 388 } 389 eventInfo.start = start; 390 eventInfo.end = end; 391 eventInfo.allDay = allDay; 392 eventInfo.when = whenString.toString(); 393 eventInfo.visibWhen = visibWhen; 394 eventInfo.color = color; 395 396 // What 397 if (TextUtils.isEmpty(title)) { 398 eventInfo.title = mContext.getString(R.string.no_title_label); 399 } else { 400 eventInfo.title = title; 401 } 402 eventInfo.visibTitle = View.VISIBLE; 403 404 // Where 405 if (!TextUtils.isEmpty(location)) { 406 eventInfo.visibWhere = View.VISIBLE; 407 eventInfo.where = location; 408 } else { 409 eventInfo.visibWhere = View.GONE; 410 } 411 return eventInfo; 412 } 413 414 private DayInfo populateDayInfo(int julianDay, Time recycle) { 415 long millis = recycle.setJulianDay(julianDay); 416 int flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_UTC; 417 flags |= DateUtils.FORMAT_SHOW_WEEKDAY; 418 419 String label; 420 if (julianDay == mTodayJulianDay + 1) { 421 label = mContext.getString(R.string.tomorrow); 422 } else { 423 label = DateUtils.formatDateRange(mContext, millis, millis, flags); 424 } 425 426 flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_UTC; 427 flags |= DateUtils.FORMAT_SHOW_DATE; 428 429 label += ", "; 430 label += DateUtils.formatDateRange(mContext, millis, millis, flags); 431 432 return new DayInfo(julianDay, label); 433 } 434 435 @Override 436 public String toString() { 437 StringBuilder builder = new StringBuilder(); 438 builder.append("\nCalendarAppWidgetModel [eventInfos="); 439 builder.append(mEventInfos); 440 builder.append(", dayOfMonth="); 441 builder.append(mDayOfMonth); 442 builder.append(", dayOfWeek="); 443 builder.append(mDayOfWeek); 444 builder.append("]"); 445 return builder.toString(); 446 } 447}