Bug 730330 - Use android datetime-picker widget. r=wesj
authorRaphael Catolino <rcatolino@mozilla.com>
Tue, 07 Aug 2012 17:09:15 +0200
changeset 107513 d38425445f835b1b1b4ce5fa7f3e319acd821494
parent 107512 42cec3ee2c80766f9c47c16239294f5e847691fb
child 107514 efd7b5216aa5ad8477126e68ac66a0dc3c9c8f2f
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewerswesj
bugs730330
milestone18.0a1
Bug 730330 - Use android datetime-picker widget. r=wesj
mobile/android/base/GeckoInputConnection.java
mobile/android/base/Makefile.in
mobile/android/base/PromptService.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/resources/layout/datetime_picker.xml
mobile/android/base/strings.xml.in
mobile/android/base/widget/DateTimePicker.java
mobile/android/base/widget/DatetimePicker.java
mobile/android/chrome/content/InputWidgetHelper.js
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -238,16 +238,22 @@ class GeckoInputConnection
         // Some IMEs call setSelection() with negative or stale indexes, so clamp them.
         Span newSelection = Span.clamp(start, end, mEditable);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(GeckoEvent.IME_SET_SELECTION,
                                                                  newSelection.start,
                                                                  newSelection.length));
         return super.setSelection(newSelection.start, newSelection.end);
     }
 
+    private static void postToUiThread(Runnable runnable) {
+        // postToUiThread() is called by the Gecko and TimerTask threads.
+        // The UI thread does not need to post Runnables to itself.
+        GeckoApp.mAppContext.mMainHandler.post(runnable);
+    }
+
     @Override
     public CharSequence getTextBeforeCursor(int length, int flags) {
         // Avoid underrunning text buffer.
         Span selection = getSelection();
         if (length > selection.start) {
             length = selection.start;
         }
 
@@ -986,26 +992,16 @@ class GeckoInputConnection
     }
 
     public boolean isIMEEnabled() {
         // make sure this picks up PASSWORD and PLUGIN states as well
         return mIMEState != IME_STATE_DISABLED;
     }
 
     public void notifyIME(int type, int state) {
-        // For some input type we will use a  widget to display the ui, for those we must not
-        // display the ime. We can display a widget for date and time types and, if the sdk version
-        // is greater than 11, for datetime/month/week as well.
-        if (typeHint.equals("date") || typeHint.equals("time") ||
-            (Build.VERSION.SDK_INT > 10 &&
-            (typeHint.equals("datetime") || typeHint.equals("month") ||
-            typeHint.equals("week") || typeHint.equals("datetime-local")))) {
-            return;
-        }
-
         View v = getView();
         if (v == null)
             return;
 
         switch (type) {
         case NOTIFY_IME_RESETINPUTSTATE:
             if (DEBUG) Log.d(LOGTAG, ". . . notifyIME: reset");
 
@@ -1043,16 +1039,26 @@ class GeckoInputConnection
         default:
             if (DEBUG)
                 throw new IllegalArgumentException("Unexpected NOTIFY_IME=" + type);
             break;
         }
     }
 
     public void notifyIMEEnabled(int state, String typeHint, final String modeHint, String actionHint) {
+        // For some input type we will use a  widget to display the ui, for those we must not
+        // display the ime. We can display a widget for date and time types and, if the sdk version
+        // is greater than 11, for datetime/month/week as well.
+        if (typeHint.equals("date") || typeHint.equals("time") ||
+            (Build.VERSION.SDK_INT > 10 &&
+            (typeHint.equals("datetime") || typeHint.equals("month") ||
+            typeHint.equals("week") || typeHint.equals("datetime-local")))) {
+            return;
+        }
+
         View v = getView();
 
         if (v == null)
             return;
 
         /* When IME is 'disabled', IME processing is disabled.
            In addition, the IME UI is hidden */
         mIMEState = state;
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -161,16 +161,17 @@ FENNEC_JAVA_FILES = \
   gfx/ViewTransform.java \
   gfx/ViewportMetrics.java \
   gfx/VirtualLayer.java \
   ui/Axis.java \
   ui/PanZoomController.java \
   ui/PanZoomTarget.java \
   ui/SimpleScaleGestureDetector.java \
   ui/SubdocumentScrollHelper.java \
+  widget/DateTimePicker.java \
   GeckoNetworkManager.java \
   GeckoScreenOrientationListener.java \
   UpdateService.java \
   GeckoUpdateReceiver.java \
   $(NULL)
 
 ifdef MOZ_WEBSMS_BACKEND
 FENNEC_JAVA_FILES += GeckoSmsManager.java
@@ -324,16 +325,17 @@ RES_LAYOUT = \
   res/layout/awesomebar_list.xml \
   res/layout/awesomebar_row.xml \
   res/layout/awesomebar_suggestion_item.xml \
   res/layout/awesomebar_suggestion_row.xml \
   res/layout/awesomebar_search.xml \
   res/layout/awesomebar_tab_indicator.xml \
   res/layout/awesomebar_tabs.xml \
   res/layout/bookmark_edit.xml \
+  res/layout/datetime_picker.xml \
   res/layout/doorhangerpopup.xml \
   res/layout/doorhanger.xml \
   res/layout/find_in_page_content.xml \
   res/layout/font_size_preference.xml \
   res/layout/web_app.xml \
   res/layout/launch_app_list.xml \
   res/layout/launch_app_listitem.xml \
   res/layout/menu_item.xml \
--- a/mobile/android/base/PromptService.java
+++ b/mobile/android/base/PromptService.java
@@ -320,45 +320,45 @@ public class PromptService implements On
             } else {
                 builder.setAdapter(adapter, this);
                 mSelected = null;
             }
         } else if (length == 1) {
             try {
                 builder.setView(mInputs[0].getView());
             } catch(UnsupportedOperationException ex) {
-              // We cannot display these input widgets with this sdk version,
-              // do not display any dialog and finish the prompt now.
-              finishDialog("{\"button\": -1}");
-              return;
+                // We cannot display these input widgets with this sdk version,
+                // do not display any dialog and finish the prompt now.
+                finishDialog("{\"button\": -1}");
+                return;
             }
         } else if (length > 1) {
             LinearLayout linearLayout = new LinearLayout(GeckoApp.mAppContext);
             linearLayout.setOrientation(LinearLayout.VERTICAL);
             try {
                 for (int i = 0; i < length; i++) {
                     View content = mInputs[i].getView();
                     linearLayout.addView(content);
                 }
             } catch(UnsupportedOperationException ex) {
-              // We cannot display these input widgets with this sdk version,
-              // do not display any dialog and finish the prompt now.
-              finishDialog("{\"button\": -1}");
-              return;
+                // We cannot display these input widgets with this sdk version,
+                // do not display any dialog and finish the prompt now.
+                finishDialog("{\"button\": -1}");
+                return;
             }
             builder.setView((View)linearLayout);
         }
 
         length = mButtons == null ? 0 : mButtons.length;
         if (length > 0) {
-            builder.setPositiveButton(mButtons[0].label, this);
+            builder.setPositiveButton(mButtons[0], this);
             if (length > 1) {
-                builder.setNeutralButton(mButtons[1].label, this);
+                builder.setNeutralButton(mButtons[1], this);
                 if (length > 2) {
-                    builder.setNegativeButton(mButtons[2].label, this);
+                    builder.setNegativeButton(mButtons[2], this);
                 }
             }
         }
 
         mDialog = builder.create();
         mDialog.setOnCancelListener(PromptService.this);
         mDialog.show();
     }
