1/* 2 * Copyright (C) 2011 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; 18 19import com.android.calendar.CalendarController.ViewType; 20 21import android.content.Context; 22import android.os.Handler; 23import android.text.format.DateUtils; 24import android.text.format.Time; 25import android.view.LayoutInflater; 26import android.view.View; 27import android.view.ViewGroup; 28import android.widget.BaseAdapter; 29import android.widget.TextView; 30 31import java.util.Formatter; 32import java.util.Locale; 33 34 35/* 36 * The MenuSpinnerAdapter defines the look of the ActionBar's pull down menu 37 * for small screen layouts. The pull down menu replaces the tabs uses for big screen layouts 38 * 39 * The MenuSpinnerAdapter responsible for creating the views used for in the pull down menu. 40 */ 41 42public class CalendarViewAdapter extends BaseAdapter { 43 44 private static final String TAG = "MenuSpinnerAdapter"; 45 46 private final String mButtonNames []; // Text on buttons 47 48 // Used to define the look of the menu button according to the current view: 49 // Day view: show day of the week + full date underneath 50 // Week view: show the month + year 51 // Month view: show the month + year 52 // Agenda view: show day of the week + full date underneath 53 private int mCurrentMainView; 54 55 private final LayoutInflater mInflater; 56 57 // Defines the types of view returned by this spinner 58 private static final int BUTTON_VIEW_TYPE = 0; 59 static final int VIEW_TYPE_NUM = 1; // Increase this if you add more view types 60 61 public static final int DAY_BUTTON_INDEX = 0; 62 public static final int WEEK_BUTTON_INDEX = 1; 63 public static final int MONTH_BUTTON_INDEX = 2; 64 public static final int AGENDA_BUTTON_INDEX = 3; 65 66 // The current selected event's time, used to calculate the date and day of the week 67 // for the buttons. 68 private long mMilliTime; 69 private String mTimeZone; 70 private long mTodayJulianDay; 71 72 private final Context mContext; 73 private final Formatter mFormatter; 74 private final StringBuilder mStringBuilder; 75 private Handler mMidnightHandler = null; // Used to run a time update every midnight 76 private final boolean mShowDate; // Spinner mode indicator (view name or view name with date) 77 78 // Updates time specific variables (time-zone, today's Julian day). 79 private final Runnable mTimeUpdater = new Runnable() { 80 @Override 81 public void run() { 82 refresh(mContext); 83 } 84 }; 85 86 public CalendarViewAdapter(Context context, int viewType, boolean showDate) { 87 super(); 88 89 mMidnightHandler = new Handler(); 90 mCurrentMainView = viewType; 91 mContext = context; 92 mShowDate = showDate; 93 94 // Initialize 95 mButtonNames = context.getResources().getStringArray(R.array.buttons_list); 96 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 97 mStringBuilder = new StringBuilder(50); 98 mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); 99 100 // Sets time specific variables and starts a thread for midnight updates 101 if (showDate) { 102 refresh(context); 103 } 104 } 105 106 107 // Sets the time zone and today's Julian day to be used by the adapter. 108 // Also, notify listener on the change and resets the midnight update thread. 109 public void refresh(Context context) { 110 mTimeZone = Utils.getTimeZone(context, mTimeUpdater); 111 Time time = new Time(mTimeZone); 112 long now = System.currentTimeMillis(); 113 time.set(now); 114 mTodayJulianDay = Time.getJulianDay(now, time.gmtoff); 115 notifyDataSetChanged(); 116 setMidnightHandler(); 117 } 118 119 // Sets a thread to run 1 second after midnight and update the current date 120 // This is used to display correctly the date of yesterday/today/tomorrow 121 private void setMidnightHandler() { 122 mMidnightHandler.removeCallbacks(mTimeUpdater); 123 // Set the time updater to run at 1 second after midnight 124 long now = System.currentTimeMillis(); 125 Time time = new Time(mTimeZone); 126 time.set(now); 127 long runInMillis = (24 * 3600 - time.hour * 3600 - time.minute * 60 - 128 time.second + 1) * 1000; 129 mMidnightHandler.postDelayed(mTimeUpdater, runInMillis); 130 } 131 132 // Stops the midnight update thread, called by the activity when it is paused. 133 public void onPause() { 134 mMidnightHandler.removeCallbacks(mTimeUpdater); 135 } 136 137 // Returns the amount of buttons in the menu 138 @Override 139 public int getCount() { 140 return mButtonNames.length; 141 } 142 143 144 @Override 145 public Object getItem(int position) { 146 if (position < mButtonNames.length) { 147 return mButtonNames[position]; 148 } 149 return null; 150 } 151 152 @Override 153 public long getItemId(int position) { 154 // Item ID is its location in the list 155 return position; 156 } 157 158 @Override 159 public boolean hasStableIds() { 160 return false; 161 } 162 163 @Override 164 public View getView(int position, View convertView, ViewGroup parent) { 165 166 View v; 167 168 if (mShowDate) { 169 // Check if can recycle the view 170 if (convertView == null || ((Integer) convertView.getTag()).intValue() 171 != R.layout.actionbar_pulldown_menu_top_button) { 172 v = mInflater.inflate(R.layout.actionbar_pulldown_menu_top_button, parent, false); 173 // Set the tag to make sure you can recycle it when you get it 174 // as a convert view 175 v.setTag(new Integer(R.layout.actionbar_pulldown_menu_top_button)); 176 } else { 177 v = convertView; 178 } 179 TextView weekDay = (TextView) v.findViewById(R.id.top_button_weekday); 180 TextView date = (TextView) v.findViewById(R.id.top_button_date); 181 182 switch (mCurrentMainView) { 183 case ViewType.DAY: 184 weekDay.setVisibility(View.VISIBLE); 185 weekDay.setText(buildDayOfWeek()); 186 date.setText(buildFullDate()); 187 break; 188 case ViewType.WEEK: 189 if (Utils.getShowWeekNumber(mContext)) { 190 weekDay.setVisibility(View.VISIBLE); 191 weekDay.setText(buildWeekNum()); 192 } else { 193 weekDay.setVisibility(View.GONE); 194 } 195 date.setText(buildMonthYearDate()); 196 break; 197 case ViewType.MONTH: 198 weekDay.setVisibility(View.GONE); 199 date.setText(buildMonthYearDate()); 200 break; 201 case ViewType.AGENDA: 202 weekDay.setVisibility(View.VISIBLE); 203 weekDay.setText(buildDayOfWeek()); 204 date.setText(buildFullDate()); 205 break; 206 default: 207 v = null; 208 break; 209 } 210 } else { 211 if (convertView == null || ((Integer) convertView.getTag()).intValue() 212 != R.layout.actionbar_pulldown_menu_top_button_no_date) { 213 v = mInflater.inflate( 214 R.layout.actionbar_pulldown_menu_top_button_no_date, parent, false); 215 // Set the tag to make sure you can recycle it when you get it 216 // as a convert view 217 v.setTag(new Integer(R.layout.actionbar_pulldown_menu_top_button_no_date)); 218 } else { 219 v = convertView; 220 } 221 TextView title = (TextView) v; 222 switch (mCurrentMainView) { 223 case ViewType.DAY: 224 title.setText(mButtonNames [DAY_BUTTON_INDEX]); 225 break; 226 case ViewType.WEEK: 227 title.setText(mButtonNames [WEEK_BUTTON_INDEX]); 228 break; 229 case ViewType.MONTH: 230 title.setText(mButtonNames [MONTH_BUTTON_INDEX]); 231 break; 232 case ViewType.AGENDA: 233 title.setText(mButtonNames [AGENDA_BUTTON_INDEX]); 234 break; 235 default: 236 v = null; 237 break; 238 } 239 } 240 return v; 241 } 242 243 @Override 244 public int getItemViewType(int position) { 245 // Only one kind of view is used 246 return BUTTON_VIEW_TYPE; 247 } 248 249 @Override 250 public int getViewTypeCount() { 251 return VIEW_TYPE_NUM; 252 } 253 254 @Override 255 public boolean isEmpty() { 256 return (mButtonNames.length == 0); 257 } 258 259 @Override 260 public View getDropDownView(int position, View convertView, ViewGroup parent) { 261 View v = mInflater.inflate(R.layout.actionbar_pulldown_menu_button, parent, false); 262 TextView viewType = (TextView)v.findViewById(R.id.button_view); 263 TextView date = (TextView)v.findViewById(R.id.button_date); 264 switch (position) { 265 case DAY_BUTTON_INDEX: 266 viewType.setText(mButtonNames [DAY_BUTTON_INDEX]); 267 if (mShowDate) { 268 date.setText(buildMonthDayDate()); 269 } 270 break; 271 case WEEK_BUTTON_INDEX: 272 viewType.setText(mButtonNames [WEEK_BUTTON_INDEX]); 273 if (mShowDate) { 274 date.setText(buildWeekDate()); 275 } 276 break; 277 case MONTH_BUTTON_INDEX: 278 viewType.setText(mButtonNames [MONTH_BUTTON_INDEX]); 279 if (mShowDate) { 280 date.setText(buildMonthDate()); 281 } 282 break; 283 case AGENDA_BUTTON_INDEX: 284 viewType.setText(mButtonNames [AGENDA_BUTTON_INDEX]); 285 if (mShowDate) { 286 date.setText(buildMonthDayDate()); 287 } 288 break; 289 default: 290 v = convertView; 291 break; 292 } 293 return v; 294 } 295 296 // Updates the current viewType 297 // Used to match the label on the menu button with the calendar view 298 public void setMainView(int viewType) { 299 mCurrentMainView = viewType; 300 notifyDataSetChanged(); 301 } 302 303 // Update the date that is displayed on buttons 304 // Used when the user selects a new day/week/month to watch 305 public void setTime(long time) { 306 mMilliTime = time; 307 notifyDataSetChanged(); 308 } 309 310 // Builds a string with the day of the week and the word yesterday/today/tomorrow 311 // before it if applicable. 312 private String buildDayOfWeek() { 313 314 Time t = new Time(mTimeZone); 315 t.set(mMilliTime); 316 long julianDay = Time.getJulianDay(mMilliTime,t.gmtoff); 317 String dayOfWeek = null; 318 mStringBuilder.setLength(0); 319 320 if (julianDay == mTodayJulianDay) { 321 dayOfWeek = mContext.getString(R.string.agenda_today, 322 DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 323 DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()); 324 } else if (julianDay == mTodayJulianDay - 1) { 325 dayOfWeek = mContext.getString(R.string.agenda_yesterday, 326 DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 327 DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()); 328 } else if (julianDay == mTodayJulianDay + 1) { 329 dayOfWeek = mContext.getString(R.string.agenda_tomorrow, 330 DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 331 DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()); 332 } else { 333 dayOfWeek = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 334 DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString(); 335 } 336 return dayOfWeek.toUpperCase(); 337 } 338 339 // Builds strings with different formats: 340 // Full date: Month,day Year 341 // Month year 342 // Month day 343 // Month 344 // Week: month day-day or month day - month day 345 private String buildFullDate() { 346 mStringBuilder.setLength(0); 347 String date = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 348 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString(); 349 return date; 350 } 351 352 private String buildMonthYearDate() { 353 mStringBuilder.setLength(0); 354 String date = DateUtils.formatDateRange( 355 mContext, 356 mFormatter, 357 mMilliTime, 358 mMilliTime, 359 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY 360 | DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString(); 361 return date; 362 } 363 364 private String buildMonthDayDate() { 365 mStringBuilder.setLength(0); 366 String date = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 367 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR, mTimeZone).toString(); 368 return date; 369 } 370 371 private String buildMonthDate() { 372 mStringBuilder.setLength(0); 373 String date = DateUtils.formatDateRange( 374 mContext, 375 mFormatter, 376 mMilliTime, 377 mMilliTime, 378 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR 379 | DateUtils.FORMAT_NO_MONTH_DAY, mTimeZone).toString(); 380 return date; 381 } 382 private String buildWeekDate() { 383 384 385 // Calculate the start of the week, taking into account the "first day of the week" 386 // setting. 387 388 Time t = new Time(mTimeZone); 389 t.set(mMilliTime); 390 int firstDayOfWeek = Utils.getFirstDayOfWeek(mContext); 391 int dayOfWeek = t.weekDay; 392 int diff = dayOfWeek - firstDayOfWeek; 393 if (diff != 0) { 394 if (diff < 0) { 395 diff += 7; 396 } 397 t.monthDay -= diff; 398 t.normalize(true /* ignore isDst */); 399 } 400 401 long weekStartTime = t.toMillis(true); 402 // The end of the week is 6 days after the start of the week 403 long weekEndTime = weekStartTime + DateUtils.WEEK_IN_MILLIS - DateUtils.DAY_IN_MILLIS; 404 405 // If week start and end is in 2 different months, use short months names 406 Time t1 = new Time(mTimeZone); 407 t.set(weekEndTime); 408 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR; 409 if (t.month != t1.month) { 410 flags |= DateUtils.FORMAT_ABBREV_MONTH; 411 } 412 413 mStringBuilder.setLength(0); 414 String date = DateUtils.formatDateRange(mContext, mFormatter, weekStartTime, 415 weekEndTime, flags, mTimeZone).toString(); 416 return date; 417 } 418 419 private String buildWeekNum() { 420 int week = Utils.getWeekNumberFromTime(mMilliTime, mContext); 421 return mContext.getResources().getQuantityString(R.plurals.weekN, week, week); 422 } 423 424} 425 426