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 17 package com.android.calendar.widget; 18 19 import com.android.calendar.R; 20 import com.android.calendar.Utils; 21 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.text.TextUtils; 25 import android.text.format.DateFormat; 26 import android.text.format.DateUtils; 27 import android.text.format.Time; 28 import android.util.Log; 29 import android.view.View; 30 31 import java.util.ArrayList; 32 import java.util.LinkedList; 33 import java.util.List; 34 import java.util.TimeZone; 35 36 class 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 RowInfo(int type, int index)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 EventInfo()90 public EventInfo() { 91 visibWhen = View.GONE; 92 visibWhere = View.GONE; 93 visibTitle = View.GONE; 94 } 95 96 @Override toString()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 hashCode()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 equals(Object obj)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 DayInfo(int julianDay, String label)203 DayInfo(int julianDay, String label) { 204 mJulianDay = julianDay; 205 mDayLabel = label; 206 } 207 208 @Override toString()209 public String toString() { 210 return mDayLabel; 211 } 212 213 @Override hashCode()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 equals(Object obj)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 CalendarAppWidgetModel(Context context, String timeZone)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 buildFromCursor(Cursor cursor, String timeZone)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 populateEventInfo(long eventId, boolean allDay, long start, long end, int startDay, int endDay, String title, String location, int color, int selfStatus)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 populateDayInfo(int julianDay, Time recycle)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 toString()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 }