@@ -435,17 +435,17 @@ public class PromptService implements On
     public void finishDialog(String aReturn) {
         mInputs = null;
         mButtons = null;
         mDialog = null;
         mSelected = null;
         try {
             mPromptQueue.put(aReturn);
         } catch(Exception ex) {
-          Log.d(LOGTAG, "mPromptQueue not ready yet");
+            Log.d(LOGTAG, "mPromptQueue not ready yet");
         }
     }
 
     private void processMessage(JSONObject geckoObject) {
         String title = "";
         try {
             title = geckoObject.getString("title");
         } catch(Exception ex) { }
@@ -557,21 +557,21 @@ public class PromptService implements On
     }
 
     public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
         private static final int VIEW_TYPE_ITEM = 0;
         private static final int VIEW_TYPE_GROUP = 1;
         private static final int VIEW_TYPE_COUNT = 2;
 
         public ListView listView = null;
-    	private int mResourceId = -1;
-    	PromptListAdapter(Context context, int textViewResourceId, PromptListItem[] objects) {
+        private int mResourceId = -1;
+        PromptListAdapter(Context context, int textViewResourceId, PromptListItem[] objects) {
             super(context, textViewResourceId, objects);
             mResourceId = textViewResourceId;
-    	}
+        }
 
         @Override
         public int getItemViewType(int position) {
             PromptListItem item = getItem(position);
             return (item.isGroup ? VIEW_TYPE_GROUP : VIEW_TYPE_ITEM);
         }
 
         @Override
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -166,16 +166,18 @@ size. -->
 <!ENTITY masterpassword_create_title "Create Master Password">
 <!ENTITY masterpassword_remove_title "Remove Master Password">
 <!ENTITY masterpassword_password "Password">
 <!ENTITY masterpassword_confirm "Confirm password">
 
 <!ENTITY button_ok "OK">
 <!ENTITY button_cancel "Cancel">
 <!ENTITY button_clear_data "Clear data">
+<!ENTITY button_set "Set">
+<!ENTITY button_clear "Clear">
 
 <!ENTITY abouthome_addons_title "Add-ons for your &brandShortName;">
 <!ENTITY abouthome_addons_browse "Browse all &brandShortName; add-ons">
 <!ENTITY abouthome_last_tabs_title "Your tabs from last time">
 <!ENTITY abouthome_last_tabs_open "Open all tabs from last time">
 <!ENTITY abouthome_top_sites_title "Top sites">
 <!ENTITY abouthome_top_sites_browse "Browse all your top sites">
 <!-- Localization note (abouthome_about_sync3, abouthome_about_apps2): The
--- a/mobile/android/base/resources/layout/datetime_picker.xml
+++ b/mobile/android/base/resources/layout/datetime_picker.xml
@@ -17,70 +17,122 @@
 */
 -->
 
 <!-- Layout of date picker-->
 
 <!-- Warning: everything within the "pickers" layout is removed and re-ordered
      depending on the date format selected by the user.
 -->
+
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:id="@+id/datetime_picker"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:layout_gravity="center_horizontal"
     android:orientation="horizontal"
     android:gravity="center">
 
-    <LinearLayout android:id="@+id/pickers"
+    <LinearLayout android:id="@+id/spinners"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:orientation="horizontal"
+        android:orientation="vertical"
         android:gravity="center">
 
-        <!-- Month -->
-        <NumberPicker
-            android:id="@+id/month"
+        <LinearLayout android:id="@+id/date_spinners"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:orientation="horizontal"
             android:layout_marginLeft="1dip"
             android:layout_marginRight="1dip"
-            android:focusable="true"
-            android:focusableInTouchMode="true"
-            />
+            android:layout_weight="1"
+            android:gravity="center">
+
+            <!-- Month -->
+            <android.widget.NumberPicker
+                android:id="@+id/month"
+                android:layout_width="60dip"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="1dip"
+                android:layout_marginRight="1dip"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                />
+
+            <!-- Week -->
+            <android.widget.NumberPicker
+                android:id="@+id/week"
+                android:layout_width="60dip"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="1dip"
+                android:layout_marginRight="1dip"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                />
 
-        <!-- Day -->
-        <NumberPicker
-            android:id="@+id/day"
+            <!-- Day -->
+            <android.widget.NumberPicker
+                android:id="@+id/day"
+                android:layout_width="60dip"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="1dip"
+                android:layout_marginRight="1dip"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                />
+
+            <!-- Year -->
+            <android.widget.NumberPicker
+                android:id="@+id/year"
+                android:layout_width="75dip"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="1dip"
+                android:layout_marginRight="1dip"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                />
+
+        </LinearLayout>
+
+        <LinearLayout android:id="@+id/time_spinners"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:orientation="horizontal"
             android:layout_marginLeft="1dip"
             android:layout_marginRight="1dip"
-            android:focusable="true"
-            android:focusableInTouchMode="true"
-            />
+            android:layout_weight="1"
+            android:gravity="center">
+
+            <!-- Hour -->
+            <android.widget.NumberPicker
+                android:id="@+id/hour"
+                android:layout_width="60dip"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="1dip"
+                android:layout_marginRight="1dip"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                />
 
-        <!-- Year -->
-        <NumberPicker
-            android:id="@+id/year"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="1dip"
-            android:layout_marginRight="1dip"
-            android:focusable="true"
-            android:focusableInTouchMode="true"
-            />
+            <TextView android:id="@+id/mincolon"
+                android:text=":"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="1dip"
+                android:layout_marginRight="1dip"/>
+
+            <!-- Minute -->
+            <android.widget.NumberPicker
+                android:id="@+id/minute"
+                android:layout_width="60dip"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="1dip"
+                android:layout_marginRight="1dip"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                />
+
+        </LinearLayout>
 
     </LinearLayout>
 
-    <!-- calendar view -->
-    <CalendarView
-        android:id="@+id/calendar_view"
-        android:layout_width="245dip"
-        android:layout_height="280dip"
-        android:layout_marginLeft="44dip"
-        android:layout_weight="1"
-        android:focusable="true"
-        android:focusableInTouchMode="true"
-        android:visibility="gone"
-        />
-
 </LinearLayout>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -159,16 +159,18 @@
   <string name="masterpassword_create_title">&masterpassword_create_title;</string>
   <string name="masterpassword_remove_title">&masterpassword_remove_title;</string>
   <string name="masterpassword_password">&masterpassword_password;</string>
   <string name="masterpassword_confirm">&masterpassword_confirm;</string>
 
   <string name="button_ok">&button_ok;</string>
   <string name="button_cancel">&button_cancel;</string>
   <string name="button_clear_data">&button_clear_data;</string>
+  <string name="button_set">&button_set;</string>
+  <string name="button_clear">&button_clear;</string>
 
   <string name="abouthome_addons_title">&abouthome_addons_title;</string>
   <string name="abouthome_addons_browse">&abouthome_addons_browse;</string>
   <string name="abouthome_last_tabs_title">&abouthome_last_tabs_title;</string>
   <string name="abouthome_last_tabs_open">&abouthome_last_tabs_open;</string>
   <string name="abouthome_top_sites_title">&abouthome_top_sites_title;</string>
   <string name="abouthome_top_sites_browse">&abouthome_top_sites_browse;</string>
   <string name="abouthome_about_sync">&abouthome_about_sync3;</string>
rename from mobile/android/base/widget/DatetimePicker.java
rename to mobile/android/base/widget/DateTimePicker.java
--- a/mobile/android/base/widget/DatetimePicker.java
+++ b/mobile/android/base/widget/DateTimePicker.java
@@ -9,450 +9,563 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-package android.widget;
 
-import android.annotation.Widget;
+package org.mozilla.gecko.widget;
+
 import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
+import android.graphics.Point;
+import android.os.Build;
 import android.text.format.DateFormat;
 import android.text.format.DateUtils;
