Home | History | Annotate | Download | only in recurrencepicker
      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     // 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