1/*
2 * Copyright (C) 2013 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.recurrencepicker;
18
19import android.app.Activity;
20import android.app.DialogFragment;
21import android.content.Context;
22import android.content.res.Configuration;
23import android.content.res.Resources;
24import android.os.Bundle;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.text.Editable;
28import android.text.TextUtils;
29import android.text.TextWatcher;
30import android.text.format.DateUtils;
31import android.text.format.Time;
32import android.util.Log;
33import android.util.TimeFormatException;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.View.OnClickListener;
37import android.view.ViewGroup;
38import android.view.ViewGroup.LayoutParams;
39import android.view.Window;
40import android.widget.AdapterView;
41import android.widget.AdapterView.OnItemSelectedListener;
42import android.widget.ArrayAdapter;
43import android.widget.Button;
44import android.widget.CompoundButton;
45import android.widget.CompoundButton.OnCheckedChangeListener;
46import android.widget.EditText;
47import android.widget.LinearLayout;
48import android.widget.RadioButton;
49import android.widget.RadioGroup;
50import android.widget.Spinner;
51import android.widget.Switch;
52import android.widget.TableLayout;
53import android.widget.TextView;
54import android.widget.Toast;
55import android.widget.ToggleButton;
56
57import com.android.calendar.R;
58import com.android.calendar.Utils;
59import com.android.calendarcommon2.EventRecurrence;
60import com.android.datetimepicker.date.DatePickerDialog;
61
62import java.text.DateFormatSymbols;
63import java.util.ArrayList;
64import java.util.Arrays;
65import java.util.Calendar;
66
67public class RecurrencePickerDialog extends DialogFragment implements OnItemSelectedListener,
68        OnCheckedChangeListener, OnClickListener,
69        android.widget.RadioGroup.OnCheckedChangeListener, DatePickerDialog.OnDateSetListener {
70
71    private static final String TAG = "RecurrencePickerDialog";
72
73    // in dp's
74    private static final int MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK = 450;
75
76    // Update android:maxLength in EditText as needed
77    private static final int INTERVAL_MAX = 99;
78    private static final int INTERVAL_DEFAULT = 1;
79    // Update android:maxLength in EditText as needed
80    private static final int COUNT_MAX = 730;
81    private static final int COUNT_DEFAULT = 5;
82
83    // Special cases in monthlyByNthDayOfWeek
84    private static final int FIFTH_WEEK_IN_A_MONTH = 5;
85    private static final int LAST_NTH_DAY_OF_WEEK = -1;
86
87    private DatePickerDialog mDatePickerDialog;
88
89    private class RecurrenceModel implements Parcelable {
90
91        // Should match EventRecurrence.DAILY, etc
92        static final int FREQ_DAILY = 0;
93        static final int FREQ_WEEKLY = 1;
94        static final int FREQ_MONTHLY = 2;
95        static final int FREQ_YEARLY = 3;
96
97        static final int END_NEVER = 0;
98        static final int END_BY_DATE = 1;
99        static final int END_BY_COUNT = 2;
100
101        static final int MONTHLY_BY_DATE = 0;
102        static final int MONTHLY_BY_NTH_DAY_OF_WEEK = 1;
103
104        static final int STATE_NO_RECURRENCE = 0;
105        static final int STATE_RECURRENCE = 1;
106
107        int recurrenceState;
108
109        /**
110         * FREQ: Repeat pattern
111         *
112         * @see FREQ_DAILY
113         * @see FREQ_WEEKLY
114         * @see FREQ_MONTHLY
115         * @see FREQ_YEARLY
116         */
117        int freq = FREQ_WEEKLY;
118
119        /**
120         * INTERVAL: Every n days/weeks/months/years. n >= 1
121         */
122        int interval = INTERVAL_DEFAULT;
123
124        /**
125         * UNTIL and COUNT: How does the the event end?
126         *
127         * @see END_NEVER
128         * @see END_BY_DATE
129         * @see END_BY_COUNT
130         * @see untilDate
131         * @see untilCount
132         */
133        int end;
134
135        /**
136         * UNTIL: Date of the last recurrence. Used when until == END_BY_DATE
137         */
138        Time endDate;
139
140        /**
141         * COUNT: Times to repeat. Use when until == END_BY_COUNT
142         */
143        int endCount = COUNT_DEFAULT;
144
145        /**
146         * BYDAY: Days of the week to be repeated. Sun = 0, Mon = 1, etc
147         */
148        boolean[] weeklyByDayOfWeek = new boolean[7];
149
150        /**
151         * BYDAY AND BYMONTHDAY: How to repeat monthly events? Same date of the
152         * month or Same nth day of week.
153         *
154         * @see MONTHLY_BY_DATE
155         * @see MONTHLY_BY_NTH_DAY_OF_WEEK
156         */
157        int monthlyRepeat;
158
159        /**
160         * Day of the month to repeat. Used when monthlyRepeat ==
161         * MONTHLY_BY_DATE
162         */
163        int monthlyByMonthDay;
164
165        /**
166         * Day of the week to repeat. Used when monthlyRepeat ==
167         * MONTHLY_BY_NTH_DAY_OF_WEEK
168         */
169        int monthlyByDayOfWeek;
170
171        /**
172         * Nth day of the week to repeat. Used when monthlyRepeat ==
173         * MONTHLY_BY_NTH_DAY_OF_WEEK 0=undefined, -1=Last, 1=1st, 2=2nd, ..., 5=5th
174         *
175         * We support 5th, just to handle backwards capabilities with old bug, but it
176         * gets converted to -1 once edited.
177         */
178        int monthlyByNthDayOfWeek;
179
180        /*
181         * (generated method)
182         */
183        @Override
184        public String toString() {
185            return "Model [freq=" + freq + ", interval=" + interval + ", end=" + end + ", endDate="
186                    + endDate + ", endCount=" + endCount + ", weeklyByDayOfWeek="
187                    + Arrays.toString(weeklyByDayOfWeek) + ", monthlyRepeat=" + monthlyRepeat
188                    + ", monthlyByMonthDay=" + monthlyByMonthDay + ", monthlyByDayOfWeek="
189                    + monthlyByDayOfWeek + ", monthlyByNthDayOfWeek=" + monthlyByNthDayOfWeek + "]";
190        }
191
192        @Override
193        public int describeContents() {
194            return 0;
195        }
196
197        public RecurrenceModel() {
198        }
199
200        @Override
201        public void writeToParcel(Parcel dest, int flags) {
202            dest.writeInt(freq);
203            dest.writeInt(interval);
204            dest.writeInt(end);
205            dest.writeInt(endDate.year);
206            dest.writeInt(endDate.month);
207            dest.writeInt(endDate.monthDay);
208            dest.writeInt(endCount);
209            dest.writeBooleanArray(weeklyByDayOfWeek);
210            dest.writeInt(monthlyRepeat);
211            dest.writeInt(monthlyByMonthDay);
212            dest.writeInt(monthlyByDayOfWeek);
213            dest.writeInt(monthlyByNthDayOfWeek);
214            dest.writeInt(recurrenceState);
215        }
216    }
217
218    class minMaxTextWatcher implements TextWatcher {
219        private int mMin;
220        private int mMax;
221        private int mDefault;
222
223        public minMaxTextWatcher(int min, int defaultInt, int max) {
224            mMin = min;
225            mMax = max;
226            mDefault = defaultInt;
227        }
228
229        @Override
230        public void afterTextChanged(Editable s) {
231
232            boolean updated = false;
233            int value;
234            try {
235                value = Integer.parseInt(s.toString());
236            } catch (NumberFormatException e) {
237                value = mDefault;
238            }
239
240            if (value < mMin) {
241                value = mMin;
242                updated = true;
243            } else if (value > mMax) {
244                updated = true;
245                value = mMax;
246            }
247
248            // Update UI
249            if (updated) {
250                s.clear();
251                s.append(Integer.toString(value));
252            }
253
254            updateDoneButtonState();
255            onChange(value);
256        }
257
258        /** Override to be called after each key stroke */
259        void onChange(int value) {
260        }
261
262        @Override
263        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
264        }
265
266        @Override
267        public void onTextChanged(CharSequence s, int start, int before, int count) {
268        }
269    }
270
271    private Resources mResources;
272    private EventRecurrence mRecurrence = new EventRecurrence();
273    private Time mTime = new Time(); // TODO timezone?
274    private RecurrenceModel mModel = new RecurrenceModel();
275    private Toast mToast;
276
277    private final int[] TIME_DAY_TO_CALENDAR_DAY = new int[] {
278            Calendar.SUNDAY,
279            Calendar.MONDAY,
280            Calendar.TUESDAY,
281            Calendar.WEDNESDAY,
282            Calendar.THURSDAY,
283            Calendar.FRIDAY,
284            Calendar.SATURDAY,
285    };
286
287    // Call mStringBuilder.setLength(0) before formatting any string or else the
288    // formatted text will accumulate.
289    // private final StringBuilder mStringBuilder = new StringBuilder();
290    // private Formatter mFormatter = new Formatter(mStringBuilder);
291
292    private View mView;
293
294    private Spinner mFreqSpinner;
295    private static final int[] mFreqModelToEventRecurrence = {
296            EventRecurrence.DAILY,
297            EventRecurrence.WEEKLY,
298            EventRecurrence.MONTHLY,
299            EventRecurrence.YEARLY
300    };
301
302    public static final String BUNDLE_START_TIME_MILLIS = "bundle_event_start_time";
303    public static final String BUNDLE_TIME_ZONE = "bundle_event_time_zone";
304    public static final String BUNDLE_RRULE = "bundle_event_rrule";
305
306    private static final String BUNDLE_MODEL = "bundle_model";
307    private static final String BUNDLE_END_COUNT_HAS_FOCUS = "bundle_end_count_has_focus";
308
309    private static final String FRAG_TAG_DATE_PICKER = "tag_date_picker_frag";
310
311    private Switch mRepeatSwitch;
312
313    private EditText mInterval;
314    private TextView mIntervalPreText;
315    private TextView mIntervalPostText;
316
317    private int mIntervalResId = -1;
318
319    private Spinner mEndSpinner;
320    private TextView mEndDateTextView;
321    private EditText mEndCount;
322    private TextView mPostEndCount;
323    private boolean mHidePostEndCount;
324
325    private ArrayList<CharSequence> mEndSpinnerArray = new ArrayList<CharSequence>(3);
326    private EndSpinnerAdapter mEndSpinnerAdapter;
327    private String mEndNeverStr;
328    private String mEndDateLabel;
329    private String mEndCountLabel;
330
331    /** Hold toggle buttons in the order per user's first day of week preference */
332    private LinearLayout mWeekGroup;
333    private LinearLayout mWeekGroup2;
334    // Sun = 0
335    private ToggleButton[] mWeekByDayButtons = new ToggleButton[7];
336    /** A double array of Strings to hold the 7x5 list of possible strings of the form:
337     *  "on every [Nth] [DAY_OF_WEEK]", e.g. "on every second Monday",
338     *  where [Nth] can be [first, second, third, fourth, last] */
339    private String[][] mMonthRepeatByDayOfWeekStrs;
340
341    private LinearLayout mMonthGroup;
342    private RadioGroup mMonthRepeatByRadioGroup;
343    private RadioButton mRepeatMonthlyByNthDayOfWeek;
344    private RadioButton mRepeatMonthlyByNthDayOfMonth;
345    private String mMonthRepeatByDayOfWeekStr;
346
347    private Button mDone;
348
349    private OnRecurrenceSetListener mRecurrenceSetListener;
350
351    public RecurrencePickerDialog() {
352    }
353
354    static public boolean isSupportedMonthlyByNthDayOfWeek(int num) {
355        // We only support monthlyByNthDayOfWeek when it is greater then 0 but less then 5.
356        // Or if -1 when it is the last monthly day of the week.
357        return (num > 0 && num <= FIFTH_WEEK_IN_A_MONTH) || num == LAST_NTH_DAY_OF_WEEK;
358    }
359
360    static public boolean canHandleRecurrenceRule(EventRecurrence er) {
361        switch (er.freq) {
362            case EventRecurrence.DAILY:
363            case EventRecurrence.MONTHLY:
364            case EventRecurrence.YEARLY:
365            case EventRecurrence.WEEKLY:
366                break;
367            default:
368                return false;
369        }
370
371        if (er.count > 0 && !TextUtils.isEmpty(er.until)) {
372            return false;
373        }
374
375        // Weekly: For "repeat by day of week", the day of week to repeat is in
376        // er.byday[]
377
378        /*
379         * Monthly: For "repeat by nth day of week" the day of week to repeat is
380         * in er.byday[] and the "nth" is stored in er.bydayNum[]. Currently we
381         * can handle only one and only in monthly
382         */
383        int numOfByDayNum = 0;
384        for (int i = 0; i < er.bydayCount; i++) {
385            if (isSupportedMonthlyByNthDayOfWeek(er.bydayNum[i])) {
386                ++numOfByDayNum;
387            }
388        }
389
390        if (numOfByDayNum > 1) {
391            return false;
392        }
393
394        if (numOfByDayNum > 0 && er.freq != EventRecurrence.MONTHLY) {
395            return false;
396        }
397
398        // The UI only handle repeat by one day of month i.e. not 9th and 10th
399        // of every month
400        if (er.bymonthdayCount > 1) {
401            return false;
402        }
403
404        if (er.freq == EventRecurrence.MONTHLY) {
405            if (er.bydayCount > 1) {
406                return false;
407            }
408            if (er.bydayCount > 0 && er.bymonthdayCount > 0) {
409                return false;
410            }
411        }
412
413        return true;
414    }
415
416    // TODO don't lose data when getting data that our UI can't handle
417    static private void copyEventRecurrenceToModel(final EventRecurrence er,
418            RecurrenceModel model) {
419        // Freq:
420        switch (er.freq) {
421            case EventRecurrence.DAILY:
422                model.freq = RecurrenceModel.FREQ_DAILY;
423                break;
424            case EventRecurrence.MONTHLY:
425                model.freq = RecurrenceModel.FREQ_MONTHLY;
426                break;
427            case EventRecurrence.YEARLY:
428                model.freq = RecurrenceModel.FREQ_YEARLY;
429                break;
430            case EventRecurrence.WEEKLY:
431                model.freq = RecurrenceModel.FREQ_WEEKLY;
432                break;
433            default:
434                throw new IllegalStateException("freq=" + er.freq);
435        }
436
437        // Interval:
438        if (er.interval > 0) {
439            model.interval = er.interval;
440        }
441
442        // End:
443        // End by count:
444        model.endCount = er.count;
445        if (model.endCount > 0) {
446            model.end = RecurrenceModel.END_BY_COUNT;
447        }
448
449        // End by date:
450        if (!TextUtils.isEmpty(er.until)) {
451            if (model.endDate == null) {
452                model.endDate = new Time();
453            }
454
455            try {
456                model.endDate.parse(er.until);
457            } catch (TimeFormatException e) {
458                model.endDate = null;
459            }
460
461            // LIMITATION: The UI can only handle END_BY_DATE or END_BY_COUNT
462            if (model.end == RecurrenceModel.END_BY_COUNT && model.endDate != null) {
463                throw new IllegalStateException("freq=" + er.freq);
464            }
465
466            model.end = RecurrenceModel.END_BY_DATE;
467        }
468
469        // Weekly: repeat by day of week or Monthly: repeat by nth day of week
470        // in the month
471        Arrays.fill(model.weeklyByDayOfWeek, false);
472        if (er.bydayCount > 0) {
473            int count = 0;
474            for (int i = 0; i < er.bydayCount; i++) {
475                int dayOfWeek = EventRecurrence.day2TimeDay(er.byday[i]);
476                model.weeklyByDayOfWeek[dayOfWeek] = true;
477
478                if (model.freq == RecurrenceModel.FREQ_MONTHLY &&
479                        isSupportedMonthlyByNthDayOfWeek(er.bydayNum[i])) {
480                    // LIMITATION: Can handle only (one) weekDayNum in nth or last and only
481                    // when
482                    // monthly
483                    model.monthlyByDayOfWeek = dayOfWeek;
484                    model.monthlyByNthDayOfWeek = er.bydayNum[i];
485                    model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK;
486                    count++;
487                }
488            }
489
490            if (model.freq == RecurrenceModel.FREQ_MONTHLY) {
491                if (er.bydayCount != 1) {
492                    // Can't handle 1st Monday and 2nd Wed
493                    throw new IllegalStateException("Can handle only 1 byDayOfWeek in monthly");
494                }
495                if (count != 1) {
496                    throw new IllegalStateException(
497                            "Didn't specify which nth day of week to repeat for a monthly");
498                }
499            }
500        }
501
502        // Monthly by day of month
503        if (model.freq == RecurrenceModel.FREQ_MONTHLY) {
504            if (er.bymonthdayCount == 1) {
505                if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
506                    throw new IllegalStateException(
507                            "Can handle only by monthday or by nth day of week, not both");
508                }
509                model.monthlyByMonthDay = er.bymonthday[0];
510                model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE;
511            } else if (er.bymonthCount > 1) {
512                // LIMITATION: Can handle only one month day
513                throw new IllegalStateException("Can handle only one bymonthday");
514            }
515        }
516    }
517
518    static private void copyModelToEventRecurrence(final RecurrenceModel model,
519            EventRecurrence er) {
520        if (model.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
521            throw new IllegalStateException("There's no recurrence");
522        }
523
524        // Freq
525        er.freq = mFreqModelToEventRecurrence[model.freq];
526
527        // Interval
528        if (model.interval <= 1) {
529            er.interval = 0;
530        } else {
531            er.interval = model.interval;
532        }
533
534        // End
535        switch (model.end) {
536            case RecurrenceModel.END_BY_DATE:
537                if (model.endDate != null) {
538                    model.endDate.switchTimezone(Time.TIMEZONE_UTC);
539                    model.endDate.normalize(false);
540                    er.until = model.endDate.format2445();
541                    er.count = 0;
542                } else {
543                    throw new IllegalStateException("end = END_BY_DATE but endDate is null");
544                }
545                break;
546            case RecurrenceModel.END_BY_COUNT:
547                er.count = model.endCount;
548                er.until = null;
549                if (er.count <= 0) {
550                    throw new IllegalStateException("count is " + er.count);
551                }
552                break;
553            default:
554                er.count = 0;
555                er.until = null;
556                break;
557        }
558
559        // Weekly && monthly repeat patterns
560        er.bydayCount = 0;
561        er.bymonthdayCount = 0;
562
563        switch (model.freq) {
564            case RecurrenceModel.FREQ_MONTHLY:
565                if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) {
566                    if (model.monthlyByMonthDay > 0) {
567                        if (er.bymonthday == null || er.bymonthdayCount < 1) {
568                            er.bymonthday = new int[1];
569                        }
570                        er.bymonthday[0] = model.monthlyByMonthDay;
571                        er.bymonthdayCount = 1;
572                    }
573                } else if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
574                    if (!isSupportedMonthlyByNthDayOfWeek(model.monthlyByNthDayOfWeek)) {
575                        throw new IllegalStateException("month repeat by nth week but n is "
576                                + model.monthlyByNthDayOfWeek);
577                    }
578                    int count = 1;
579                    if (er.bydayCount < count || er.byday == null || er.bydayNum == null) {
580                        er.byday = new int[count];
581                        er.bydayNum = new int[count];
582                    }
583                    er.bydayCount = count;
584                    er.byday[0] = EventRecurrence.timeDay2Day(model.monthlyByDayOfWeek);
585                    er.bydayNum[0] = model.monthlyByNthDayOfWeek;
586                }
587                break;
588            case RecurrenceModel.FREQ_WEEKLY:
589                int count = 0;
590                for (int i = 0; i < 7; i++) {
591                    if (model.weeklyByDayOfWeek[i]) {
592                        count++;
593                    }
594                }
595
596                if (er.bydayCount < count || er.byday == null || er.bydayNum == null) {
597                    er.byday = new int[count];
598                    er.bydayNum = new int[count];
599                }
600                er.bydayCount = count;
601
602                for (int i = 6; i >= 0; i--) {
603                    if (model.weeklyByDayOfWeek[i]) {
604                        er.bydayNum[--count] = 0;
605                        er.byday[count] = EventRecurrence.timeDay2Day(i);
606                    }
607                }
608                break;
609        }
610
611        if (!canHandleRecurrenceRule(er)) {
612            throw new IllegalStateException("UI generated recurrence that it can't handle. ER:"
613                    + er.toString() + " Model: " + model.toString());
614        }
615    }
616
617    @Override
618    public View onCreateView(LayoutInflater inflater, ViewGroup container,
619            Bundle savedInstanceState) {
620        mRecurrence.wkst = EventRecurrence.timeDay2Day(Utils.getFirstDayOfWeek(getActivity()));
621
622        getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
623
624        boolean endCountHasFocus = false;
625        if (savedInstanceState != null) {
626            RecurrenceModel m = (RecurrenceModel) savedInstanceState.get(BUNDLE_MODEL);
627            if (m != null) {
628                mModel = m;
629            }
630            endCountHasFocus = savedInstanceState.getBoolean(BUNDLE_END_COUNT_HAS_FOCUS);
631        } else {
632            Bundle b = getArguments();
633            if (b != null) {
634                mTime.set(b.getLong(BUNDLE_START_TIME_MILLIS));
635
636                String tz = b.getString(BUNDLE_TIME_ZONE);
637                if (!TextUtils.isEmpty(tz)) {
638                    mTime.timezone = tz;
639                }
640                mTime.normalize(false);
641
642                // Time days of week: Sun=0, Mon=1, etc
643                mModel.weeklyByDayOfWeek[mTime.weekDay] = true;
644                String rrule = b.getString(BUNDLE_RRULE);
645                if (!TextUtils.isEmpty(rrule)) {
646                    mModel.recurrenceState = RecurrenceModel.STATE_RECURRENCE;
647                    mRecurrence.parse(rrule);
648                    copyEventRecurrenceToModel(mRecurrence, mModel);
649                    // Leave today's day of week as checked by default in weekly view.
650                    if (mRecurrence.bydayCount == 0) {
651                        mModel.weeklyByDayOfWeek[mTime.weekDay] = true;
652                    }
653                }
654
655            } else {
656                mTime.setToNow();
657            }
658        }
659
660        mResources = getResources();
661        mView = inflater.inflate(R.layout.recurrencepicker, container, true);
662
663        final Activity activity = getActivity();
664        final Configuration config = activity.getResources().getConfiguration();
665
666        mRepeatSwitch = (Switch) mView.findViewById(R.id.repeat_switch);
667        mRepeatSwitch.setChecked(mModel.recurrenceState == RecurrenceModel.STATE_RECURRENCE);
668        mRepeatSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
669
670            @Override
671            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
672                mModel.recurrenceState = isChecked ? RecurrenceModel.STATE_RECURRENCE
673                        : RecurrenceModel.STATE_NO_RECURRENCE;
674                togglePickerOptions();
675            }
676        });
677
678        mFreqSpinner = (Spinner) mView.findViewById(R.id.freqSpinner);
679        mFreqSpinner.setOnItemSelectedListener(this);
680        ArrayAdapter<CharSequence> freqAdapter = ArrayAdapter.createFromResource(getActivity(),
681                R.array.recurrence_freq, R.layout.recurrencepicker_freq_item);
682        freqAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item);
683        mFreqSpinner.setAdapter(freqAdapter);
684
685        mInterval = (EditText) mView.findViewById(R.id.interval);
686        mInterval.addTextChangedListener(new minMaxTextWatcher(1, INTERVAL_DEFAULT, INTERVAL_MAX) {
687            @Override
688            void onChange(int v) {
689                if (mIntervalResId != -1 && mInterval.getText().toString().length() > 0) {
690                    mModel.interval = v;
691                    updateIntervalText();
692                    mInterval.requestLayout();
693                }
694            }
695        });
696        mIntervalPreText = (TextView) mView.findViewById(R.id.intervalPreText);
697        mIntervalPostText = (TextView) mView.findViewById(R.id.intervalPostText);
698
699        mEndNeverStr = mResources.getString(R.string.recurrence_end_continously);
700        mEndDateLabel = mResources.getString(R.string.recurrence_end_date_label);
701        mEndCountLabel = mResources.getString(R.string.recurrence_end_count_label);
702
703        mEndSpinnerArray.add(mEndNeverStr);
704        mEndSpinnerArray.add(mEndDateLabel);
705        mEndSpinnerArray.add(mEndCountLabel);
706        mEndSpinner = (Spinner) mView.findViewById(R.id.endSpinner);
707        mEndSpinner.setOnItemSelectedListener(this);
708        mEndSpinnerAdapter = new EndSpinnerAdapter(getActivity(), mEndSpinnerArray,
709                R.layout.recurrencepicker_freq_item, R.layout.recurrencepicker_end_text);
710        mEndSpinnerAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item);
711        mEndSpinner.setAdapter(mEndSpinnerAdapter);
712
713        mEndCount = (EditText) mView.findViewById(R.id.endCount);
714        mEndCount.addTextChangedListener(new minMaxTextWatcher(1, COUNT_DEFAULT, COUNT_MAX) {
715            @Override
716            void onChange(int v) {
717                if (mModel.endCount != v) {
718                    mModel.endCount = v;
719                    updateEndCountText();
720                    mEndCount.requestLayout();
721                }
722            }
723        });
724        mPostEndCount = (TextView) mView.findViewById(R.id.postEndCount);
725
726        mEndDateTextView = (TextView) mView.findViewById(R.id.endDate);
727        mEndDateTextView.setOnClickListener(this);
728        if (mModel.endDate == null) {
729            mModel.endDate = new Time(mTime);
730            switch (mModel.freq) {
731                case RecurrenceModel.FREQ_DAILY:
732                case RecurrenceModel.FREQ_WEEKLY:
733                    mModel.endDate.month += 1;
734                    break;
735                case RecurrenceModel.FREQ_MONTHLY:
736                    mModel.endDate.month += 3;
737                    break;
738                case RecurrenceModel.FREQ_YEARLY:
739                    mModel.endDate.year += 3;
740                    break;
741            }
742            mModel.endDate.normalize(false);
743        }
744
745        mWeekGroup = (LinearLayout) mView.findViewById(R.id.weekGroup);
746        mWeekGroup2 = (LinearLayout) mView.findViewById(R.id.weekGroup2);
747
748        // In Calendar.java day of week order e.g Sun = 1 ... Sat = 7
749        String[] dayOfWeekString = new DateFormatSymbols().getWeekdays();
750
751        mMonthRepeatByDayOfWeekStrs = new String[7][];
752        // from Time.SUNDAY as 0 through Time.SATURDAY as 6
753        mMonthRepeatByDayOfWeekStrs[0] = mResources.getStringArray(R.array.repeat_by_nth_sun);
754        mMonthRepeatByDayOfWeekStrs[1] = mResources.getStringArray(R.array.repeat_by_nth_mon);
755        mMonthRepeatByDayOfWeekStrs[2] = mResources.getStringArray(R.array.repeat_by_nth_tues);
756        mMonthRepeatByDayOfWeekStrs[3] = mResources.getStringArray(R.array.repeat_by_nth_wed);
757        mMonthRepeatByDayOfWeekStrs[4] = mResources.getStringArray(R.array.repeat_by_nth_thurs);
758        mMonthRepeatByDayOfWeekStrs[5] = mResources.getStringArray(R.array.repeat_by_nth_fri);
759        mMonthRepeatByDayOfWeekStrs[6] = mResources.getStringArray(R.array.repeat_by_nth_sat);
760
761        // In Time.java day of week order e.g. Sun = 0
762        int idx = Utils.getFirstDayOfWeek(getActivity());
763
764        // In Calendar.java day of week order e.g Sun = 1 ... Sat = 7
765        dayOfWeekString = new DateFormatSymbols().getShortWeekdays();
766
767        int numOfButtonsInRow1;
768        int numOfButtonsInRow2;
769
770        if (mResources.getConfiguration().screenWidthDp > MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK) {
771            numOfButtonsInRow1 = 7;
772            numOfButtonsInRow2 = 0;
773            mWeekGroup2.setVisibility(View.GONE);
774            mWeekGroup2.getChildAt(3).setVisibility(View.GONE);
775        } else {
776            numOfButtonsInRow1 = 4;
777            numOfButtonsInRow2 = 3;
778
779            mWeekGroup2.setVisibility(View.VISIBLE);
780            // Set rightmost button on the second row invisible so it takes up
781            // space and everything centers properly
782            mWeekGroup2.getChildAt(3).setVisibility(View.INVISIBLE);
783        }
784
785        /* First row */
786        for (int i = 0; i < 7; i++) {
787            if (i >= numOfButtonsInRow1) {
788                mWeekGroup.getChildAt(i).setVisibility(View.GONE);
789                continue;
790            }
791
792            mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup.getChildAt(i);
793            mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
794            mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
795            mWeekByDayButtons[idx].setOnCheckedChangeListener(this);
796
797            if (++idx >= 7) {
798                idx = 0;
799            }
800        }
801
802        /* 2nd Row */
803        for (int i = 0; i < 3; i++) {
804            if (i >= numOfButtonsInRow2) {
805                mWeekGroup2.getChildAt(i).setVisibility(View.GONE);
806                continue;
807            }
808            mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup2.getChildAt(i);
809            mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
810            mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
811            mWeekByDayButtons[idx].setOnCheckedChangeListener(this);
812
813            if (++idx >= 7) {
814                idx = 0;
815            }
816        }
817
818        mMonthGroup = (LinearLayout) mView.findViewById(R.id.monthGroup);
819        mMonthRepeatByRadioGroup = (RadioGroup) mView.findViewById(R.id.monthGroup);
820        mMonthRepeatByRadioGroup.setOnCheckedChangeListener(this);
821        mRepeatMonthlyByNthDayOfWeek = (RadioButton) mView
822                .findViewById(R.id.repeatMonthlyByNthDayOfTheWeek);
823        mRepeatMonthlyByNthDayOfMonth = (RadioButton) mView
824                .findViewById(R.id.repeatMonthlyByNthDayOfMonth);
825
826        mDone = (Button) mView.findViewById(R.id.done);
827        mDone.setOnClickListener(this);
828
829        togglePickerOptions();
830        updateDialog();
831        if (endCountHasFocus) {
832            mEndCount.requestFocus();
833        }
834        return mView;
835    }
836
837    private void togglePickerOptions() {
838        if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
839            mFreqSpinner.setEnabled(false);
840            mEndSpinner.setEnabled(false);
841            mIntervalPreText.setEnabled(false);
842            mInterval.setEnabled(false);
843            mIntervalPostText.setEnabled(false);
844            mMonthRepeatByRadioGroup.setEnabled(false);
845            mEndCount.setEnabled(false);
846            mPostEndCount.setEnabled(false);
847            mEndDateTextView.setEnabled(false);
848            mRepeatMonthlyByNthDayOfWeek.setEnabled(false);
849            mRepeatMonthlyByNthDayOfMonth.setEnabled(false);
850            for (Button button : mWeekByDayButtons) {
851                button.setEnabled(false);
852            }
853        } else {
854            mView.findViewById(R.id.options).setEnabled(true);
855            mFreqSpinner.setEnabled(true);
856            mEndSpinner.setEnabled(true);
857            mIntervalPreText.setEnabled(true);
858            mInterval.setEnabled(true);
859            mIntervalPostText.setEnabled(true);
860            mMonthRepeatByRadioGroup.setEnabled(true);
861            mEndCount.setEnabled(true);
862            mPostEndCount.setEnabled(true);
863            mEndDateTextView.setEnabled(true);
864            mRepeatMonthlyByNthDayOfWeek.setEnabled(true);
865            mRepeatMonthlyByNthDayOfMonth.setEnabled(true);
866            for (Button button : mWeekByDayButtons) {
867                button.setEnabled(true);
868            }
869        }
870        updateDoneButtonState();
871    }
872
873    private void updateDoneButtonState() {
874        if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
875            mDone.setEnabled(true);
876            return;
877        }
878
879        if (mInterval.getText().toString().length() == 0) {
880            mDone.setEnabled(false);
881            return;
882        }
883
884        if (mEndCount.getVisibility() == View.VISIBLE &&
885                mEndCount.getText().toString().length() == 0) {
886            mDone.setEnabled(false);
887            return;
888        }
889
890        if (mModel.freq == RecurrenceModel.FREQ_WEEKLY) {
891            for (CompoundButton b : mWeekByDayButtons) {
892                if (b.isChecked()) {
893                    mDone.setEnabled(true);
894                    return;
895                }
896            }
897            mDone.setEnabled(false);
898            return;
899        }
900
901        mDone.setEnabled(true);
902    }
903
904    @Override
905    public void onSaveInstanceState(Bundle outState) {
906        super.onSaveInstanceState(outState);
907        outState.putParcelable(BUNDLE_MODEL, mModel);
908        if (mEndCount.hasFocus()) {
909            outState.putBoolean(BUNDLE_END_COUNT_HAS_FOCUS, true);
910        }
911    }
912
913    public void updateDialog() {
914        // Interval
915        // Checking before setting because this causes infinite recursion
916        // in afterTextWatcher
917        final String intervalStr = Integer.toString(mModel.interval);
918        if (!intervalStr.equals(mInterval.getText().toString())) {
919            mInterval.setText(intervalStr);
920        }
921
922        mFreqSpinner.setSelection(mModel.freq);
923        mWeekGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE);
924        mWeekGroup2.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE);
925        mMonthGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_MONTHLY ? View.VISIBLE : View.GONE);
926
927        switch (mModel.freq) {
928            case RecurrenceModel.FREQ_DAILY:
929                mIntervalResId = R.plurals.recurrence_interval_daily;
930                break;
931
932            case RecurrenceModel.FREQ_WEEKLY:
933                mIntervalResId = R.plurals.recurrence_interval_weekly;
934                for (int i = 0; i < 7; i++) {
935                    mWeekByDayButtons[i].setChecked(mModel.weeklyByDayOfWeek[i]);
936                }
937                break;
938
939            case RecurrenceModel.FREQ_MONTHLY:
940                mIntervalResId = R.plurals.recurrence_interval_monthly;
941
942                if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) {
943                    mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfMonth);
944                } else if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
945                    mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfTheWeek);
946                }
947
948                if (mMonthRepeatByDayOfWeekStr == null) {
949                    if (mModel.monthlyByNthDayOfWeek == 0) {
950                        mModel.monthlyByNthDayOfWeek = (mTime.monthDay + 6) / 7;
951                        // Since not all months have 5 weeks, we convert 5th NthDayOfWeek to
952                        // -1 for last monthly day of the week
953                        if (mModel.monthlyByNthDayOfWeek >= FIFTH_WEEK_IN_A_MONTH) {
954                            mModel.monthlyByNthDayOfWeek = LAST_NTH_DAY_OF_WEEK;
955                        }
956                        mModel.monthlyByDayOfWeek = mTime.weekDay;
957                    }
958
959                    String[] monthlyByNthDayOfWeekStrs =
960                            mMonthRepeatByDayOfWeekStrs[mModel.monthlyByDayOfWeek];
961
962                    // TODO(psliwowski): Find a better way handle -1 indexes
963                    int msgIndex = mModel.monthlyByNthDayOfWeek < 0 ? FIFTH_WEEK_IN_A_MONTH :
964                            mModel.monthlyByNthDayOfWeek;
965                    mMonthRepeatByDayOfWeekStr =
966                            monthlyByNthDayOfWeekStrs[msgIndex - 1];
967                    mRepeatMonthlyByNthDayOfWeek.setText(mMonthRepeatByDayOfWeekStr);
968                }
969                break;
970
971            case RecurrenceModel.FREQ_YEARLY:
972                mIntervalResId = R.plurals.recurrence_interval_yearly;
973                break;
974        }
975        updateIntervalText();
976        updateDoneButtonState();
977
978        mEndSpinner.setSelection(mModel.end);
979        if (mModel.end == RecurrenceModel.END_BY_DATE) {
980            final String dateStr = DateUtils.formatDateTime(getActivity(),
981                    mModel.endDate.toMillis(false), DateUtils.FORMAT_NUMERIC_DATE);
982            mEndDateTextView.setText(dateStr);
983        } else {
984            if (mModel.end == RecurrenceModel.END_BY_COUNT) {
985                // Checking before setting because this causes infinite
986                // recursion
987                // in afterTextWatcher
988                final String countStr = Integer.toString(mModel.endCount);
989                if (!countStr.equals(mEndCount.getText().toString())) {
990                    mEndCount.setText(countStr);
991                }
992            }
993        }
994    }
995
996    /**
997     * @param endDateString
998     */
999    private void setEndSpinnerEndDateStr(final String endDateString) {
1000        mEndSpinnerArray.set(1, endDateString);
1001        mEndSpinnerAdapter.notifyDataSetChanged();
1002    }
1003
1004    private void doToast() {
1005        Log.e(TAG, "Model = " + mModel.toString());
1006        String rrule;
1007        if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
1008            rrule = "Not repeating";
1009        } else {
1010            copyModelToEventRecurrence(mModel, mRecurrence);
1011            rrule = mRecurrence.toString();
1012        }
1013
1014        if (mToast != null) {
1015            mToast.cancel();
1016        }
1017        mToast = Toast.makeText(getActivity(), rrule,
1018                Toast.LENGTH_LONG);
1019        mToast.show();
1020    }
1021
1022    // TODO Test and update for Right-to-Left
1023    private void updateIntervalText() {
1024        if (mIntervalResId == -1) {
1025            return;
1026        }
1027
1028        final String INTERVAL_COUNT_MARKER = "%d";
1029        String intervalString = mResources.getQuantityString(mIntervalResId, mModel.interval);
1030        int markerStart = intervalString.indexOf(INTERVAL_COUNT_MARKER);
1031
1032        if (markerStart != -1) {
1033          int postTextStart = markerStart + INTERVAL_COUNT_MARKER.length();
1034          mIntervalPostText.setText(intervalString.substring(postTextStart,
1035                  intervalString.length()).trim());
1036          mIntervalPreText.setText(intervalString.substring(0, markerStart).trim());
1037        }
1038    }
1039
1040    /**
1041     * Update the "Repeat for N events" end option with the proper string values
1042     * based on the value that has been entered for N.
1043     */
1044    private void updateEndCountText() {
1045        final String END_COUNT_MARKER = "%d";
1046        String endString = mResources.getQuantityString(R.plurals.recurrence_end_count,
1047                mModel.endCount);
1048        int markerStart = endString.indexOf(END_COUNT_MARKER);
1049
1050        if (markerStart != -1) {
1051            if (markerStart == 0) {
1052                Log.e(TAG, "No text to put in to recurrence's end spinner.");
1053            } else {
1054                int postTextStart = markerStart + END_COUNT_MARKER.length();
1055                mPostEndCount.setText(endString.substring(postTextStart,
1056                        endString.length()).trim());
1057            }
1058        }
1059    }
1060
1061    // Implements OnItemSelectedListener interface
1062    // Freq spinner
1063    // End spinner
1064    @Override
1065    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1066        if (parent == mFreqSpinner) {
1067            mModel.freq = position;
1068        } else if (parent == mEndSpinner) {
1069            switch (position) {
1070                case RecurrenceModel.END_NEVER:
1071                    mModel.end = RecurrenceModel.END_NEVER;
1072                    break;
1073                case RecurrenceModel.END_BY_DATE:
1074                    mModel.end = RecurrenceModel.END_BY_DATE;
1075                    break;
1076                case RecurrenceModel.END_BY_COUNT:
1077                    mModel.end = RecurrenceModel.END_BY_COUNT;
1078
1079                    if (mModel.endCount <= 1) {
1080                        mModel.endCount = 1;
1081                    } else if (mModel.endCount > COUNT_MAX) {
1082                        mModel.endCount = COUNT_MAX;
1083                    }
1084                    updateEndCountText();
1085                    break;
1086            }
1087            mEndCount.setVisibility(mModel.end == RecurrenceModel.END_BY_COUNT ? View.VISIBLE
1088                    : View.GONE);
1089            mEndDateTextView.setVisibility(mModel.end == RecurrenceModel.END_BY_DATE ? View.VISIBLE
1090                    : View.GONE);
1091            mPostEndCount.setVisibility(
1092                    mModel.end == RecurrenceModel.END_BY_COUNT  && !mHidePostEndCount?
1093                            View.VISIBLE : View.GONE);
1094
1095        }
1096        updateDialog();
1097    }
1098
1099    // Implements OnItemSelectedListener interface
1100    @Override
1101    public void onNothingSelected(AdapterView<?> arg0) {
1102    }
1103
1104    @Override
1105    public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
1106        if (mModel.endDate == null) {
1107            mModel.endDate = new Time(mTime.timezone);
1108            mModel.endDate.hour = mModel.endDate.minute = mModel.endDate.second = 0;
1109        }
1110        mModel.endDate.year = year;
1111        mModel.endDate.month = monthOfYear;
1112        mModel.endDate.monthDay = dayOfMonth;
1113        mModel.endDate.normalize(false);
1114        updateDialog();
1115    }
1116
1117    // Implements OnCheckedChangeListener interface
1118    // Week repeat by day of week
1119    @Override
1120    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
1121        int itemIdx = -1;
1122        for (int i = 0; i < 7; i++) {
1123            if (itemIdx == -1 && buttonView == mWeekByDayButtons[i]) {
1124                itemIdx = i;
1125                mModel.weeklyByDayOfWeek[i] = isChecked;
1126            }
1127        }
1128        updateDialog();
1129    }
1130
1131    // Implements android.widget.RadioGroup.OnCheckedChangeListener interface
1132    // Month repeat by radio buttons
1133    @Override
1134    public void onCheckedChanged(RadioGroup group, int checkedId) {
1135        if (checkedId == R.id.repeatMonthlyByNthDayOfMonth) {
1136            mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE;
1137        } else if (checkedId == R.id.repeatMonthlyByNthDayOfTheWeek) {
1138            mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK;
1139        }
1140        updateDialog();
1141    }
1142
1143    // Implements OnClickListener interface
1144    // EndDate button
1145    // Done button
1146    @Override
1147    public void onClick(View v) {
1148        if (mEndDateTextView == v) {
1149            if (mDatePickerDialog != null) {
1150                mDatePickerDialog.dismiss();
1151            }
1152            mDatePickerDialog = DatePickerDialog.newInstance(this, mModel.endDate.year,
1153                    mModel.endDate.month, mModel.endDate.monthDay);
1154            mDatePickerDialog.setFirstDayOfWeek(Utils.getFirstDayOfWeekAsCalendar(getActivity()));
1155            mDatePickerDialog.setYearRange(Utils.YEAR_MIN, Utils.YEAR_MAX);
1156            mDatePickerDialog.show(getFragmentManager(), FRAG_TAG_DATE_PICKER);
1157        } else if (mDone == v) {
1158            String rrule;
1159            if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
1160                rrule = null;
1161            } else {
1162                copyModelToEventRecurrence(mModel, mRecurrence);
1163                rrule = mRecurrence.toString();
1164            }
1165            mRecurrenceSetListener.onRecurrenceSet(rrule);
1166            dismiss();
1167        }
1168    }
1169
1170    @Override
1171    public void onActivityCreated(Bundle savedInstanceState) {
1172        super.onActivityCreated(savedInstanceState);
1173        mDatePickerDialog = (DatePickerDialog) getFragmentManager()
1174                .findFragmentByTag(FRAG_TAG_DATE_PICKER);
1175        if (mDatePickerDialog != null) {
1176            mDatePickerDialog.setOnDateSetListener(this);
1177        }
1178    }
1179
1180    public interface OnRecurrenceSetListener {
1181        void onRecurrenceSet(String rrule);
1182    }
1183
1184    public void setOnRecurrenceSetListener(OnRecurrenceSetListener l) {
1185        mRecurrenceSetListener = l;
1186    }
1187
1188    private class EndSpinnerAdapter extends ArrayAdapter<CharSequence> {
1189        final String END_DATE_MARKER = "%s";
1190        final String END_COUNT_MARKER = "%d";
1191
1192        private LayoutInflater mInflater;
1193        private int mItemResourceId;
1194        private int mTextResourceId;
1195        private ArrayList<CharSequence> mStrings;
1196        private String mEndDateString;
1197        private boolean mUseFormStrings;
1198
1199        /**
1200         * @param context
1201         * @param textViewResourceId
1202         * @param objects
1203         */
1204        public EndSpinnerAdapter(Context context, ArrayList<CharSequence> strings,
1205                int itemResourceId, int textResourceId) {
1206            super(context, itemResourceId, strings);
1207            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1208            mItemResourceId = itemResourceId;
1209            mTextResourceId = textResourceId;
1210            mStrings = strings;
1211            mEndDateString = getResources().getString(R.string.recurrence_end_date);
1212
1213            // If either date or count strings don't translate well, such that we aren't assured
1214            // to have some text available to be placed in the spinner, then we'll have to use
1215            // the more form-like versions of both strings instead.
1216            int markerStart = mEndDateString.indexOf(END_DATE_MARKER);
1217            if (markerStart <= 0) {
1218                // The date string does not have any text before the "%s" so we'll have to use the
1219                // more form-like strings instead.
1220                mUseFormStrings = true;
1221            } else {
1222                String countEndStr = getResources().getQuantityString(
1223                        R.plurals.recurrence_end_count, 1);
1224                markerStart = countEndStr.indexOf(END_COUNT_MARKER);
1225                if (markerStart <= 0) {
1226                    // The count string does not have any text before the "%d" so we'll have to use
1227                    // the more form-like strings instead.
1228                    mUseFormStrings = true;
1229                }
1230            }
1231
1232            if (mUseFormStrings) {
1233                // We'll have to set the layout for the spinner to be weight=0 so it doesn't
1234                // take up too much space.
1235                mEndSpinner.setLayoutParams(
1236                        new TableLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f));
1237            }
1238        }
1239
1240        @Override
1241        public View getView(int position, View convertView, ViewGroup parent) {
1242            View v;
1243            // Check if we can recycle the view
1244            if (convertView == null) {
1245                v = mInflater.inflate(mTextResourceId, parent, false);
1246            } else {
1247                v = convertView;
1248            }
1249
1250            TextView item = (TextView) v.findViewById(R.id.spinner_item);
1251            int markerStart;
1252            switch (position) {
1253                case RecurrenceModel.END_NEVER:
1254                    item.setText(mStrings.get(RecurrenceModel.END_NEVER));
1255                    break;
1256                case RecurrenceModel.END_BY_DATE:
1257                    markerStart = mEndDateString.indexOf(END_DATE_MARKER);
1258
1259                    if (markerStart != -1) {
1260                        if (mUseFormStrings || markerStart == 0) {
1261                            // If we get here, the translation of "Until" doesn't work correctly,
1262                            // so we'll just set the whole "Until a date" string.
1263                            item.setText(mEndDateLabel);
1264                        } else {
1265                            item.setText(mEndDateString.substring(0, markerStart).trim());
1266                        }
1267                    }
1268                    break;
1269                case RecurrenceModel.END_BY_COUNT:
1270                    String endString = mResources.getQuantityString(R.plurals.recurrence_end_count,
1271                            mModel.endCount);
1272                    markerStart = endString.indexOf(END_COUNT_MARKER);
1273
1274                    if (markerStart != -1) {
1275                        if (mUseFormStrings || markerStart == 0) {
1276                            // If we get here, the translation of "For" doesn't work correctly,
1277                            // so we'll just set the whole "For a number of events" string.
1278                            item.setText(mEndCountLabel);
1279                            // Also, we'll hide the " events" that would have been at the end.
1280                            mPostEndCount.setVisibility(View.GONE);
1281                            // Use this flag so the onItemSelected knows whether to show it later.
1282                            mHidePostEndCount = true;
1283                        } else {
1284                            int postTextStart = markerStart + END_COUNT_MARKER.length();
1285                            mPostEndCount.setText(endString.substring(postTextStart,
1286                                    endString.length()).trim());
1287                            // In case it's a recycled view that wasn't visible.
1288                            if (mModel.end == RecurrenceModel.END_BY_COUNT) {
1289                                mPostEndCount.setVisibility(View.VISIBLE);
1290                            }
1291                            if (endString.charAt(markerStart - 1) == ' ') {
1292                                markerStart--;
1293                            }
1294                            item.setText(endString.substring(0, markerStart).trim());
1295                        }
1296                    }
1297                    break;
1298                default:
1299                    v = null;
1300                    break;
1301            }
1302
1303            return v;
1304        }
1305
1306        @Override
1307        public View getDropDownView(int position, View convertView, ViewGroup parent) {
1308            View v;
1309            // Check if we can recycle the view
1310            if (convertView == null) {
1311                v = mInflater.inflate(mItemResourceId, parent, false);
1312            } else {
1313                v = convertView;
1314            }
1315
1316            TextView item = (TextView) v.findViewById(R.id.spinner_item);
1317            item.setText(mStrings.get(position));
1318
1319            return v;
1320        }
1321    }
1322}
1323