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