1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.app;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.StyleRes;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.DialogInterface.OnClickListener;
25import android.os.Bundle;
26import android.util.TypedValue;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.widget.Button;
30import android.widget.DatePicker;
31import android.widget.DatePicker.OnDateChangedListener;
32import android.widget.DatePicker.ValidationCallback;
33
34import com.android.internal.R;
35
36import java.util.Calendar;
37
38/**
39 * A simple dialog containing an {@link android.widget.DatePicker}.
40 * <p>
41 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
42 * guide.
43 */
44public class DatePickerDialog extends AlertDialog implements OnClickListener,
45        OnDateChangedListener {
46    private static final String YEAR = "year";
47    private static final String MONTH = "month";
48    private static final String DAY = "day";
49
50    private final DatePicker mDatePicker;
51
52    private OnDateSetListener mDateSetListener;
53
54    /**
55     * Creates a new date picker dialog for the current date using the parent
56     * context's default date picker dialog theme.
57     *
58     * @param context the parent context
59     */
60    public DatePickerDialog(@NonNull Context context) {
61        this(context, 0, null, Calendar.getInstance(), -1, -1, -1);
62    }
63
64    /**
65     * Creates a new date picker dialog for the current date.
66     *
67     * @param context the parent context
68     * @param themeResId the resource ID of the theme against which to inflate
69     *                   this dialog, or {@code 0} to use the parent
70     *                   {@code context}'s default alert dialog theme
71     */
72    public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId) {
73        this(context, themeResId, null, Calendar.getInstance(), -1, -1, -1);
74    }
75
76    /**
77     * Creates a new date picker dialog for the specified date using the parent
78     * context's default date picker dialog theme.
79     *
80     * @param context the parent context
81     * @param listener the listener to call when the user sets the date
82     * @param year the initially selected year
83     * @param month the initially selected month (0-11 for compatibility with
84     *              {@link Calendar#MONTH})
85     * @param dayOfMonth the initially selected day of month (1-31, depending
86     *                   on month)
87     */
88    public DatePickerDialog(@NonNull Context context, @Nullable OnDateSetListener listener,
89            int year, int month, int dayOfMonth) {
90        this(context, 0, listener, null, year, month, dayOfMonth);
91    }
92
93    /**
94     * Creates a new date picker dialog for the specified date.
95     *
96     * @param context the parent context
97     * @param themeResId the resource ID of the theme against which to inflate
98     *                   this dialog, or {@code 0} to use the parent
99     *                   {@code context}'s default alert dialog theme
100     * @param listener the listener to call when the user sets the date
101     * @param year the initially selected year
102     * @param monthOfYear the initially selected month of the year (0-11 for
103     *                    compatibility with {@link Calendar#MONTH})
104     * @param dayOfMonth the initially selected day of month (1-31, depending
105     *                   on month)
106     */
107    public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId,
108            @Nullable OnDateSetListener listener, int year, int monthOfYear, int dayOfMonth) {
109        this(context, themeResId, listener, null, year, monthOfYear, dayOfMonth);
110    }
111
112    private DatePickerDialog(@NonNull Context context, @StyleRes int themeResId,
113            @Nullable OnDateSetListener listener, @Nullable Calendar calendar, int year,
114            int monthOfYear, int dayOfMonth) {
115        super(context, resolveDialogTheme(context, themeResId));
116
117        final Context themeContext = getContext();
118        final LayoutInflater inflater = LayoutInflater.from(themeContext);
119        final View view = inflater.inflate(R.layout.date_picker_dialog, null);
120        setView(view);
121
122        setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this);
123        setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);
124        setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
125
126        if (calendar != null) {
127            year = calendar.get(Calendar.YEAR);
128            monthOfYear = calendar.get(Calendar.MONTH);
129            dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
130        }
131
132        mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
133        mDatePicker.init(year, monthOfYear, dayOfMonth, this);
134        mDatePicker.setValidationCallback(mValidationCallback);
135
136        mDateSetListener = listener;
137    }
138
139    static @StyleRes int resolveDialogTheme(@NonNull Context context, @StyleRes int themeResId) {
140        if (themeResId == 0) {
141            final TypedValue outValue = new TypedValue();
142            context.getTheme().resolveAttribute(R.attr.datePickerDialogTheme, outValue, true);
143            return outValue.resourceId;
144        } else {
145            return themeResId;
146        }
147    }
148
149    @Override
150    public void onDateChanged(@NonNull DatePicker view, int year, int month, int dayOfMonth) {
151        mDatePicker.init(year, month, dayOfMonth, this);
152    }
153
154    /**
155     * Sets the listener to call when the user sets the date.
156     *
157     * @param listener the listener to call when the user sets the date
158     */
159    public void setOnDateSetListener(@Nullable OnDateSetListener listener) {
160        mDateSetListener = listener;
161    }
162
163    @Override
164    public void onClick(@NonNull DialogInterface dialog, int which) {
165        switch (which) {
166            case BUTTON_POSITIVE:
167                if (mDateSetListener != null) {
168                    // Clearing focus forces the dialog to commit any pending
169                    // changes, e.g. typed text in a NumberPicker.
170                    mDatePicker.clearFocus();
171                    mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(),
172                            mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
173                }
174                break;
175            case BUTTON_NEGATIVE:
176                cancel();
177                break;
178        }
179    }
180
181    /**
182     * Returns the {@link DatePicker} contained in this dialog.
183     *
184     * @return the date picker
185     */
186    @NonNull
187    public DatePicker getDatePicker() {
188        return mDatePicker;
189    }
190
191    /**
192     * Sets the current date.
193     *
194     * @param year the year
195     * @param month the month (0-11 for compatibility with
196     *              {@link Calendar#MONTH})
197     * @param dayOfMonth the day of month (1-31, depending on month)
198     */
199    public void updateDate(int year, int month, int dayOfMonth) {
200        mDatePicker.updateDate(year, month, dayOfMonth);
201    }
202
203    @Override
204    public Bundle onSaveInstanceState() {
205        final Bundle state = super.onSaveInstanceState();
206        state.putInt(YEAR, mDatePicker.getYear());
207        state.putInt(MONTH, mDatePicker.getMonth());
208        state.putInt(DAY, mDatePicker.getDayOfMonth());
209        return state;
210    }
211
212    @Override
213    public void onRestoreInstanceState(Bundle savedInstanceState) {
214        super.onRestoreInstanceState(savedInstanceState);
215        final int year = savedInstanceState.getInt(YEAR);
216        final int month = savedInstanceState.getInt(MONTH);
217        final int day = savedInstanceState.getInt(DAY);
218        mDatePicker.init(year, month, day, this);
219    }
220
221    private final ValidationCallback mValidationCallback = new ValidationCallback() {
222        @Override
223        public void onValidationChanged(boolean valid) {
224            final Button positive = getButton(BUTTON_POSITIVE);
225            if (positive != null) {
226                positive.setEnabled(valid);
227            }
228        }
229    };
230
231    /**
232     * The listener used to indicate the user has finished selecting a date.
233     */
234    public interface OnDateSetListener {
235        /**
236         * @param view the picker associated with the dialog
237         * @param year the selected year
238         * @param month the selected month (0-11 for compatibility with
239         *              {@link Calendar#MONTH})
240         * @param dayOfMonth th selected day of the month (1-31, depending on
241         *                   month)
242         */
243        void onDateSet(DatePicker view, int year, int month, int dayOfMonth);
244    }
245}
246