+import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseArray;
-import android.view.LayoutInflater;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.Display;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.NumberPicker.OnValueChangeListener;
-
-import com.android.internal.R;
-
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.OrientationEventListener;
+import android.view.WindowManager;
+import android.widget.CalendarView;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.NumberPicker;
+import android.widget.TextView;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Locale;
 import java.util.TimeZone;
 
-/**
- * This class is a widget for selecting a date. The date can be selected by a
- * year, month, and day spinners or a {@link CalendarView}. The set of spinners
- * and the calendar view are automatically synchronized. The client can
- * customize whether only the spinners, or only the calendar view, or both to be
- * displayed. Also the minimal and maximal date from which dates to be selected
- * can be customized.
- * <p>
- * See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date
- * Picker tutorial</a>.
- * </p>
- * <p>
- * For a dialog using this view, see {@link android.app.DatetimePickerDialog}.
- * </p>
- *
- * @attr ref android.R.styleable#DatetimePicker_startYear
- * @attr ref android.R.styleable#DatetimePicker_endYear
- * @attr ref android.R.styleable#DatetimePicker_maxDate
- * @attr ref android.R.styleable#DatetimePicker_minDate
- * @attr ref android.R.styleable#DatetimePicker_spinnersShown
- * @attr ref android.R.styleable#DatetimePicker_calendarViewShown
- */
-@Widget
-public class DatetimePicker extends FrameLayout {
-
-    private static final String LOG_TAG = DatetimePicker.class.getSimpleName();
-
-    private static final String DATE_FORMAT = "MM/dd/yyyy";
-
-    private static final int DEFAULT_START_YEAR = 1900;
-
-    private static final int DEFAULT_END_YEAR = 2100;
-
-    private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
-
-    private static final boolean DEFAULT_SPINNERS_SHOWN = true;
+import org.mozilla.gecko.R;
 
-    private static final boolean DEFAULT_ENABLED_STATE = true;
-
-    private final LinearLayout mSpinners;
-
-    private final NumberPicker mDaySpinner;
-
-    private final NumberPicker mMonthSpinner;
-
-    private final NumberPicker mYearSpinner;
-
-    private final EditText mDaySpinnerInput;
-
-    private final EditText mMonthSpinnerInput;
-
-    private final EditText mYearSpinnerInput;
-
-    private final CalendarView mCalendarView;
-
-    private Locale mCurrentLocale;
-
-    private OnDateChangedListener mOnDateChangedListener;
-
-    private String[] mShortMonths;
-
-    private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
-
-    private int mNumberOfMonths;
-
-    private Calendar mTempDate;
-
-    private Calendar mMinDate;
-
-    private Calendar mMaxDate;
-
-    private Calendar mCurrentDate;
-
-    private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
+public class DateTimePicker extends FrameLayout {
 
-    /**
-     * The callback used to indicate the user changes\d the date.
-     */
-    public interface OnDateChangedListener {
-
-        /**
-         * Called upon a date change.
-         *
-         * @param view The view associated with this listener.
-         * @param year The year that was set.
-         * @param monthOfYear The month that was set (0-11) for compatibility
-         *            with {@link java.util.Calendar}.
-         * @param dayOfMonth The day of the month that was set.
-         */
-        void onDateChanged(DatetimePicker view, int year, int monthOfYear, int dayOfMonth);
-    }
-
-    public DatetimePicker(Context context) {
-        this(context, null);
-    }
-
-    public DatetimePicker(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.datePickerStyle);
-    }
-
-    public DatetimePicker(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        // initialization based on locale
-        setCurrentLocale(Locale.getDefault());
+    private static final boolean DEBUG = true;
+    private static final String LOGTAG = "GeckoDateTimePicker";
+    private static final String DATE_FORMAT = "MM/dd/yyyy";
+    private static final int DEFAULT_START_YEAR = 1;
+    private static final int DEFAULT_END_YEAR = 9999;
+    // Minimal screen width (in inches) for which we can show the calendar;
+    private static final int SCREEN_SIZE_THRESHOLD = 5;
+    private boolean mYearEnabled = true;
+    private boolean mMonthEnabled = true;
+    private boolean mWeekEnabled = false;
+    private boolean mDayEnabled = true;
+    private boolean mHourEnabled = true;
+    private boolean mMinuteEnabled = true;
+    private boolean mCalendarEnabled = false;
+    // Size of the screen in inches;
+    private int mScreenWidth;
+    private int mScreenHeight;
+    private OnValueChangeListener mOnChangeListener;
+    private final LinearLayout mPickers;
+    private final LinearLayout mDateSpinners;
+    private final LinearLayout mTimeSpinners;
+    private final LinearLayout mSpinners;
+    private final NumberPicker mDaySpinner;
+    private final NumberPicker mMonthSpinner;
+    private final NumberPicker mWeekSpinner;
+    private final NumberPicker mYearSpinner;
+    private final NumberPicker mHourSpinner;
+    private final NumberPicker mMinuteSpinner;
+    private final CalendarView mCalendar;
+    private final EditText mDaySpinnerInput;
+    private final EditText mMonthSpinnerInput;
+    private final EditText mWeekSpinnerInput;
+    private final EditText mYearSpinnerInput;
+    private final EditText mHourSpinnerInput;
+    private final EditText mMinuteSpinnerInput;
+    private Locale mCurrentLocale;
+    private String[] mShortMonths;
+    private int mNumberOfMonths;
+    private Calendar mTempDate;
+    private Calendar mMinDate;
+    private Calendar mMaxDate;
+    private Calendar mCurrentDate;
+    private pickersState mState;
 
-        TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatetimePicker,
-                defStyle, 0);
-        boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatetimePicker_spinnersShown,
-                DEFAULT_SPINNERS_SHOWN);
-        boolean calendarViewShown = attributesArray.getBoolean(
-                R.styleable.DatetimePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
-        int startYear = attributesArray.getInt(R.styleable.DatetimePicker_startYear,
-                DEFAULT_START_YEAR);
-        int endYear = attributesArray.getInt(R.styleable.DatetimePicker_endYear, DEFAULT_END_YEAR);
-        String minDate = attributesArray.getString(R.styleable.DatetimePicker_minDate);
-        String maxDate = attributesArray.getString(R.styleable.DatetimePicker_maxDate);
-        int layoutResourceId = attributesArray.getResourceId(R.styleable.DatetimePicker_layout,
-                R.layout.datetime_picker);
-        attributesArray.recycle();
-
-        LayoutInflater inflater = (LayoutInflater) context
-                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(layoutResourceId, this, true);
+    public static enum pickersState { DATE, MONTH, WEEK, TIME, DATETIME };
 
-        OnValueChangeListener onChangeListener = new OnValueChangeListener() {
-            public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
-                updateInputState();
-                mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
-                // take care of wrapping of days and months to update greater fields
-                if (picker == mDaySpinner) {
+    public class OnValueChangeListener implements NumberPicker.OnValueChangeListener {
+        public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+            updateInputState();
+            mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
+            boolean newBehavior = (Build.VERSION.SDK_INT > 10);
+            if (newBehavior) {
+                if (DEBUG) Log.d(LOGTAG, "Sdk version > 10, using new behavior");
+                //The native date picker widget on these sdks increment
+                //the next field when one field reach the maximum
+                if (picker == mDaySpinner && mDayEnabled) {
                     int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
-                    if (oldVal == maxDayOfMonth && newVal == 1) {
-                        mTempDate.add(Calendar.DAY_OF_MONTH, 1);
-                    } else if (oldVal == 1 && newVal == maxDayOfMonth) {
-                        mTempDate.add(Calendar.DAY_OF_MONTH, -1);
-                    } else {
-                        mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
+                    int old = mTempDate.get(Calendar.DAY_OF_MONTH);
+                    setTempDate(Calendar.DAY_OF_MONTH, old, newVal, 1, maxDayOfMonth);
+                } else if (picker == mMonthSpinner && mMonthEnabled) {
+                    int old = mTempDate.get(Calendar.MONTH);
+                    setTempDate(Calendar.MONTH, old, newVal, 0, 11);
+                } else if (picker == mWeekSpinner) {
+                    int old = mTempDate.get(Calendar.WEEK_OF_YEAR);
+                    int maxWeekOfYear = mTempDate.getActualMaximum(Calendar.WEEK_OF_YEAR);
+                    setTempDate(Calendar.WEEK_OF_YEAR, old, newVal, 0, maxWeekOfYear);
+                } else if (picker == mYearSpinner && mYearEnabled) {
+                    int month=mTempDate.get(Calendar.MONTH);
+                    mTempDate.set(Calendar.YEAR,newVal);
+                    // Changing the year shouldn't change the month. (in case of non-leap year a Feb 29)
+                    // change the day instead;
+                    if (month != mTempDate.get(Calendar.MONTH)){
+                        mTempDate.set(Calendar.MONTH, month);
+                        mTempDate.set(Calendar.DAY_OF_MONTH,
+                                mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
                     }
-                } else if (picker == mMonthSpinner) {
-                    if (oldVal == 11 && newVal == 0) {
-                        mTempDate.add(Calendar.MONTH, 1);
-                    } else if (oldVal == 0 && newVal == 11) {
-                        mTempDate.add(Calendar.MONTH, -1);
-                    } else {
-                        mTempDate.add(Calendar.MONTH, newVal - oldVal);
-                    }
-                } else if (picker == mYearSpinner) {
-                    mTempDate.set(Calendar.YEAR, newVal);
+                } else if (picker == mHourSpinner && mHourEnabled) {
+                    setTempDate(Calendar.HOUR_OF_DAY, oldVal, newVal, 0, 23);
+                } else if (picker == mMinuteSpinner && mMinuteEnabled) {
+                    setTempDate(Calendar.MINUTE, oldVal, newVal, 0, 59);
                 } else {
                     throw new IllegalArgumentException();
                 }
-                // now set the date to the adjusted one
-                setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
-                        mTempDate.get(Calendar.DAY_OF_MONTH));
-                updateSpinners();
-                updateCalendarView();
-                notifyDateChanged();
-            }
-        };
-
-        mSpinners = (LinearLayout) findViewById(R.id.pickers);
-
-        // calendar view day-picker
-        mCalendarView = (CalendarView) findViewById(R.id.calendar_view);
-        mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
-            public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
-                setDate(year, month, monthDay);
-                updateSpinners();
-                notifyDateChanged();
+            } else {
+                if (DEBUG) Log.d(LOGTAG,"Sdk version < 10, using old behavior");
+                if (picker == mDaySpinner && mDayEnabled){
+                    mTempDate.set(Calendar.DAY_OF_MONTH, newVal);
+                } else if (picker == mMonthSpinner && mMonthEnabled){
+                    mTempDate.set(Calendar.MONTH, newVal);
+                    if (mTempDate.get(Calendar.MONTH) == newVal+1){
+                        mTempDate.set(Calendar.MONTH, newVal);
+                        mTempDate.set(Calendar.DAY_OF_MONTH,
+                                mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+                    }
+                } else if (picker == mWeekSpinner){
+                    mTempDate.set(Calendar.WEEK_OF_YEAR, newVal);
+                } else if (picker == mYearSpinner && mYearEnabled){
+                    int month=mTempDate.get(Calendar.MONTH);
+                    mTempDate.set(Calendar.YEAR, newVal);
+                    if (month != mTempDate.get(Calendar.MONTH)){
+                        mTempDate.set(Calendar.MONTH, month);
+                        mTempDate.set(Calendar.DAY_OF_MONTH,
+                                mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+                    }
+                } else if (picker == mHourSpinner && mHourEnabled){
+                    mTempDate.set(Calendar.HOUR_OF_DAY, newVal);
+                } else if (picker == mMinuteSpinner && mMinuteEnabled){
+                    mTempDate.set(Calendar.MINUTE, newVal);
+                } else {
+                  throw new IllegalArgumentException();
+                }
             }
-        });
-
-        // day
-        mDaySpinner = (NumberPicker) findViewById(R.id.day);
-        mDaySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
-        mDaySpinner.setOnLongPressUpdateInterval(100);
-        mDaySpinner.setOnValueChangedListener(onChangeListener);
-        mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
-
-        // month
-        mMonthSpinner = (NumberPicker) findViewById(R.id.month);
-        mMonthSpinner.setMinValue(0);
-        mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
-        mMonthSpinner.setDisplayedValues(mShortMonths);
-        mMonthSpinner.setOnLongPressUpdateInterval(200);
-        mMonthSpinner.setOnValueChangedListener(onChangeListener);
-        mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
-
-        // year
-        mYearSpinner = (NumberPicker) findViewById(R.id.year);
-        mYearSpinner.setOnLongPressUpdateInterval(100);
-        mYearSpinner.setOnValueChangedListener(onChangeListener);
-        mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
-
-        // show only what the user required but make sure we
-        // show something and the spinners have higher priority
-        if (!spinnersShown && !calendarViewShown) {
-            setSpinnersShown(true);
-        } else {
-            setSpinnersShown(spinnersShown);
-            setCalendarViewShown(calendarViewShown);
+            setDate(mTempDate);
+            if (mDayEnabled) {
+                mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+            }
+            if(mWeekEnabled) {
+               mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
+            }
+            updateCalendar();
+            updateSpinners();
+            notifyDateChanged();
         }
 
-        // set the min date giving priority of the minDate over startYear
-        mTempDate.clear();
-        if (!TextUtils.isEmpty(minDate)) {
-            if (!parseDate(minDate, mTempDate)) {
-                mTempDate.set(startYear, 0, 1);
-            }
-        } else {
-            mTempDate.set(startYear, 0, 1);
-        }
-        setMinDate(mTempDate.getTimeInMillis());
-
-        // set the max date giving priority of the maxDate over endYear
-        mTempDate.clear();
-        if (!TextUtils.isEmpty(maxDate)) {
-            if (!parseDate(maxDate, mTempDate)) {
-                mTempDate.set(endYear, 11, 31);
+        private void setTempDate(int field, int oldVal, int newVal, int min, int max) {
+            if (oldVal == max && newVal == min ) {
+                mTempDate.add(field, 1);
+            } else if (oldVal == min && newVal == max) {
+                mTempDate.add(field, -1);
+            } else {
+                mTempDate.add(field, newVal - oldVal);
             }
-        } else {
-            mTempDate.set(endYear, 11, 31);
         }
-        setMaxDate(mTempDate.getTimeInMillis());
+    }
+
+    private static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() {
+        final StringBuilder mBuilder = new StringBuilder();
+
+        final java.util.Formatter mFmt = new java.util.Formatter(mBuilder, java.util.Locale.US);
+
+        final Object[] mArgs = new Object[1];
 
-        // initialize to current date
-        mCurrentDate.setTimeInMillis(System.currentTimeMillis());
-        init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
-                .get(Calendar.DAY_OF_MONTH), null);
+        public String format(int value) {
+            mArgs[0] = value;
+            mBuilder.delete(0, mBuilder.length());
+            mFmt.format("%02d", mArgs);
+            return mFmt.toString();
+        }
+    };
 
-        // re-order the number spinners to match the current date format
-        reorderSpinners();
-
-        // set content descriptions
-        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
-            setContentDescriptions();
+    private void displayPickers() {
+        setWeekShown(false);
+        if (mState == pickersState.DATETIME) {
+            return;
+        }
+        setHourShown(false);
+        setMinuteShown(false);
+        if (mState == pickersState.WEEK) {
+            setDayShown(false);
+            setMonthShown(false);
+            setWeekShown(true);
+        } else if (mState == pickersState.MONTH) {
+            setDayShown(false);
         }
     }
 
-    /**
-     * Gets the minimal date supported by this {@link DatetimePicker} in
-     * milliseconds since January 1, 1970 00:00:00 in
-     * {@link TimeZone#getDefault()} time zone.
-     * <p>
-     * Note: The default minimal date is 01/01/1900.
-     * <p>
-     *
-     * @return The minimal supported date.
-     */
-    public long getMinDate() {
-        return mCalendarView.getMinDate();
+    public DateTimePicker(Context context) {
+        this(context, "", "", pickersState.DATE);
     }
 
-    /**
-     * Sets the minimal date supported by this {@link NumberPicker} in
-     * milliseconds since January 1, 1970 00:00:00 in
-     * {@link TimeZone#getDefault()} time zone.
-     *
-     * @param minDate The minimal supported date.
-     */
-    public void setMinDate(long minDate) {
-        mTempDate.setTimeInMillis(minDate);
-        if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
-                && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
-            return;
+    public DateTimePicker(Context context, String dateFormat, String dateTimeValue, pickersState state) {
+        super(context);
+        if (Build.VERSION.SDK_INT < 11) {
+            throw new UnsupportedOperationException("Custom DateTimePicker is only available for SDK > 10");
         }
-        mMinDate.setTimeInMillis(minDate);
-        mCalendarView.setMinDate(minDate);
-        if (mCurrentDate.before(mMinDate)) {
-            mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
-            updateCalendarView();
+        setCurrentLocale(Locale.getDefault());
+        mMinDate.set(DEFAULT_START_YEAR,1,1);
+        mMaxDate.set(DEFAULT_END_YEAR,12,31);
+        mState = state;
+        LayoutInflater inflater = LayoutInflater.from(context);
+        inflater.inflate(R.layout.datetime_picker, this, true);
+
+        mOnChangeListener = new OnValueChangeListener();
+
+        mDateSpinners = (LinearLayout)findViewById(R.id.date_spinners);
+        mTimeSpinners = (LinearLayout)findViewById(R.id.time_spinners);
+        mSpinners = (LinearLayout)findViewById(R.id.spinners);
+        mPickers = (LinearLayout)findViewById(R.id.datetime_picker);
+
+        // We will display differently according to the screen size width.
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = wm.getDefaultDisplay();
+        DisplayMetrics dm = new DisplayMetrics();
+        display.getMetrics(dm);
+        mScreenWidth = display.getWidth() / dm.densityDpi;
+        mScreenHeight = display.getHeight() / dm.densityDpi;
+        if (DEBUG) Log.d(LOGTAG, "screen width: " + mScreenWidth + " screen height: " + mScreenHeight);
+
+        // If we're displaying a date, the screen is wide enought (and if we're using a sdk where the calendar view exists)
+        // then display a calendar.
+        if ((mState == pickersState.DATE || mState == pickersState.DATETIME) &&
+            Build.VERSION.SDK_INT > 10 && mScreenWidth >= SCREEN_SIZE_THRESHOLD) {
+            if (DEBUG) Log.d(LOGTAG,"SDK > 10 and screen wide enough, displaying calendar");
+            mCalendar = new CalendarView(context);
+            mCalendar.setVisibility(GONE);
+
+            LayoutParams layoutParams = new LayoutParams(250,280);
+            mCalendar.setLayoutParams(layoutParams);
+            mCalendar.setFocusable(true);
+            mCalendar.setFocusableInTouchMode(true);
+            mCalendar.setMaxDate(mMaxDate.getTimeInMillis());
+            mCalendar.setMinDate(mMinDate.getTimeInMillis());
+
+            mCalendar.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
+                public void onSelectedDayChange(
+                    CalendarView view, int year, int month, int monthDay) {
+                    mTempDate.set(year, month, monthDay);
+                    setDate(mTempDate);
+                    notifyDateChanged();
+                }
+            });
+
+            mPickers.addView(mCalendar);
+        } else {
+          // If the screen is more wide than high, we are displaying daye and time spinners,
+          // and if there is no calendar displayed,
+          // we should display the fields in one row.
+            if (mScreenWidth > mScreenHeight && mState == pickersState.DATETIME) {
+                mSpinners.setOrientation(LinearLayout.HORIZONTAL);
+            }
+            mCalendar = null;
         }
-        updateSpinners();
+
+        // Find the initial date from the constructor arguments.
+        try {
+            if (!dateTimeValue.equals("")) {
+                mTempDate.setTime(new SimpleDateFormat(dateFormat).parse(dateTimeValue));
+            } else {
+                mTempDate.setTimeInMillis(System.currentTimeMillis());
+            }
+        } catch (Exception ex) {
+            Log.e(LOGTAG, "Error parsing format string: " + ex);
+            mTempDate.setTimeInMillis(System.currentTimeMillis());
+        }
+
+        // Initialize all spinners.
+        mDaySpinner = setupSpinner(R.id.day, 1,
+                                   mTempDate.get(Calendar.DAY_OF_MONTH));
+        mDaySpinner.setFormatter(TWO_DIGIT_FORMATTER);
+        mDaySpinnerInput = (EditText) mDaySpinner.getChildAt(1);
+
+        mMonthSpinner = setupSpinner(R.id.month, 1,
+                                     mTempDate.get(Calendar.MONTH));
+        mMonthSpinner.setFormatter(TWO_DIGIT_FORMATTER);
+        mMonthSpinner.setDisplayedValues(mShortMonths);
+        mMonthSpinnerInput = (EditText) mMonthSpinner.getChildAt(1);
+
+        mWeekSpinner = setupSpinner(R.id.week, 1,
+                                    mTempDate.get(Calendar.WEEK_OF_YEAR));
+        mWeekSpinner.setFormatter(TWO_DIGIT_FORMATTER);
+        mWeekSpinnerInput = (EditText) mWeekSpinner.getChildAt(1);
+
+        mYearSpinner = setupSpinner(R.id.year, DEFAULT_START_YEAR,
+                                    DEFAULT_END_YEAR);
+        mYearSpinnerInput = (EditText) mYearSpinner.getChildAt(1);
+
+        mHourSpinner = setupSpinner(R.id.hour, 0, 23);
+        mHourSpinner.setFormatter(TWO_DIGIT_FORMATTER);
+        mHourSpinnerInput = (EditText) mHourSpinner.getChildAt(1);
+
+        mMinuteSpinner = setupSpinner(R.id.minute, 0, 59);
+        mMinuteSpinner.setFormatter(TWO_DIGIT_FORMATTER);
+        mMinuteSpinnerInput = (EditText) mMinuteSpinner.getChildAt(1);
+
+        // The order in which the spinners are displayed are locale-dependent
+        reorderDateSpinners();
+        // Set the date to the initial date. Since this date can come from the user,
+        // it can fire an exception (out-of-bound date)
+        try {
+          updateDate(mTempDate);
+        } catch (Exception ex) { }
+
+        // Display only the pickers needed for the current state.
+        displayPickers();
     }
 
-    /**
-     * Gets the maximal date supported by this {@link DatetimePicker} in
-     * milliseconds since January 1, 1970 00:00:00 in
-     * {@link TimeZone#getDefault()} time zone.
-     * <p>
-     * Note: The default maximal date is 12/31/2100.
-     * <p>
-     *
-     * @return The maximal supported date.
-     */
-    public long getMaxDate() {
-        return mCalendarView.getMaxDate();
+    public NumberPicker setupSpinner(int id, int min, int max) {
+        NumberPicker mSpinner = (NumberPicker) findViewById(id);
+        mSpinner.setMinValue(min);
+        mSpinner.setMaxValue(max);
+        mSpinner.setOnValueChangedListener(mOnChangeListener);
+        mSpinner.setOnLongPressUpdateInterval(100);
+        return mSpinner;
+    }
+
+    public long getTimeInMillis(){
+        return mCurrentDate.getTimeInMillis();
+    }
+
+    private void reorderDateSpinners() {
+        mDateSpinners.removeAllViews();
+        char[] order = DateFormat.getDateFormatOrder(getContext());
+        final int spinnerCount = order.length;
+        for (int i = 0; i < spinnerCount; i++) {
+            switch (order[i]) {
+                case DateFormat.DATE:
+                    mDateSpinners.addView(mDaySpinner);
+                    break;
+                case DateFormat.MONTH:
+                    mDateSpinners.addView(mMonthSpinner);
+                    break;
+                case DateFormat.YEAR:
+                    mDateSpinners.addView(mYearSpinner);
+                    break;
+                default:
+                    throw new IllegalArgumentException();
+            }
+        }
+        mDateSpinners.addView(mWeekSpinner);
+    }
+
+    private void setDate(Calendar calendar){
+        mCurrentDate = mTempDate;
+        if (mCurrentDate.before(mMinDate)) {
+            mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
+        } else if (mCurrentDate.after(mMaxDate)) {
+            mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
+        }
+    }
+
+    private void updateInputState() {
+        InputMethodManager inputMethodManager = (InputMethodManager)
+          getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (mYearEnabled && inputMethodManager.isActive(mYearSpinnerInput)) {
+            mYearSpinnerInput.clearFocus();
+            inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+        } else if (mMonthEnabled && inputMethodManager.isActive(mMonthSpinnerInput)) {
+            mMonthSpinnerInput.clearFocus();
+            inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+        } else if (mDayEnabled && inputMethodManager.isActive(mDaySpinnerInput)) {
+            mDaySpinnerInput.clearFocus();
+            inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+        } else if (mHourEnabled && inputMethodManager.isActive(mHourSpinnerInput)) {
+            mHourSpinnerInput.clearFocus();
+            inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+        } else if (mMinuteEnabled && inputMethodManager.isActive(mMinuteSpinnerInput)) {
+            mMinuteSpinnerInput.clearFocus();
+            inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+        }
     }
 
-    /**
-     * Sets the maximal date supported by this {@link DatetimePicker} in
-     * milliseconds since January 1, 1970 00:00:00 in
-     * {@link TimeZone#getDefault()} time zone.
-     *
-     * @param maxDate The maximal supported date.
-     */
-    public void setMaxDate(long maxDate) {
-        mTempDate.setTimeInMillis(maxDate);
-        if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
-                && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
-            return;
+    private void updateSpinners() {
+        if (mDayEnabled) {
+            if (mCurrentDate.equals(mMinDate)) {
+                mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
+                mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+            } else if (mCurrentDate.equals(mMaxDate)) {
+                mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
+                mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
+            } else {
+                mDaySpinner.setMinValue(1);
+                mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+            }
+            mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
+        }
+
+        if (mWeekEnabled) {
+            mWeekSpinner.setMinValue(1);
+            mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
+            mWeekSpinner.setValue(mCurrentDate.get(Calendar.WEEK_OF_YEAR));
         }
-        mMaxDate.setTimeInMillis(maxDate);
-        mCalendarView.setMaxDate(maxDate);
-        if (mCurrentDate.after(mMaxDate)) {
-            mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
-            updateCalendarView();
+
+        if (mMonthEnabled) {
+            mMonthSpinner.setDisplayedValues(null);
+            if (mCurrentDate.equals(mMinDate)) {
+                mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
+                mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
+            } else if (mCurrentDate.equals(mMaxDate)) {
+                mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
+                mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
+            } else {
+                mMonthSpinner.setMinValue(0);
+                mMonthSpinner.setMaxValue(11);
+            }
+
+            String[] displayedValues = Arrays.copyOfRange(mShortMonths,
+                    mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
+            mMonthSpinner.setDisplayedValues(displayedValues);
+            mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
         }
-        updateSpinners();
+
+        if (mYearEnabled) {
+            mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
+            mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
+            mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
+        }
+
+        if (mHourEnabled) {
+            mHourSpinner.setValue(mCurrentDate.get(Calendar.HOUR_OF_DAY));
+        }
+        if (mMinuteEnabled) {
+            mMinuteSpinner.setValue(mCurrentDate.get(Calendar.MINUTE));
+        }
     }
 
-    @Override
-    public void setEnabled(boolean enabled) {
-        if (mIsEnabled == enabled) {
+    private void updateCalendar() {
+        if (mCalendarEnabled){
+            mCalendar.setDate(mCurrentDate.getTimeInMillis(), false, false);
+        }
+    }
+
+    private void notifyDateChanged() {
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+    }
+
+    public void toggleCalendar(boolean shown) {
+        if ((mState != pickersState.DATE && mState != pickersState.DATETIME) ||
+            Build.VERSION.SDK_INT < 11 || mScreenWidth < SCREEN_SIZE_THRESHOLD) {
+            if (DEBUG) Log.d(LOGTAG,"Cannot display calendar on this device, in this state" +
+                ": screen width :"+mScreenWidth);
             return;
         }
-        super.setEnabled(enabled);
-        mDaySpinner.setEnabled(enabled);
-        mMonthSpinner.setEnabled(enabled);
-        mYearSpinner.setEnabled(enabled);
-        mCalendarView.setEnabled(enabled);
-        mIsEnabled = enabled;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return mIsEnabled;
+        if (shown){
+            mCalendarEnabled = true;
+            mCalendar.setVisibility(VISIBLE);
+            setYearShown(false);
+            setWeekShown(false);
+            setMonthShown(false);
+            setDayShown(false);
+        } else {
+            mCalendar.setVisibility(GONE);
+            setYearShown(true);
+            setMonthShown(true);
+            setDayShown(true);
+            mSpinners.setOrientation(LinearLayout.HORIZONTAL);
+            mCalendarEnabled = false;
+        }
     }
 
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        onPopulateAccessibilityEvent(event);
-        return true;
+    private void setYearShown(boolean shown) {
+        if (shown) {
+            toggleCalendar(false);
+            mYearSpinner.setVisibility(VISIBLE);
+            mYearEnabled = true;
+        } else {
+            mYearSpinner.setVisibility(GONE);
+            mYearEnabled = false;
+        }
     }
 
-    @Override
-    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
-        super.onPopulateAccessibilityEvent(event);
-
-        final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
-        String selectedDateUtterance = DateUtils.formatDateTime(mContext,
-                mCurrentDate.getTimeInMillis(), flags);
-        event.getText().add(selectedDateUtterance);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        setCurrentLocale(newConfig.locale);
+    private void setWeekShown(boolean shown) {
+        if (shown) {
+            toggleCalendar(false);
+            mWeekSpinner.setVisibility(VISIBLE);
+            mWeekEnabled = true;
+        } else {
+            mWeekSpinner.setVisibility(GONE);
+            mWeekEnabled = false;
+        }
     }
 
-    /**
-     * Gets whether the {@link CalendarView} is shown.
-     *
-     * @return True if the calendar view is shown.
-     * @see #getCalendarView()
-     */
-    public boolean getCalendarViewShown() {
-        return mCalendarView.isShown();
+    private void setMonthShown(boolean shown) {
+        if (shown) {
+            toggleCalendar(false);
+            mMonthSpinner.setVisibility(VISIBLE);
+            mMonthEnabled = true;
+        } else {
+            mMonthSpinner.setVisibility(GONE);
+            mMonthEnabled = false;
+        }
     }
 
-    /**
-     * Gets the {@link CalendarView}.
-     *
-     * @return The calendar view.
-     * @see #getCalendarViewShown()
-     */
-    public CalendarView getCalendarView () {
-        return mCalendarView;
+    private void setDayShown(boolean shown) {
+        if (shown) {
+            toggleCalendar(false);
+            mDaySpinner.setVisibility(VISIBLE);
+            mDayEnabled = true;
+        } else {
+            mDaySpinner.setVisibility(GONE);
+            mDayEnabled = false;
+        }
     }
 
-    /**
-     * Sets whether the {@link CalendarView} is shown.
-     *
-     * @param shown True if the calendar view is to be shown.
-     */
-    public void setCalendarViewShown(boolean shown) {
-        mCalendarView.setVisibility(shown ? VISIBLE : GONE);
+    private void setHourShown(boolean shown) {
+        if (shown) {
+            mHourSpinner.setVisibility(VISIBLE);
+            mHourEnabled= true;
+        } else {
+            mHourSpinner.setVisibility(GONE);
+            mTimeSpinners.setVisibility(GONE);
+            mHourEnabled = false;
+        }
     }
 
-    /**
-     * Gets whether the spinners are shown.
-     *
-     * @return True if the spinners are shown.
-     */
-    public boolean getSpinnersShown() {
-        return mSpinners.isShown();
+    private void setMinuteShown(boolean shown) {
+        if (shown) {
+            mMinuteSpinner.setVisibility(VISIBLE);
+            mTimeSpinners.findViewById(R.id.mincolon).setVisibility(VISIBLE);
+            mMinuteEnabled= true;
+        } else {
+            mMinuteSpinner.setVisibility(GONE);
+            mTimeSpinners.findViewById(R.id.mincolon).setVisibility(GONE);
+            mMinuteEnabled = false;
+        }
     }
 
-    /**
-     * Sets whether the spinners are shown.
-     *
-     * @param shown True if the spinners are to be shown.
-     */
-    public void setSpinnersShown(boolean shown) {
-        mSpinners.setVisibility(shown ? VISIBLE : GONE);
-    }
-
-    /**
-     * Sets the current locale.
-     *
-     * @param locale The current locale.
-     */
     private void setCurrentLocale(Locale locale) {
         if (locale.equals(mCurrentLocale)) {
             return;
         }
 
         mCurrentLocale = locale;
 
         mTempDate = getCalendarForLocale(mTempDate, locale);
@@ -463,334 +576,35 @@ public class DatetimePicker extends Fram
         mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
         mShortMonths = new String[mNumberOfMonths];
         for (int i = 0; i < mNumberOfMonths; i++) {
             mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
                     DateUtils.LENGTH_MEDIUM);
         }
     }
 
-    /**
-     * Gets a calendar for locale bootstrapped with the value of a given calendar.
-     *
-     * @param oldCalendar The old calendar.
-     * @param locale The locale.
-     */
     private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
         if (oldCalendar == null) {
             return Calendar.getInstance(locale);
         } else {
             final long currentTimeMillis = oldCalendar.getTimeInMillis();
             Calendar newCalendar = Calendar.getInstance(locale);
             newCalendar.setTimeInMillis(currentTimeMillis);
             return newCalendar;
         }
     }
 
-    /**
-     * Reorders the spinners according to the date format that is
-     * explicitly set by the user and if no such is set fall back
-     * to the current locale's default format.
-     */
-    private void reorderSpinners() {
-        mSpinners.removeAllViews();
-        char[] order = DateFormat.getDateFormatOrder(getContext());
-        final int spinnerCount = order.length;
-        for (int i = 0; i < spinnerCount; i++) {
-            switch (order[i]) {
-                case DateFormat.DATE:
-                    mSpinners.addView(mDaySpinner);
-                    setImeOptions(mDaySpinner, spinnerCount, i);
-                    break;
-                case DateFormat.MONTH:
-                    mSpinners.addView(mMonthSpinner);
-                    setImeOptions(mMonthSpinner, spinnerCount, i);
-                    break;
-                case DateFormat.YEAR:
-                    mSpinners.addView(mYearSpinner);
-                    setImeOptions(mYearSpinner, spinnerCount, i);
-                    break;
-                default:
-                    throw new IllegalArgumentException();
-            }
-        }
-    }
-
-    /**
-     * Updates the current date.
-     *
-     * @param year The year.
-     * @param month The month which is <strong>starting from zero</strong>.
-     * @param dayOfMonth The day of the month.
-     */
-    public void updateDate(int year, int month, int dayOfMonth) {
-        if (!isNewDate(year, month, dayOfMonth)) {
+    public void updateDate(Calendar calendar) {
+        if (mCurrentDate.equals(calendar)) {
             return;
         }
-        setDate(year, month, dayOfMonth);
-        updateSpinners();
-        updateCalendarView();
-        notifyDateChanged();
-    }
-
-    // Override so we are in complete control of save / restore for this widget.
-    @Override
-    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
-        dispatchThawSelfOnly(container);
-    }
-
-    @Override
-    protected Parcelable onSaveInstanceState() {
-        Parcelable superState = super.onSaveInstanceState();
-        return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
-    }
-
-    @Override
-    protected void onRestoreInstanceState(Parcelable state) {
-        SavedState ss = (SavedState) state;
-        super.onRestoreInstanceState(ss.getSuperState());
-        setDate(ss.mYear, ss.mMonth, ss.mDay);
-        updateSpinners();
-        updateCalendarView();
-    }
-
-    /**
-     * Initialize the state. If the provided values designate an inconsistent
-     * date the values are normalized before updating the spinners.
-     *
-     * @param year The initial year.
-     * @param monthOfYear The initial month <strong>starting from zero</strong>.
-     * @param dayOfMonth The initial day of the month.
-     * @param onDateChangedListener How user is notified date is changed by
-     *            user, can be null.
-     */
-    public void init(int year, int monthOfYear, int dayOfMonth,
-            OnDateChangedListener onDateChangedListener) {
-        setDate(year, monthOfYear, dayOfMonth);
-        updateSpinners();
-        updateCalendarView();
-        mOnDateChangedListener = onDateChangedListener;
-    }
-
-    /**
-     * Parses the given <code>date</code> and in case of success sets the result
-     * to the <code>outDate</code>.
-     *
-     * @return True if the date was parsed.
-     */
-    private boolean parseDate(String date, Calendar outDate) {
-        try {
-            outDate.setTime(mDateFormat.parse(date));
-            return true;
-        } catch (ParseException e) {
-            Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
-            return false;
-        }
-    }
-
-    private boolean isNewDate(int year, int month, int dayOfMonth) {
-        return (mCurrentDate.get(Calendar.YEAR) != year
-                || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
-                || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
-    }
-
-    private void setDate(int year, int month, int dayOfMonth) {
-        mCurrentDate.set(year, month, dayOfMonth);
+        mCurrentDate.setTimeInMillis(calendar.getTimeInMillis());
         if (mCurrentDate.before(mMinDate)) {
             mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
         } else if (mCurrentDate.after(mMaxDate)) {
             mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
         }
-    }
-
-    private void updateSpinners() {
-        // set the spinner ranges respecting the min and max dates
-        if (mCurrentDate.equals(mMinDate)) {
-            mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
-            mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
-            mDaySpinner.setWrapSelectorWheel(false);
-            mMonthSpinner.setDisplayedValues(null);
-            mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
-            mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
-            mMonthSpinner.setWrapSelectorWheel(false);
-        } else if (mCurrentDate.equals(mMaxDate)) {
-            mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
-            mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
-            mDaySpinner.setWrapSelectorWheel(false);
-            mMonthSpinner.setDisplayedValues(null);
-            mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
-            mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
-            mMonthSpinner.setWrapSelectorWheel(false);
-        } else {
-            mDaySpinner.setMinValue(1);
-            mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
-            mDaySpinner.setWrapSelectorWheel(true);
-            mMonthSpinner.setDisplayedValues(null);
-            mMonthSpinner.setMinValue(0);
-            mMonthSpinner.setMaxValue(11);
-            mMonthSpinner.setWrapSelectorWheel(true);
-        }
-
-        // make sure the month names are a zero based array
-        // with the months in the month spinner
-        String[] displayedValues = Arrays.copyOfRange(mShortMonths,
-                mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
-        mMonthSpinner.setDisplayedValues(displayedValues);
-
-        // year spinner range does not change based on the current date
-        mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
-        mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
-        mYearSpinner.setWrapSelectorWheel(false);
-
-        // set the spinner values
-        mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
-        mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
-        mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
-    }
-
-    /**
-     * Updates the calendar view with the current date.
-     */
-    private void updateCalendarView() {
-         mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
-    }
-
-    /**
-     * @return The selected year.
-     */
-    public int getYear() {
-        return mCurrentDate.get(Calendar.YEAR);
-    }
-
-    /**
-     * @return The selected month.
-     */
-    public int getMonth() {
-        return mCurrentDate.get(Calendar.MONTH);
-    }
-
-    /**
-     * @return The selected day of month.
-     */
-    public int getDayOfMonth() {
-        return mCurrentDate.get(Calendar.DAY_OF_MONTH);
-    }
-
-    /**
-     * Notifies the listener, if such, for a change in the selected date.
-     */
-    private void notifyDateChanged() {
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
-        if (mOnDateChangedListener != null) {
-            mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
-        }
+        updateSpinners();
+        notifyDateChanged();
     }
 
-    /**
-     * Sets the IME options for a spinner based on its ordering.
-     *
-     * @param spinner The spinner.
-     * @param spinnerCount The total spinner count.
-     * @param spinnerIndex The index of the given spinner.
-     */
-    private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
-        final int imeOptions;
-        if (spinnerIndex < spinnerCount - 1) {
-            imeOptions = EditorInfo.IME_ACTION_NEXT;
-        } else {
-            imeOptions = EditorInfo.IME_ACTION_DONE;
-        }
-        TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
-        input.setImeOptions(imeOptions);
-    }
-
-    private void setContentDescriptions() {
-        // Day
-        String text = mContext.getString(R.string.datetime_picker_increment_day_button);
-        mDaySpinner.findViewById(R.id.increment).setContentDescription(text);
-        text = mContext.getString(R.string.datetime_picker_decrement_day_button);
-        mDaySpinner.findViewById(R.id.decrement).setContentDescription(text);
-        // Month
-        text = mContext.getString(R.string.datetime_picker_increment_month_button);
-        mMonthSpinner.findViewById(R.id.increment).setContentDescription(text);
-        text = mContext.getString(R.string.datetime_picker_decrement_month_button);
-        mMonthSpinner.findViewById(R.id.decrement).setContentDescription(text);
-        // Year
-        text = mContext.getString(R.string.datetime_picker_increment_year_button);
-        mYearSpinner.findViewById(R.id.increment).setContentDescription(text);
-        text = mContext.getString(R.string.datetime_picker_decrement_year_button);
-        mYearSpinner.findViewById(R.id.decrement).setContentDescription(text);
-    }
+}
 
-    private void updateInputState() {
-        // Make sure that if the user changes the value and the IME is active
-        // for one of the inputs if this widget, the IME is closed. If the user
-        // changed the value via the IME and there is a next input the IME will
-        // be shown, otherwise the user chose another means of changing the
-        // value and having the IME up makes no sense.
-        InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
-        if (inputMethodManager != null) {
-            if (inputMethodManager.isActive(mYearSpinnerInput)) {
-                mYearSpinnerInput.clearFocus();
-                inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
-            } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
-                mMonthSpinnerInput.clearFocus();
-                inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
-            } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
-                mDaySpinnerInput.clearFocus();
-                inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
-            }
-        }
-    }
-
-    /**
-     * Class for managing state storing/restoring.
-     */
-    private static class SavedState extends BaseSavedState {
-
-        private final int mYear;
-
-        private final int mMonth;
-
-        private final int mDay;
-
-        /**
-         * Constructor called from {@link DatetimePicker#onSaveInstanceState()}
-         */
-        private SavedState(Parcelable superState, int year, int month, int day) {
-            super(superState);
-            mYear = year;
-            mMonth = month;
-            mDay = day;
-        }
-
-        /**
-         * Constructor called from {@link #CREATOR}
-         */
-        private SavedState(Parcel in) {
-            super(in);
-            mYear = in.readInt();
-            mMonth = in.readInt();
-            mDay = in.readInt();
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            super.writeToParcel(dest, flags);
-            dest.writeInt(mYear);
-            dest.writeInt(mMonth);
-            dest.writeInt(mDay);
-        }
-
-        @SuppressWarnings("all")
-        // suppress unused and hiding
-        public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
-
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in);
-            }
-
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-    }
-}
--- a/mobile/android/chrome/content/InputWidgetHelper.js
+++ b/mobile/android/chrome/content/InputWidgetHelper.js
@@ -17,26 +17,27 @@ var InputWidgetHelper = {
       return;
 
     this._uiBusy = true;
     this.show(aTarget);
     this._uiBusy = false;
   },
 
   show: function(aElement) {
+    let type = aElement.getAttribute('type');
     let msg = {
       type: "Prompt:Show",
       title: Strings.browser.GetStringFromName("inputWidgetHelper." + aElement.getAttribute('type')),
       buttons: [
         { label: Strings.browser.GetStringFromName("inputWidgetHelper.set") },
         { label: Strings.browser.GetStringFromName("inputWidgetHelper.clear") },
         { label: Strings.browser.GetStringFromName("inputWidgetHelper.cancel") }
       ],
       inputs: [
-        { type: aElement.getAttribute('type'), value: aElement.value }
+        { type: type, value: aElement.value }
       ]
     };
 
     let data = JSON.parse(sendMessageToJava({ gecko: msg }));
 
     let changed = false;
     if (data.button == -1) {
       // This type is not supported with this android version.
@@ -45,18 +46,18 @@ var InputWidgetHelper = {
     if (data.button == 1) {
       // The user cleared the value.
       if (aElement.value != "") {
         aElement.value = "";
         changed = true;
       }
     } else if (data.button == 0) {
       // Commit the new value.
-      if (aElement.value != data[aElement.getAttribute('type')]) {
-        aElement.value = data[aElement.getAttribute('type')];
+      if (aElement.value != data[type]) {
+        aElement.value = data[type];
         changed = true;
       }
     }
     // Else the user canceled the input.
 
     if (changed)
       this.fireOnChange(aElement);
 
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -31,16 +31,17 @@ XPCOMUtils.defineLazyGetter(this, "NetUt
   Cu.import("resource://gre/modules/NetUtil.jsm");
   return NetUtil;
 });
 
 // Lazily-loaded browser scripts:
 [
   ["HelperApps", "chrome://browser/content/HelperApps.js"],
   ["SelectHelper", "chrome://browser/content/SelectHelper.js"],
+  ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
   ["AboutReader", "chrome://browser/content/aboutReader.js"],
   ["WebAppRT", "chrome://browser/content/WebAppRT.js"],
 ].forEach(function (aScript) {
   let [name, script] = aScript;
   XPCOMUtils.defineLazyGetter(window, name, function() {
     let sandbox = {};
     Services.scriptloader.loadSubScript(script, sandbox);
     return sandbox[name];