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