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