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 17 package com.android.calendar; 18 19 import com.android.calendar.CalendarController.ViewType; 20 21 import android.content.Context; 22 import android.os.Handler; 23 import android.text.format.DateUtils; 24 import android.text.format.Time; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.BaseAdapter; 29 import android.widget.TextView; 30 31 import java.util.Formatter; 32 import 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 42 public 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 CalendarViewAdapter(Context context, int viewType, boolean showDate)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. refresh(Context context)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 setMidnightHandler()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. onPause()133 public void onPause() { 134 mMidnightHandler.removeCallbacks(mTimeUpdater); 135 } 136 137 // Returns the amount of buttons in the menu 138 @Override getCount()139 public int getCount() { 140 return mButtonNames.length; 141 } 142 143 144 @Override getItem(int position)145 public Object getItem(int position) { 146 if (position < mButtonNames.length) { 147 return mButtonNames[position]; 148 } 149 return null; 150 } 151 152 @Override getItemId(int position)153 public long getItemId(int position) { 154 // Item ID is its location in the list 155 return position; 156 } 157 158 @Override hasStableIds()159 public boolean hasStableIds() { 160 return false; 161 } 162 163 @Override getView(int position, View convertView, ViewGroup parent)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 default: 202 v = null; 203 break; 204 } 205 } else { 206 if (convertView == null || ((Integer) convertView.getTag()).intValue() 207 != R.layout.actionbar_pulldown_menu_top_button_no_date) { 208 v = mInflater.inflate( 209 R.layout.actionbar_pulldown_menu_top_button_no_date, parent, false); 210 // Set the tag to make sure you can recycle it when you get it 211 // as a convert view 212 v.setTag(new Integer(R.layout.actionbar_pulldown_menu_top_button_no_date)); 213 } else { 214 v = convertView; 215 } 216 TextView title = (TextView) v; 217 switch (mCurrentMainView) { 218 case ViewType.DAY: 219 title.setText(mButtonNames [DAY_BUTTON_INDEX]); 220 break; 221 case ViewType.WEEK: 222 title.setText(mButtonNames [WEEK_BUTTON_INDEX]); 223 break; 224 case ViewType.MONTH: 225 title.setText(mButtonNames [MONTH_BUTTON_INDEX]); 226 break; 227 default: 228 v = null; 229 break; 230 } 231 } 232 return v; 233 } 234 235 @Override getItemViewType(int position)236 public int getItemViewType(int position) { 237 // Only one kind of view is used 238 return BUTTON_VIEW_TYPE; 239 } 240 241 @Override getViewTypeCount()242 public int getViewTypeCount() { 243 return VIEW_TYPE_NUM; 244 } 245 246 @Override isEmpty()247 public boolean isEmpty() { 248 return (mButtonNames.length == 0); 249 } 250 251 @Override getDropDownView(int position, View convertView, ViewGroup parent)252 public View getDropDownView(int position, View convertView, ViewGroup parent) { 253 View v = mInflater.inflate(R.layout.actionbar_pulldown_menu_button, parent, false); 254 TextView viewType = (TextView)v.findViewById(R.id.button_view); 255 TextView date = (TextView)v.findViewById(R.id.button_date); 256 switch (position) { 257 case DAY_BUTTON_INDEX: 258 viewType.setText(mButtonNames [DAY_BUTTON_INDEX]); 259 if (mShowDate) { 260 date.setText(buildMonthDayDate()); 261 } 262 break; 263 case WEEK_BUTTON_INDEX: 264 viewType.setText(mButtonNames [WEEK_BUTTON_INDEX]); 265 if (mShowDate) { 266 date.setText(buildWeekDate()); 267 } 268 break; 269 case MONTH_BUTTON_INDEX: 270 viewType.setText(mButtonNames [MONTH_BUTTON_INDEX]); 271 if (mShowDate) { 272 date.setText(buildMonthDate()); 273 } 274 break; 275 default: 276 v = convertView; 277 break; 278 } 279 return v; 280 } 281 282 // Updates the current viewType 283 // Used to match the label on the menu button with the calendar view setMainView(int viewType)284 public void setMainView(int viewType) { 285 mCurrentMainView = viewType; 286 notifyDataSetChanged(); 287 } 288 289 // Update the date that is displayed on buttons 290 // Used when the user selects a new day/week/month to watch setTime(long time)291 public void setTime(long time) { 292 mMilliTime = time; 293 notifyDataSetChanged(); 294 } 295 296 // Builds a string with the day of the week and the word yesterday/today/tomorrow 297 // before it if applicable. buildDayOfWeek()298 private String buildDayOfWeek() { 299 300 Time t = new Time(mTimeZone); 301 t.set(mMilliTime); 302 long julianDay = Time.getJulianDay(mMilliTime,t.gmtoff); 303 String dayOfWeek = null; 304 mStringBuilder.setLength(0); 305 306 if (julianDay == mTodayJulianDay) { 307 dayOfWeek = mContext.getString(R.string.agenda_today, 308 DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 309 DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()); 310 } else if (julianDay == mTodayJulianDay - 1) { 311 dayOfWeek = mContext.getString(R.string.agenda_yesterday, 312 DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 313 DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()); 314 } else if (julianDay == mTodayJulianDay + 1) { 315 dayOfWeek = mContext.getString(R.string.agenda_tomorrow, 316 DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 317 DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()); 318 } else { 319 dayOfWeek = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 320 DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString(); 321 } 322 return dayOfWeek.toUpperCase(); 323 } 324 325 // Builds strings with different formats: 326 // Full date: Month,day Year 327 // Month year 328 // Month day 329 // Month 330 // Week: month day-day or month day - month day buildFullDate()331 private String buildFullDate() { 332 mStringBuilder.setLength(0); 333 String date = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 334 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString(); 335 return date; 336 } 337 buildMonthYearDate()338 private String buildMonthYearDate() { 339 mStringBuilder.setLength(0); 340 String date = DateUtils.formatDateRange( 341 mContext, 342 mFormatter, 343 mMilliTime, 344 mMilliTime, 345 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY 346 | DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString(); 347 return date; 348 } 349 buildMonthDayDate()350 private String buildMonthDayDate() { 351 mStringBuilder.setLength(0); 352 String date = DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, 353 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR, mTimeZone).toString(); 354 return date; 355 } 356 buildMonthDate()357 private String buildMonthDate() { 358 mStringBuilder.setLength(0); 359 String date = DateUtils.formatDateRange( 360 mContext, 361 mFormatter, 362 mMilliTime, 363 mMilliTime, 364 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR 365 | DateUtils.FORMAT_NO_MONTH_DAY, mTimeZone).toString(); 366 return date; 367 } 368 buildWeekDate()369 private String buildWeekDate() { 370 // Calculate the start of the week, taking into account the "first day of the week" 371 // setting. 372 373 Time t = new Time(mTimeZone); 374 t.set(mMilliTime); 375 int firstDayOfWeek = Utils.getFirstDayOfWeek(mContext); 376 int dayOfWeek = t.weekDay; 377 int diff = dayOfWeek - firstDayOfWeek; 378 if (diff != 0) { 379 if (diff < 0) { 380 diff += 7; 381 } 382 t.monthDay -= diff; 383 t.normalize(true /* ignore isDst */); 384 } 385 386 long weekStartTime = t.toMillis(true); 387 // The end of the week is 6 days after the start of the week 388 long weekEndTime = weekStartTime + DateUtils.WEEK_IN_MILLIS - DateUtils.DAY_IN_MILLIS; 389 390 // If week start and end is in 2 different months, use short months names 391 Time t1 = new Time(mTimeZone); 392 t.set(weekEndTime); 393 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR; 394 if (t.month != t1.month) { 395 flags |= DateUtils.FORMAT_ABBREV_MONTH; 396 } 397 398 mStringBuilder.setLength(0); 399 String date = DateUtils.formatDateRange(mContext, mFormatter, weekStartTime, 400 weekEndTime, flags, mTimeZone).toString(); 401 return date; 402 } 403 buildWeekNum()404 private String buildWeekNum() { 405 int week = Utils.getWeekNumberFromTime(mMilliTime, mContext); 406 return mContext.getResources().getQuantityString(R.plurals.weekN, week, week); 407 } 408 409 } 410