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                 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
getItemViewType(int position)244     public int getItemViewType(int position) {
245         // Only one kind of view is used
246         return BUTTON_VIEW_TYPE;
247     }
248 
249     @Override
getViewTypeCount()250     public int getViewTypeCount() {
251         return VIEW_TYPE_NUM;
252     }
253 
254     @Override
isEmpty()255     public boolean isEmpty() {
256         return (mButtonNames.length == 0);
257     }
258 
259     @Override
getDropDownView(int position, View convertView, ViewGroup parent)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
setMainView(int viewType)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
setTime(long time)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.
buildDayOfWeek()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
buildFullDate()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 
buildMonthYearDate()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 
buildMonthDayDate()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 
buildMonthDate()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     }
buildWeekDate()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 
buildWeekNum()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