summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Blitzstein <sblitz@google.com>2013-03-22 17:56:25 -0700
committerSam Blitzstein <sblitz@google.com>2013-03-29 15:36:18 -0700
commitb8f95646fc0510eebfeaa27864023d630f34090f (patch)
treeaa52ea44901c22e76975337cffbe0c9ff57bf920
parent3d5a23b698cb8c59f43914ea2f9bb4fb36575f88 (diff)
downloadandroid_frameworks_opt_datetimepicker-b8f95646fc0510eebfeaa27864023d630f34090f.tar.gz
android_frameworks_opt_datetimepicker-b8f95646fc0510eebfeaa27864023d630f34090f.tar.bz2
android_frameworks_opt_datetimepicker-b8f95646fc0510eebfeaa27864023d630f34090f.zip
Support for accessibility and hardware IME.
Change-Id: If6096d4105e78cad8e082091776213756b0ebde1
-rw-r--r--AndroidManifest.xml2
-rw-r--r--proguard.flags4
-rw-r--r--res/layout-land/time_picker_dialog.xml67
-rw-r--r--res/layout-sw600dp/time_picker_dialog.xml105
-rw-r--r--res/layout/time_header_label.xml74
-rw-r--r--res/layout/time_picker_dialog.xml67
-rw-r--r--res/values-v14/strings.xml18
-rw-r--r--res/values/arrays.xml62
-rw-r--r--res/values/colors.xml2
-rw-r--r--res/values/dimens.xml10
-rw-r--r--res/values/ids.xml19
-rw-r--r--res/values/strings.xml12
-rw-r--r--src/com/android/datetimepicker/FakeButton.java46
-rw-r--r--src/com/android/datetimepicker/TimePickerDialog.java307
-rw-r--r--src/com/android/datetimepicker/Utils.java25
-rw-r--r--src/com/android/datetimepicker/time/AmPmCirclesView.java (renamed from src/com/android/datetimepicker/AmPmCirclesView.java)12
-rw-r--r--src/com/android/datetimepicker/time/CircleView.java (renamed from src/com/android/datetimepicker/CircleView.java)7
-rw-r--r--src/com/android/datetimepicker/time/RadialPickerLayout.java (renamed from src/com/android/datetimepicker/TimePicker.java)346
-rw-r--r--src/com/android/datetimepicker/time/RadialSelectorView.java (renamed from src/com/android/datetimepicker/RadialSelectorView.java)28
-rw-r--r--src/com/android/datetimepicker/time/RadialTextsView.java (renamed from src/com/android/datetimepicker/RadialTextsView.java)9
-rw-r--r--src/com/android/datetimepicker/time/TimePickerDialog.java823
21 files changed, 1270 insertions, 775 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1b4672e..8ca36f9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,7 +16,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.datetimepicker"
android:versionCode="1" >
- <uses-sdk android:maxSdkVersion="17" android:minSdkVersion="14"/>
+ <uses-sdk android:maxSdkVersion="17" android:minSdkVersion="15"/>
<uses-permission android:name="android.permission.VIBRATE" />
</manifest>
diff --git a/proguard.flags b/proguard.flags
index 807160f..12d6a4c 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1,7 +1,7 @@
--keepclassmembers class com.android.datetimepicker.RadialSelectorView {
+-keepclassmembers class com.android.datetimepicker.time.RadialSelectorView {
*** setAnimationRadiusMultiplier(...);
}
--keepclassmembers class com.android.datetimepicker.RadialTextsView {
+-keepclassmembers class com.android.datetimepicker.time.RadialTextsView {
*** setAnimationRadiusMultiplier(...);
}
diff --git a/res/layout-land/time_picker_dialog.xml b/res/layout-land/time_picker_dialog.xml
index 2eb400f..e8c43ea 100644
--- a/res/layout-land/time_picker_dialog.xml
+++ b/res/layout-land/time_picker_dialog.xml
@@ -18,7 +18,9 @@
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_height"
android:orientation="horizontal"
- android:background="@color/gray" >
+ android:background="@color/black_03"
+ android:id="@+id/time_picker_dialog"
+ android:focusable="true" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -28,62 +30,11 @@
android:layout_height="0dip"
android:layout_weight="1"
android:background="@color/white" >
- <RelativeLayout
+ <include
+ layout="@layout/time_header_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center" >
- <View
- android:id="@+id/empty_view"
- android:layout_width="1dp"
- android:layout_height="1dp"
- android:background="#00000000"
- android:layout_centerInParent="true" />
- <TextView
- android:id="@+id/hours"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:textColor="@color/blue"
- android:layout_toLeftOf="@+id/separator"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <TextView
- android:id="@+id/separator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_separator"
- android:paddingLeft="@dimen/separator_padding"
- android:paddingRight="@dimen/separator_padding"
- android:layout_alignRight="@+id/empty_view"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <TextView
- android:id="@+id/minutes"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:layout_toRightOf="@+id/separator"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <View
- android:id="@+id/ampm_hitspace"
- android:layout_width="@dimen/ampm_label_size"
- android:layout_height="wrap_content"
- android:layout_alignTop="@+id/minutes"
- android:layout_alignBottom="@+id/minutes"
- android:layout_alignLeft="@+id/ampm_label"
- android:layout_alignRight="@+id/ampm_label" />
- <TextView
- android:id="@+id/ampm_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:paddingLeft="@dimen/ampm_left_padding"
- android:paddingRight="@dimen/ampm_left_padding"
- android:layout_toRightOf="@+id/minutes"
- android:layout_alignBaseline="@+id/minutes"
- style="@style/ampm_label" />
- </RelativeLayout>
+ android:layout_gravity="center" />
</FrameLayout>
<View
android:layout_width="match_parent"
@@ -103,10 +54,12 @@
android:text="@string/done_label" />
</LinearLayout>
</LinearLayout>
- <com.android.datetimepicker.TimePicker
+ <com.android.datetimepicker.time.RadialPickerLayout
android:id="@+id/time_picker"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
- android:gravity="center" />
+ android:gravity="center"
+ android:focusable="true"
+ android:focusableInTouchMode="true" />
</LinearLayout>
diff --git a/res/layout-sw600dp/time_picker_dialog.xml b/res/layout-sw600dp/time_picker_dialog.xml
deleted file mode 100644
index 3b5459f..0000000
--- a/res/layout-sw600dp/time_picker_dialog.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2013 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ 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
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@color/gray" >
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="@dimen/header_height"
- android:background="@color/white" >
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center" >
- <View
- android:id="@+id/empty_view"
- android:layout_width="1dp"
- android:layout_height="1dp"
- android:background="#00000000"
- android:layout_centerInParent="true" />
- <TextView
- android:id="@+id/hours"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:textColor="@color/blue"
- android:layout_toLeftOf="@+id/separator"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <TextView
- android:id="@+id/separator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_separator"
- android:paddingLeft="@dimen/separator_padding"
- android:paddingRight="@dimen/separator_padding"
- android:layout_alignRight="@+id/empty_view"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <TextView
- android:id="@+id/minutes"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:layout_toRightOf="@+id/separator"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <View
- android:id="@+id/ampm_hitspace"
- android:layout_width="@dimen/ampm_label_size"
- android:layout_height="wrap_content"
- android:layout_alignTop="@+id/minutes"
- android:layout_alignBottom="@+id/minutes"
- android:layout_alignLeft="@+id/ampm_label"
- android:layout_alignRight="@+id/ampm_label" />
- <TextView
- android:id="@+id/ampm_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:paddingLeft="@dimen/ampm_left_padding"
- android:paddingRight="@dimen/ampm_left_padding"
- android:layout_toRightOf="@+id/minutes"
- android:layout_alignBaseline="@+id/minutes"
- style="@style/ampm_label" />
- </RelativeLayout>
- </FrameLayout>
- <com.android.datetimepicker.TimePicker
- android:id="@+id/time_picker"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:gravity="center" />
- <View
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:background="@color/black_20" />
- <LinearLayout
- style="?android:attr/buttonBarStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal" >
- <Button
- android:id="@+id/done_button"
- style="?android:attr/buttonBarButtonStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/done_label" />
- </LinearLayout>
-</LinearLayout>
diff --git a/res/layout/time_header_label.xml b/res/layout/time_header_label.xml
new file mode 100644
index 0000000..8f64583
--- /dev/null
+++ b/res/layout/time_header_label.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2013 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ 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
+ -->
+ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" >
+ <View
+ android:id="@+id/empty_view"
+ android:layout_width="1dp"
+ android:layout_height="1dp"
+ android:background="#00000000"
+ android:layout_centerInParent="true" />
+ <com.android.datetimepicker.FakeButton
+ android:id="@+id/hours"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/time_placeholder"
+ android:textColor="@color/blue"
+ android:layout_toLeftOf="@+id/separator"
+ android:layout_centerVertical="true"
+ style="@style/time_label" />
+ <TextView
+ android:id="@+id/separator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/time_separator"
+ android:paddingLeft="@dimen/separator_padding"
+ android:paddingRight="@dimen/separator_padding"
+ android:layout_alignRight="@+id/empty_view"
+ android:layout_centerVertical="true"
+ style="@style/time_label"
+ android:importantForAccessibility="no" />
+ <com.android.datetimepicker.FakeButton
+ android:id="@+id/minutes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/time_placeholder"
+ android:layout_toRightOf="@+id/separator"
+ android:layout_centerVertical="true"
+ style="@style/time_label" />
+ <com.android.datetimepicker.FakeButton
+ android:id="@+id/ampm_hitspace"
+ android:layout_width="@dimen/ampm_label_size"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@+id/minutes"
+ android:layout_alignBottom="@+id/minutes"
+ android:layout_alignLeft="@+id/ampm_label"
+ android:layout_alignRight="@+id/ampm_label" />
+ <TextView
+ android:id="@+id/ampm_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/time_placeholder"
+ android:paddingLeft="@dimen/ampm_left_padding"
+ android:paddingRight="@dimen/ampm_left_padding"
+ android:layout_toRightOf="@+id/minutes"
+ android:layout_alignBaseline="@+id/minutes"
+ style="@style/ampm_label"
+ android:importantForAccessibility="no" />
+ </RelativeLayout> \ No newline at end of file
diff --git a/res/layout/time_picker_dialog.xml b/res/layout/time_picker_dialog.xml
index d1069dc..18b6d4c 100644
--- a/res/layout/time_picker_dialog.xml
+++ b/res/layout/time_picker_dialog.xml
@@ -18,74 +18,27 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:background="@color/gray" >
+ android:background="@color/black_03"
+ android:id="@+id/time_picker_dialog"
+ android:focusable="true" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="@dimen/header_height"
android:background="@color/white" >
- <RelativeLayout
+ <include
+ layout="@layout/time_header_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center" >
- <View
- android:id="@+id/empty_view"
- android:layout_width="1dp"
- android:layout_height="1dp"
- android:background="#00000000"
- android:layout_centerInParent="true" />
- <TextView
- android:id="@+id/hours"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:textColor="@color/blue"
- android:layout_toLeftOf="@+id/separator"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <TextView
- android:id="@+id/separator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_separator"
- android:paddingLeft="@dimen/separator_padding"
- android:paddingRight="@dimen/separator_padding"
- android:layout_alignRight="@+id/empty_view"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <TextView
- android:id="@+id/minutes"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:layout_toRightOf="@+id/separator"
- android:layout_centerVertical="true"
- style="@style/time_label" />
- <View
- android:id="@+id/ampm_hitspace"
- android:layout_width="@dimen/ampm_label_size"
- android:layout_height="wrap_content"
- android:layout_alignTop="@+id/minutes"
- android:layout_alignBottom="@+id/minutes"
- android:layout_alignLeft="@+id/ampm_label"
- android:layout_alignRight="@+id/ampm_label" />
- <TextView
- android:id="@+id/ampm_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/time_placeholder"
- android:paddingLeft="@dimen/ampm_left_padding"
- android:paddingRight="@dimen/ampm_left_padding"
- android:layout_toRightOf="@+id/minutes"
- android:layout_alignBaseline="@+id/minutes"
- style="@style/ampm_label" />
- </RelativeLayout>
+ android:layout_gravity="center" />
</FrameLayout>
- <com.android.datetimepicker.TimePicker
+ <com.android.datetimepicker.time.RadialPickerLayout
android:id="@+id/time_picker"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
- android:gravity="center" />
+ android:gravity="center"
+ android:focusable="true"
+ android:focusableInTouchMode="true" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
diff --git a/res/values-v14/strings.xml b/res/values-v14/strings.xml
deleted file mode 100644
index 66faff9..0000000
--- a/res/values-v14/strings.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- 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.
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
deleted file mode 100644
index b18b2a5..0000000
--- a/res/values/arrays.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- 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.
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string-array name="hours">
- <item>12</item>
- <item>1</item>
- <item>2</item>
- <item>3</item>
- <item>4</item>
- <item>5</item>
- <item>6</item>
- <item>7</item>
- <item>8</item>
- <item>9</item>
- <item>10</item>
- <item>11</item>
- </string-array>
-
- <string-array name="hours_24">
- <item>00</item>
- <item>13</item>
- <item>14</item>
- <item>15</item>
- <item>16</item>
- <item>17</item>
- <item>18</item>
- <item>19</item>
- <item>20</item>
- <item>21</item>
- <item>22</item>
- <item>23</item>
- </string-array>
-
- <string-array name="minutes">
- <item>00</item>
- <item>05</item>
- <item>10</item>
- <item>15</item>
- <item>20</item>
- <item>25</item>
- <item>30</item>
- <item>35</item>
- <item>40</item>
- <item>45</item>
- <item>50</item>
- <item>55</item>
- </string-array>
-</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index bbc87e2..8ce587e 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -16,7 +16,7 @@
<resources>
<color name="white">#ffffff</color>
- <color name="gray">#08000000</color>
+ <color name="black_03">#08000000</color>
<color name="black_20">#33000000</color>
<color name="black_50">#7F000000</color>
<color name="black_60">#99000000</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 86c163a..25090b2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -21,13 +21,13 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
xmlns:android="http://schemas.android.com/apk/res/android" >
<item name="circle_radius_multiplier" format="float" type="string">0.82</item>
- <item name="circle_radius_multiplier_24HourMode" format="float" type="string">0.85 </item>
+ <item name="circle_radius_multiplier_24HourMode" format="float" type="string">0.85</item>
<item name="selection_radius_multiplier" format="float" type="string">0.14</item>
<item name="ampm_circle_radius_multiplier" format="float" type="string">0.19</item>
- <item name="numbers_radius_multiplier_normal" format="float" type="string">0.81 </item>
- <item name="numbers_radius_multiplier_inner" format="float" type="string">0.60 </item>
- <item name="numbers_radius_multiplier_outer" format="float" type="string">0.83 </item>
- <item name="text_size_multiplier_normal" format="float" type="string">0.14</item>
+ <item name="numbers_radius_multiplier_normal" format="float" type="string">0.81</item>
+ <item name="numbers_radius_multiplier_inner" format="float" type="string">0.60</item>
+ <item name="numbers_radius_multiplier_outer" format="float" type="string">0.83</item>
+ <item name="text_size_multiplier_normal" format="float" type="string">0.17</item>
<item name="text_size_multiplier_inner" format="float" type="string">0.14</item>
<item name="text_size_multiplier_outer" format="float" type="string">0.11</item>
<dimen name="time_label_right_padding">12sp</dimen>
diff --git a/res/values/ids.xml b/res/values/ids.xml
deleted file mode 100644
index f4374b8..0000000
--- a/res/values/ids.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- 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.
--->
-
-<resources>
- <item type="id" name="numbers_key" />
-</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3614c30..0b46e21 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -16,15 +16,19 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">TimePicker</string>
+ <string name="done_label">Done</string>
+ <string name="hour_picker_description">Hours circular slider</string>
+ <string name="minute_picker_description">Minutes circular slider</string>
+ <string name="select_hours">Select hours</string>
+ <string name="select_minutes">Select minutes</string>
- <string name="trailing_hour_digit">1</string>
+ <!-- DO NOT TRANSLATE -->
<string name="time_placeholder">--</string>
+ <!-- DO NOT TRANSLATE -->
<string name="time_separator">:</string>
- <string name="done_label">Done</string>
- <string name="am_label">AM</string>
- <string name="pm_label">PM</string>
<!-- DO NOT TRANSLATE -->
<string name="radial_numbers_typeface">sans-serif</string>
+ <!-- DO NOT TRANSLATE -->
<string name="sans_serif">sans-serif</string>
</resources>
diff --git a/src/com/android/datetimepicker/FakeButton.java b/src/com/android/datetimepicker/FakeButton.java
new file mode 100644
index 0000000..6ab1b61
--- /dev/null
+++ b/src/com/android/datetimepicker/FakeButton.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.android.datetimepicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ */
+public class FakeButton extends TextView {
+
+ public FakeButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(Button.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(Button.class.getName());
+ }
+}
diff --git a/src/com/android/datetimepicker/TimePickerDialog.java b/src/com/android/datetimepicker/TimePickerDialog.java
deleted file mode 100644
index 2b3960d..0000000
--- a/src/com/android/datetimepicker/TimePickerDialog.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * 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 com.android.datetimepicker;
-
-import android.app.ActionBar.LayoutParams;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.text.style.AlignmentSpan;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import com.android.datetimepicker.R;
-
-import com.android.datetimepicker.TimePicker.OnValueSelectedListener;
-
-/**
- * Dialog to set a time.
- */
-public class TimePickerDialog extends DialogFragment implements OnValueSelectedListener{
- private static final String TAG = "TimePickerDialog";
-
- private static final String KEY_HOUR_OF_DAY = "hour_of_day";
- private static final String KEY_MINUTE = "minute";
- private static final String KEY_IS_24_HOUR_VIEW = "is_24_hour_view";
- private static final String KEY_CURRENT_ITEM_SHOWING = "current_item_showing";
- public static final int HOUR_INDEX = 0;
- public static final int MINUTE_INDEX = 1;
- public static final int AMPM_INDEX = 2; // NOT a real index for the purpose of what's showing.
- public static final int AM = 0;
- public static final int PM = 1;
-
- private Handler mHandler = new Handler();
-
- private OnTimeSetListener mCallback;
-
- private TextView mDoneButton;
- private TextView mHourView;
- private TextView mMinuteView;
- private TextView mAmPmTextView;
- private TimePicker mTimePicker;
-
- private int mBlue;
- private int mBlack;
- private String mAmText;
- private String mPmText;
-
- private boolean mAllowAutoAdvance;
- private int mInitialHourOfDay;
- private int mInitialMinute;
- private boolean mIs24HourMode;
- private int mWidthPixels;
-
- /**
- * The callback interface used to indicate the user is done filling in
- * the time (they clicked on the 'Set' button).
- */
- public interface OnTimeSetListener {
-
- /**
- * @param view The view associated with this listener.
- * @param hourOfDay The hour that was set.
- * @param minute The minute that was set.
- */
- void onTimeSet(TimePicker view, int hourOfDay, int minute);
- }
-
- public TimePickerDialog() {
- // Empty constructor required for dialog fragment.
- }
-
- public TimePickerDialog(Context context, int theme, OnTimeSetListener callback,
- int hourOfDay, int minute, boolean is24HourMode) {
- // Empty constructor required for dialog fragment.
- }
-
- public static TimePickerDialog newInstance(OnTimeSetListener callback,
- int hourOfDay, int minute, boolean is24HourMode) {
- TimePickerDialog ret = new TimePickerDialog();
- ret.initialize(callback, hourOfDay, minute, is24HourMode);
- return ret;
- }
-
- public void initialize(OnTimeSetListener callback,
- int hourOfDay, int minute, boolean is24HourMode) {
- mCallback = callback;
-
- mInitialHourOfDay = hourOfDay;
- mInitialMinute = minute;
- mIs24HourMode = is24HourMode;
- }
-
- public void setOnTimeSetListener(OnTimeSetListener callback) {
- mCallback = callback;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null && savedInstanceState.containsKey(KEY_HOUR_OF_DAY)
- && savedInstanceState.containsKey(KEY_MINUTE)
- && savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) {
- mInitialHourOfDay = savedInstanceState.getInt(KEY_HOUR_OF_DAY);
- mInitialMinute = savedInstanceState.getInt(KEY_MINUTE);
- mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
-
- View view = inflater.inflate(R.layout.time_picker_dialog, null);
- Resources res = getResources();
-
- mBlue = res.getColor(R.color.blue);
- mBlack = res.getColor(R.color.black_80);
-
- mHourView = (TextView) view.findViewById(R.id.hours);
- mMinuteView = (TextView) view.findViewById(R.id.minutes);
- mAmPmTextView = (TextView) view.findViewById(R.id.ampm_label);
- mAmText = res.getString(R.string.am_label);
- mPmText = res.getString(R.string.pm_label);
-
- mTimePicker = (TimePicker) view.findViewById(R.id.time_picker);
- mTimePicker.setOnValueSelectedListener(this);
- mTimePicker.initialize(getActivity(), mInitialHourOfDay, mInitialMinute, mIs24HourMode);
- int currentItemShowing = HOUR_INDEX;
- if (savedInstanceState != null &&
- savedInstanceState.containsKey(KEY_CURRENT_ITEM_SHOWING)) {
- currentItemShowing = savedInstanceState.getInt(KEY_CURRENT_ITEM_SHOWING);
- }
- setCurrentItemShowing(currentItemShowing, false);
- mTimePicker.invalidate();
-
- mHourView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- setCurrentItemShowing(HOUR_INDEX, true);
- mTimePicker.tryVibrate();
- }
- });
- mMinuteView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- setCurrentItemShowing(MINUTE_INDEX, true);
- mTimePicker.tryVibrate();
- }
- });
-
- mDoneButton = (TextView) view.findViewById(R.id.done_button);
- mDoneButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mTimePicker.tryVibrate();
- if (mCallback != null) {
- mCallback.onTimeSet(mTimePicker,
- mTimePicker.getHours(), mTimePicker.getMinutes());
- }
- dismiss();
- }
- });
-
- DisplayMetrics metrics = new DisplayMetrics();
- getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
- mWidthPixels = metrics.widthPixels;
-
- if (mIs24HourMode) {
- mAmPmTextView.setVisibility(View.GONE);
-
- RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams(
- LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- paramsSeparator.addRule(RelativeLayout.CENTER_IN_PARENT);
- TextView separatorView = (TextView) view.findViewById(R.id.separator);
- separatorView.setLayoutParams(paramsSeparator);
- } else {
- mAmPmTextView.setVisibility(View.VISIBLE);
- updateAmPmDisplay(mInitialHourOfDay < 12? AM : PM);
- View amPmHitspace = view.findViewById(R.id.ampm_hitspace);
- amPmHitspace.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mTimePicker.tryVibrate();
- int amOrPm = mTimePicker.getIsCurrentlyAmOrPm();
- if (amOrPm == AM) {
- amOrPm = PM;
- } else if (amOrPm == PM){
- amOrPm = AM;
- }
- updateAmPmDisplay(amOrPm);
- mTimePicker.setAmOrPm(amOrPm);
- }
- });
- }
-
- mAllowAutoAdvance = true;
- setHour(mInitialHourOfDay);
- setMinute(mInitialMinute);
-
- return view;
- }
-
- private void updateAmPmDisplay(int amOrPm) {
- if (amOrPm == AM) {
- mAmPmTextView.setText(mAmText);
- } else if (amOrPm == PM){
- mAmPmTextView.setText(mPmText);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- if (mTimePicker != null) {
- outState.putInt(KEY_HOUR_OF_DAY, mTimePicker.getHours());
- outState.putInt(KEY_MINUTE, mTimePicker.getMinutes());
- outState.putBoolean(KEY_IS_24_HOUR_VIEW, mIs24HourMode);
- outState.putInt(KEY_CURRENT_ITEM_SHOWING, mTimePicker.getCurrentItemShowing());
- }
- }
-
- @Override
- public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
- if (pickerIndex == HOUR_INDEX) {
- setHour(newValue);
- if (mAllowAutoAdvance && autoAdvance) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- setCurrentItemShowing(MINUTE_INDEX, true);
- }
- }, 150);
- }
- } else if (pickerIndex == MINUTE_INDEX){
- setMinute(newValue);
- } else if (pickerIndex == AMPM_INDEX) {
- updateAmPmDisplay(newValue);
- }
- }
-
- private void setHour(int value) {
- String format;
- if (mIs24HourMode) {
- format = "%02d";
- } else {
- format = "%d";
- value = value % 12;
- if (value == 0) {
- value = 12;
- }
- }
-
- mHourView.setText(String.format(format, value));
- }
-
- private void setMinute(int value) {
- if (value == 60) {
- value = 0;
- }
- mMinuteView.setText(String.format("%02d", value));
- }
-
- private void setCurrentItemShowing(int index, boolean animate) {
-/*
- if (mAllowAutoAdvance && index == 1) {
- // Once we've seen the minutes, no need to auto-advance.
- mAllowAutoAdvance = false;
- }
-*/
- mTimePicker.setCurrentItemShowing(index, animate);
- int hourColor = (index == HOUR_INDEX)? mBlue : mBlack;
- int minuteColor = (index == MINUTE_INDEX)? mBlue : mBlack;
- mHourView.setTextColor(hourColor);
- mMinuteView.setTextColor(minuteColor);
- }
-}
diff --git a/src/com/android/datetimepicker/Utils.java b/src/com/android/datetimepicker/Utils.java
new file mode 100644
index 0000000..8772fb8
--- /dev/null
+++ b/src/com/android/datetimepicker/Utils.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.android.datetimepicker;
+
+import android.os.Build;
+
+public class Utils {
+ public static boolean isJellybeanOrLater() {
+ return Build.VERSION.SDK_INT >= 16;
+ }
+}
diff --git a/src/com/android/datetimepicker/AmPmCirclesView.java b/src/com/android/datetimepicker/time/AmPmCirclesView.java
index 0e2bdb4..1ead926 100644
--- a/src/com/android/datetimepicker/AmPmCirclesView.java
+++ b/src/com/android/datetimepicker/time/AmPmCirclesView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.datetimepicker;
+package com.android.datetimepicker.time;
import android.content.Context;
import android.content.res.Resources;
@@ -22,14 +22,13 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
-import android.util.AttributeSet;
import android.util.Log;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnTouchListener;
import com.android.datetimepicker.R;
+import java.text.DateFormatSymbols;
+
public class AmPmCirclesView extends View {
private static final String TAG = "AmPmCirclesView";
@@ -79,8 +78,9 @@ public class AmPmCirclesView extends View {
Float.parseFloat(res.getString(R.string.circle_radius_multiplier));
mAmPmCircleRadiusMultiplier =
Float.parseFloat(res.getString(R.string.ampm_circle_radius_multiplier));
- mAmText = res.getString(R.string.am_label);
- mPmText = res.getString(R.string.pm_label);
+ String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
+ mAmText = amPmTexts[0];
+ mPmText = amPmTexts[1];
setAmOrPm(amOrPm);
mAmOrPmPressed = -1;
diff --git a/src/com/android/datetimepicker/CircleView.java b/src/com/android/datetimepicker/time/CircleView.java
index d7ec92a..b588db5 100644
--- a/src/com/android/datetimepicker/CircleView.java
+++ b/src/com/android/datetimepicker/time/CircleView.java
@@ -14,19 +14,14 @@
* limitations under the License.
*/
-package com.android.datetimepicker;
+package com.android.datetimepicker.time;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Typeface;
-import android.util.AttributeSet;
import android.util.Log;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnTouchListener;
import com.android.datetimepicker.R;
diff --git a/src/com/android/datetimepicker/TimePicker.java b/src/com/android/datetimepicker/time/RadialPickerLayout.java
index 3f4b4ef..7faa377 100644
--- a/src/com/android/datetimepicker/TimePicker.java
+++ b/src/com/android/datetimepicker/time/RadialPickerLayout.java
@@ -14,45 +14,45 @@
* limitations under the License.
*/
-package com.android.datetimepicker;
+package com.android.datetimepicker.time;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Typeface;
+import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.os.Vibrator;
+import android.text.format.DateUtils;
+import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
-import android.view.animation.AnimationSet;
-import android.view.animation.TranslateAnimation;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import com.android.datetimepicker.R;
-public class TimePicker extends FrameLayout implements OnTouchListener {
+public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
private static final String TAG = "TimePicker";
private final int TOUCH_SLOP;
private final int TAP_TIMEOUT;
- private final int PRESSED_STATE_DURATION;
private static final int HOUR_VALUE_TO_DEGREES_STEP_SIZE = 30;
private static final int MINUTE_VALUE_TO_DEGREES_STEP_SIZE = 6;
private static final int HOUR_INDEX = TimePickerDialog.HOUR_INDEX;
private static final int MINUTE_INDEX = TimePickerDialog.MINUTE_INDEX;
private static final int AMPM_INDEX = TimePickerDialog.AMPM_INDEX;
+ private static final int ENABLE_PICKER_INDEX = TimePickerDialog.ENABLE_PICKER_INDEX;
private static final int AM = TimePickerDialog.AM;
private static final int PM = TimePickerDialog.PM;
@@ -65,6 +65,7 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
private int mCurrentHoursOfDay;
private int mCurrentMinutes;
private boolean mIs24HourMode;
+ private boolean mHideAmPm;
private int mCurrentItemShowing;
private CircleView mCircleView;
@@ -73,14 +74,16 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
private RadialTextsView mMinuteRadialTextsView;
private RadialSelectorView mHourRadialSelectorView;
private RadialSelectorView mMinuteRadialSelectorView;
+ private View mGrayBox;
+ private boolean mInputEnabled;
private int mIsTouchingAmOrPm = -1;
private boolean mDoingMove;
+ private boolean mDoingTouch;
private int mDownDegrees;
private float mDownX;
private float mDownY;
-
- private ReselectSelectorRunnable mReselectSelectorRunnable;
+ private AccessibilityManager mAccessibilityManager;
private Handler mHandler = new Handler();
@@ -88,14 +91,13 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
}
- public TimePicker(Context context, AttributeSet attrs) {
+ public RadialPickerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOnTouchListener(this);
ViewConfiguration vc = ViewConfiguration.get(context);
TOUCH_SLOP = vc.getScaledTouchSlop();
TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
- PRESSED_STATE_DURATION = ViewConfiguration.getPressedStateDuration();
mDoingMove = false;
mCircleView = new CircleView(context);
@@ -114,13 +116,21 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
mMinuteRadialSelectorView = new RadialSelectorView(context);
addView(mMinuteRadialSelectorView);
- mReselectSelectorRunnable = new ReselectSelectorRunnable(this);
-
mVibrator = (Vibrator) context.getSystemService(Service.VIBRATOR_SERVICE);
mLastVibrate = 0;
mLastValueSelected = -1;
mTimeInitialized = false;
+
+ mInputEnabled = true;
+ mGrayBox = new View(context);
+ mGrayBox.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ mGrayBox.setBackgroundColor(getResources().getColor(R.color.black_50));
+ mGrayBox.setVisibility(View.INVISIBLE);
+ addView(mGrayBox);
+
+ mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
@Override
@@ -141,41 +151,67 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
Log.e(TAG, "Time has already been initialized.");
return;
}
-
- setValueForItem(HOUR_INDEX, initialHoursOfDay);
- setValueForItem(MINUTE_INDEX, initialMinutes);
mIs24HourMode = is24HourMode;
+ mHideAmPm = mAccessibilityManager.isTouchExplorationEnabled()? true : mIs24HourMode;
- mCircleView.initialize(context, is24HourMode);
+ mCircleView.initialize(context, mHideAmPm);
mCircleView.invalidate();
- if (!is24HourMode) {
+ if (!mHideAmPm) {
mAmPmCirclesView.initialize(context, initialHoursOfDay < 12? AM : PM);
mAmPmCirclesView.invalidate();
}
Resources res = context.getResources();
- String[] hoursTexts = res.getStringArray(is24HourMode? R.array.hours_24 : R.array.hours);
- String[] innerHoursTexts = res.getStringArray(R.array.hours);
- String[] minutesTexts = res.getStringArray(R.array.minutes);
+ int[] hours = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+ int[] hours_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
+ int[] minutes = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
+ String[] hoursTexts = new String[12];
+ String[] innerHoursTexts = new String[12];
+ String[] minutesTexts = new String[12];
+ for (int i = 0; i < 12; i++) {
+ hoursTexts[i] = is24HourMode?
+ String.format("%02d", hours_24[i]) : String.format("%d", hours[i]);
+ innerHoursTexts[i] = String.format("%d", hours[i]);
+ minutesTexts[i] = String.format("%02d", minutes[i]);
+ }
mHourRadialTextsView.initialize(res,
- hoursTexts, (is24HourMode? innerHoursTexts : null), is24HourMode, true);
+ hoursTexts, (is24HourMode? innerHoursTexts : null), mHideAmPm, true);
mHourRadialTextsView.invalidate();
- mMinuteRadialTextsView.initialize(res, minutesTexts, null, is24HourMode, false);
+ mMinuteRadialTextsView.initialize(res, minutesTexts, null, mHideAmPm, false);
mMinuteRadialTextsView.invalidate();
- int initialHourDegrees = (initialHoursOfDay % 12) * HOUR_VALUE_TO_DEGREES_STEP_SIZE;
- int initialMinuteDegrees = initialMinutes * MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
- mHourRadialSelectorView.initialize(context, initialHourDegrees,
- is24HourMode, is24HourMode, isHourInnerCircle(initialHoursOfDay), true);
- mHourRadialSelectorView.invalidate();
- mMinuteRadialSelectorView.initialize(context, initialMinuteDegrees,
- is24HourMode, false, false, false);
- mHourRadialSelectorView.invalidate();
-
+ setValueForItem(HOUR_INDEX, initialHoursOfDay);
+ setValueForItem(MINUTE_INDEX, initialMinutes);
+ int hourDegrees = (initialHoursOfDay % 12) * HOUR_VALUE_TO_DEGREES_STEP_SIZE;
+ mHourRadialSelectorView.initialize(context, mHideAmPm, is24HourMode, true,
+ hourDegrees, isHourInnerCircle(initialHoursOfDay));
+ int minuteDegrees = initialMinutes * MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
+ mMinuteRadialSelectorView.initialize(context, mHideAmPm, false, false,
+ minuteDegrees, false);
mTimeInitialized = true;
}
+ public void setTime(int hours, int minutes) {
+ setItem(HOUR_INDEX, hours);
+ setItem(MINUTE_INDEX, minutes);
+ }
+
+ private void setItem(int index, int value) {
+ if (index == HOUR_INDEX) {
+ setValueForItem(HOUR_INDEX, value);
+ int hourDegrees = (value % 12) * HOUR_VALUE_TO_DEGREES_STEP_SIZE;
+ mHourRadialSelectorView.setSelection(hourDegrees, isHourInnerCircle(value),
+ false, false, false);
+ mHourRadialSelectorView.invalidate();
+ } else if (index == MINUTE_INDEX) {
+ setValueForItem(MINUTE_INDEX, value);
+ int minuteDegrees = value * MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
+ mMinuteRadialSelectorView.setSelection(minuteDegrees, false, false, false, false);
+ mMinuteRadialSelectorView.invalidate();
+ }
+ }
+
private boolean isHourInnerCircle(int hourOfDay) {
// We'll have the 00 hours on the outside circle.
return mIs24HourMode && (hourOfDay <= 12 && hourOfDay != 0);
@@ -229,38 +265,63 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
setValueForItem(AMPM_INDEX, amOrPm);
}
- private int reselectSelector(int index, int degrees, boolean isInnerCircle,
+ private int highPass30sFilter(int degrees) {
+ int offset = (degrees + 2) / 30;
+ degrees = Math.max(degrees - (30*offset + 4), 0) + 20*offset;
+ degrees /= 4;
+ degrees *= 6;
+ /* // less aggressive filtering.
+ degrees /= 5;
+ int offset = degrees / 6;
+ degrees = degrees - offset;
+ degrees *= 6; */
+ return degrees;
+ }
+
+ private int snapToStepSize(int degrees, int stepSize, int ceilingOrFloor) {
+ int floor = (degrees / stepSize) * stepSize;
+ int ceiling = floor + stepSize;
+ if (ceilingOrFloor == 1) {
+ degrees = ceiling;
+ } else if (ceilingOrFloor == -1) {
+ if (degrees == floor) {
+ floor -= stepSize;
+ }
+ degrees = floor;
+ } else {
+ if ((degrees - floor) < (ceiling - degrees)) {
+ degrees = floor;
+ } else {
+ degrees = ceiling;
+ }
+ }
+ return degrees;
+ }
+
+ private int reselectSelector(int degrees, boolean isInnerCircle,
boolean forceNotFineGrained, boolean forceDrawLine, boolean forceDrawDot) {
- if (degrees == -1 || (index != 0 && index != 1)) {
+ if (degrees == -1) {
return -1;
}
+ int currentShowing = getCurrentItemShowing();
int stepSize;
- int currentShowing = getCurrentItemShowing();
- if (!forceNotFineGrained && (currentShowing == 1)) {
- stepSize = MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
+ boolean allowFineGrained = !forceNotFineGrained && (currentShowing == MINUTE_INDEX);
+ if (allowFineGrained) {
+ degrees = highPass30sFilter(degrees);
} else {
- stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
- }
- int floor = (degrees / stepSize) * stepSize;
- int ceiling = floor + stepSize;
- if ((degrees - floor) < (ceiling - degrees)) {
- degrees = floor;
- } else {
- degrees = ceiling;
+ degrees = snapToStepSize(degrees, HOUR_VALUE_TO_DEGREES_STEP_SIZE, 0);
}
RadialSelectorView radialSelectorView;
- if (index == 0) {
- // Index == 0, hours.
+ if (currentShowing == HOUR_INDEX) {
radialSelectorView = mHourRadialSelectorView;
stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
} else {
- // Index == 1, minutes.
radialSelectorView = mMinuteRadialSelectorView;
stepSize = MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
}
- radialSelectorView.setSelection(degrees, isInnerCircle, forceDrawLine, forceDrawDot);
+ radialSelectorView.setSelection(degrees, isInnerCircle, forceDrawLine, forceDrawDot, false);
radialSelectorView.invalidate();
@@ -288,10 +349,10 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
private int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal,
final Boolean[] isInnerCircle) {
int currentItem = getCurrentItemShowing();
- if (currentItem == 0) {
+ if (currentItem == HOUR_INDEX) {
return mHourRadialSelectorView.getDegreesFromCoords(
pointX, pointY, forceLegal, isInnerCircle);
- } else if (currentItem == 1) {
+ } else if (currentItem == MINUTE_INDEX) {
return mMinuteRadialSelectorView.getDegreesFromCoords(
pointX, pointY, forceLegal, isInnerCircle);
} else {
@@ -313,7 +374,10 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
return;
}
- if (animate && (index != getCurrentItemShowing())) {
+ int lastIndex = getCurrentItemShowing();
+ mCurrentItemShowing = index;
+
+ if (animate && (index != lastIndex)) {
ObjectAnimator[] anims = new ObjectAnimator[4];
if (index == MINUTE_INDEX) {
anims[0] = mHourRadialTextsView.getDisappearAnimator();
@@ -331,15 +395,14 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
transition.playTogether(anims);
transition.start();
} else {
- int hourAlpha = (index == 0) ? 255 : 0;
- int minuteAlpha = (index == 1) ? 255 : 0;
+ int hourAlpha = (index == HOUR_INDEX) ? 255 : 0;
+ int minuteAlpha = (index == MINUTE_INDEX) ? 255 : 0;
mHourRadialTextsView.setAlpha(hourAlpha);
mHourRadialSelectorView.setAlpha(hourAlpha);
mMinuteRadialTextsView.setAlpha(minuteAlpha);
mMinuteRadialSelectorView.setAlpha(minuteAlpha);
}
- mCurrentItemShowing = index;
}
@Override
@@ -348,7 +411,6 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
final float eventY = event.getY();
int degrees;
int value;
- final int currentShowing = getCurrentItemShowing();
final Boolean[] isInnerCircle = new Boolean[1];
isInnerCircle[0] = false;
@@ -356,12 +418,17 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
+ if (!mInputEnabled) {
+ return true;
+ }
+
mDownX = eventX;
mDownY = eventY;
mLastValueSelected = -1;
mDoingMove = false;
- if (!mIs24HourMode) {
+ mDoingTouch = true;
+ if (!mHideAmPm) {
mIsTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY);
} else {
mIsTouchingAmOrPm = -1;
@@ -377,16 +444,17 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
}
}, TAP_TIMEOUT);
} else {
- mDownDegrees = getDegreesFromCoords(eventX, eventY, false, isInnerCircle);
+ boolean forceLegal = mAccessibilityManager.isTouchExplorationEnabled();
+ mDownDegrees = getDegreesFromCoords(eventX, eventY, forceLegal, isInnerCircle);
if (mDownDegrees != -1) {
- tryTick();
- mLastValueSelected = getCurrentlyShowingValue();
+ tryVibrate();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mDoingMove = true;
- int value = reselectSelector(currentShowing, mDownDegrees,
+ int value = reselectSelector(mDownDegrees,
isInnerCircle[0], false, true, true);
+ mLastValueSelected = value;
mListener.onValueSelected(getCurrentItemShowing(), value, false);
}
}, TAP_TIMEOUT);
@@ -394,6 +462,12 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
}
return true;
case MotionEvent.ACTION_MOVE:
+ if (!mInputEnabled) {
+ // We shouldn't be in this state, because input is disabled.
+ Log.e(TAG, "Input was disabled, but received ACTION_MOVE.");
+ return true;
+ }
+
float dY = Math.abs(eventY - mDownY);
float dX = Math.abs(eventX - mDownX);
@@ -425,17 +499,24 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
mHandler.removeCallbacksAndMessages(null);
degrees = getDegreesFromCoords(eventX, eventY, true, isInnerCircle);
if (degrees != -1) {
- value = reselectSelector(currentShowing, degrees,
+ value = reselectSelector(degrees,
isInnerCircle[0], false, true, true);
if (value != mLastValueSelected) {
- tryTick();
+ tryVibrate();
mLastValueSelected = value;
+ mListener.onValueSelected(getCurrentItemShowing(), value, false);
}
- mListener.onValueSelected(getCurrentItemShowing(), value, false);
}
return true;
case MotionEvent.ACTION_UP:
+ if (!mInputEnabled) {
+ Log.d(TAG, "Input was disabled, but received ACTION_UP.");
+ mListener.onValueSelected(ENABLE_PICKER_INDEX, 1, false);
+ return true;
+ }
+
mHandler.removeCallbacksAndMessages(null);
+ mDoingTouch = false;
if (mIsTouchingAmOrPm == AM || mIsTouchingAmOrPm == PM) {
int isTouchingAmOrPm = mAmPmCirclesView.getIsTouchingAmOrPm(eventX, eventY);
@@ -456,11 +537,9 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
if (mDownDegrees != -1) {
degrees = getDegreesFromCoords(eventX, eventY, mDoingMove, isInnerCircle);
if (degrees != -1) {
- value = reselectSelector(currentShowing, degrees, isInnerCircle[0],
+ value = reselectSelector(degrees, isInnerCircle[0],
!mDoingMove, true, false);
- mListener.onValueSelected(getCurrentItemShowing(), value, true);
-
- if (currentShowing == HOUR_INDEX && !mIs24HourMode) {
+ if (getCurrentItemShowing() == HOUR_INDEX && !mIs24HourMode) {
int amOrPm = getIsCurrentlyAmOrPm();
if (amOrPm == AM && value == 12) {
value = 0;
@@ -469,6 +548,7 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
}
}
setValueForItem(getCurrentItemShowing(), value);
+ mListener.onValueSelected(getCurrentItemShowing(), value, true);
}
}
mDoingMove = false;
@@ -479,47 +559,105 @@ public class TimePicker extends FrameLayout implements OnTouchListener {
return false;
}
- private class ReselectSelectorRunnable implements Runnable {
- TimePicker mTimePicker;
- private int mIndex;
- private int mDegrees;
- private boolean mIsInnerCircle;
- private boolean mForceNotFineGrained;
- private boolean mForceDrawLine;
- private boolean mForceDrawDot;
-
- public ReselectSelectorRunnable(TimePicker timePicker) {
- mTimePicker = timePicker;
- }
-
- public void initializeValues(int index, int degrees, boolean isInnerCircle,
- boolean forceNotFineGrained, boolean forceDrawLine, boolean forceDrawDot) {
- mIndex = index;
- mDegrees = degrees;
- mIsInnerCircle = isInnerCircle;
- mForceNotFineGrained = forceNotFineGrained;
- mForceDrawDot = forceDrawDot;
- }
-
- @Override
- public void run() {
- mTimePicker.reselectSelector(mIndex, mDegrees, mIsInnerCircle, mForceNotFineGrained,
- mForceDrawLine, mForceDrawDot);
- }
- }
-
public void tryVibrate() {
if (mVibrator != null) {
long now = SystemClock.uptimeMillis();
// We want to try to vibrate each individual tick discretely.
- if (now - mLastVibrate >= 100) {
+ if (now - mLastVibrate >= 125) {
mVibrator.vibrate(5);
mLastVibrate = now;
}
}
}
- public void tryTick() {
- tryVibrate();
+ public boolean trySettingInputEnabled(boolean inputEnabled) {
+ if (mDoingTouch && !inputEnabled) {
+ // If we're trying to disable input, but we're in the middle of a touch event,
+ // we'll allow the touch event to continue before disabling input.
+ return false;
+ }
+ mInputEnabled = inputEnabled;
+ mGrayBox.setVisibility(inputEnabled? View.INVISIBLE : View.VISIBLE);
+ return true;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ event.getText().clear();
+ Time time = new Time();
+ time.hour = getHours();
+ time.minute = getMinutes();
+ long millis = time.normalize(true);
+ int flags = DateUtils.FORMAT_SHOW_TIME;
+ if (mIs24HourMode) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ }
+ String timeString = DateUtils.formatDateTime(getContext(), millis, flags);
+ event.getText().add(timeString);
+ return true;
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (super.performAccessibilityAction(action, arguments)) {
+ return true;
+ }
+
+ int changeMultiplier = 0;
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
+ changeMultiplier = 1;
+ } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ changeMultiplier = -1;
+ }
+ if (changeMultiplier != 0) {
+ int value = getCurrentlyShowingValue();
+ int stepSize = 0;
+ int currentItemShowing = getCurrentItemShowing();
+ if (currentItemShowing == HOUR_INDEX) {
+ stepSize = HOUR_VALUE_TO_DEGREES_STEP_SIZE;
+ value %= 12;
+ } else if (currentItemShowing == MINUTE_INDEX) {
+ stepSize = MINUTE_VALUE_TO_DEGREES_STEP_SIZE;
+ }
+
+ int degrees = value * stepSize;
+ degrees = snapToStepSize(degrees, HOUR_VALUE_TO_DEGREES_STEP_SIZE, changeMultiplier);
+ value = degrees / stepSize;
+ int maxValue = 0;
+ int minValue = 0;
+ if (currentItemShowing == HOUR_INDEX) {
+ if (mIs24HourMode) {
+ maxValue = 23;
+ } else {
+ maxValue = 12;
+ minValue = 1;
+ }
+ } else {
+ maxValue = 55;
+ }
+ if (value > maxValue) {
+ // If we scrolled forward past the highest number, wrap around to the lowest.
+ value = minValue;
+ } else if (value < minValue) {
+ // If we scrolled backward past the lowest number, wrap around to the highest.
+ value = maxValue;
+ }
+ setItem(currentItemShowing, value);
+ mListener.onValueSelected(currentItemShowing, value, false);
+ return true;
+ }
+
+ return false;
}
}
diff --git a/src/com/android/datetimepicker/RadialSelectorView.java b/src/com/android/datetimepicker/time/RadialSelectorView.java
index bb1f78a..92324a2 100644
--- a/src/com/android/datetimepicker/RadialSelectorView.java
+++ b/src/com/android/datetimepicker/time/RadialSelectorView.java
@@ -14,26 +14,19 @@
* limitations under the License.
*/
-package com.android.datetimepicker;
+package com.android.datetimepicker.time;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.Service;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Typeface;
-import android.os.SystemClock;
-import android.os.Vibrator;
-import android.util.AttributeSet;
import android.util.Log;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnTouchListener;
import com.android.datetimepicker.R;
@@ -66,6 +59,7 @@ public class RadialSelectorView extends View {
private int mSelectionDegrees;
private double mSelectionRadians;
+ private boolean mHideSelector;
private boolean mDrawLine;
private boolean mForceDrawDot;
@@ -74,8 +68,8 @@ public class RadialSelectorView extends View {
mIsInitialized = false;
}
- public void initialize(Context context, int selectionDegrees, boolean is24HourMode,
- boolean hasInnerCircle, boolean isInnerCircle, boolean disappearsOut) {
+ public void initialize(Context context, boolean is24HourMode, boolean hasInnerCircle,
+ boolean disappearsOut, int selectionDegrees, boolean isInnerCircle) {
if (mIsInitialized) {
Log.e(TAG, "This RadialSelectorView may only be initialized once.");
return;
@@ -111,18 +105,17 @@ public class RadialSelectorView extends View {
mSelectionRadiusMultiplier =
Float.parseFloat(res.getString(R.string.selection_radius_multiplier));
- setSelection(selectionDegrees, isInnerCircle, false, false);
-
mAnimationRadiusMultiplier = 1;
mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
mInvalidateUpdateListener = new InvalidateUpdateListener();
+ setSelection(selectionDegrees, isInnerCircle, false, false, false);
mIsInitialized = true;
}
public void setSelection(int selectionDegrees, boolean isInnerCircle,
- boolean drawLine, boolean forceDrawDot) {
+ boolean drawLine, boolean forceDrawDot, boolean hideSelector) {
mSelectionDegrees = selectionDegrees;
mSelectionRadians = selectionDegrees * Math.PI / 180;
mDrawLine = drawLine;
@@ -135,6 +128,7 @@ public class RadialSelectorView extends View {
mNumbersRadiusMultiplier = mOuterNumbersRadiusMultiplier;
}
}
+ mHideSelector = hideSelector;
}
public void setDrawLine(boolean drawLine) {
@@ -252,6 +246,10 @@ public class RadialSelectorView extends View {
}
mLineLength = (int) (mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier);
+ if (mHideSelector) {
+ return;
+ }
+
int pointX = mXCenter + (int) (mLineLength * Math.sin(mSelectionRadians));
int pointY = mYCenter - (int) (mLineLength * Math.cos(mSelectionRadians));
@@ -316,8 +314,8 @@ public class RadialSelectorView extends View {
// The time points are half of what they would normally be, because this animation is
// staggered against the disappear so they happen seamlessly. The reappear starts
// halfway into the disappear.
- float delayMultiplier = 0.5f;
- float transitionDurationMultiplier = 0.75f;
+ float delayMultiplier = 0.25f;
+ float transitionDurationMultiplier = 1f;
float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
int totalDuration = (int) (duration * totalDurationMultiplier);
float delayPoint = (delayMultiplier * duration) / totalDuration;
diff --git a/src/com/android/datetimepicker/RadialTextsView.java b/src/com/android/datetimepicker/time/RadialTextsView.java
index b128d0d..5e159c8 100644
--- a/src/com/android/datetimepicker/RadialTextsView.java
+++ b/src/com/android/datetimepicker/time/RadialTextsView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.datetimepicker;
+package com.android.datetimepicker.time;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
@@ -27,11 +27,8 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
-import android.util.AttributeSet;
import android.util.Log;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnTouchListener;
import com.android.datetimepicker.R;
@@ -274,8 +271,8 @@ public class RadialTextsView extends View {
// Set up animator for reappearing.
- float delayMultiplier = 0.5f;
- float transitionDurationMultiplier = 0.75f;
+ float delayMultiplier = 0.25f;
+ float transitionDurationMultiplier = 1f;
float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
int totalDuration = (int) (duration * totalDurationMultiplier);
float delayPoint = (delayMultiplier * duration) / totalDuration;
diff --git a/src/com/android/datetimepicker/time/TimePickerDialog.java b/src/com/android/datetimepicker/time/TimePickerDialog.java
new file mode 100644
index 0000000..c9e5a02
--- /dev/null
+++ b/src/com/android/datetimepicker/time/TimePickerDialog.java
@@ -0,0 +1,823 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.android.datetimepicker.time;
+
+import android.annotation.SuppressLint;
+import android.app.ActionBar.LayoutParams;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.datetimepicker.R;
+
+import com.android.datetimepicker.time.RadialPickerLayout.OnValueSelectedListener;
+import com.android.datetimepicker.Utils;
+
+import java.text.DateFormatSymbols;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * Dialog to set a time.
+ */
+public class TimePickerDialog extends DialogFragment implements OnValueSelectedListener{
+ private static final String TAG = "TimePickerDialog";
+
+ private static final String KEY_HOUR_OF_DAY = "hour_of_day";
+ private static final String KEY_MINUTE = "minute";
+ private static final String KEY_IS_24_HOUR_VIEW = "is_24_hour_view";
+ private static final String KEY_CURRENT_ITEM_SHOWING = "current_item_showing";
+ private static final String KEY_IN_KB_MODE = "in_kb_mode";
+ private static final String KEY_TYPED_TIMES = "typed_times";
+
+ public static final int HOUR_INDEX = 0;
+ public static final int MINUTE_INDEX = 1;
+ public static final int AMPM_INDEX = 2; // NOT a real index for the purpose of what's showing.
+ public static final int ENABLE_PICKER_INDEX = 3; // Also NOT a real index, just used for KB mode.
+ public static final int AM = 0;
+ public static final int PM = 1;
+
+ private OnTimeSetListener mCallback;
+
+ private TextView mDoneButton;
+ private TextView mHourView;
+ private TextView mMinuteView;
+ private TextView mAmPmTextView;
+ private View mAmPmHitspace;
+ private RadialPickerLayout mTimePicker;
+
+ private int mBlue;
+ private int mBlack;
+ private String mAmText;
+ private String mPmText;
+
+ private boolean mAllowAutoAdvance;
+ private int mInitialHourOfDay;
+ private int mInitialMinute;
+ private boolean mIs24HourMode;
+
+ // For hardware IME input.
+ private char mPlaceholderText;
+ private String mDoublePlaceholderText;
+ private boolean mInKbMode;
+ private ArrayList<Integer> mTypedTimes;
+ private Node mLegalTimesTree;
+ private int mAmKeyCode;
+ private int mPmKeyCode;
+
+ // Accessibility strings.
+ private String mHourPickerDescription;
+ private String mSelectHours;
+ private String mMinutePickerDescription;
+ private String mSelectMinutes;
+
+ /**
+ * The callback interface used to indicate the user is done filling in
+ * the time (they clicked on the 'Set' button).
+ */
+ public interface OnTimeSetListener {
+
+ /**
+ * @param view The view associated with this listener.
+ * @param hourOfDay The hour that was set.
+ * @param minute The minute that was set.
+ */
+ void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute);
+ }
+
+ public TimePickerDialog() {
+ // Empty constructor required for dialog fragment.
+ }
+
+ public TimePickerDialog(Context context, int theme, OnTimeSetListener callback,
+ int hourOfDay, int minute, boolean is24HourMode) {
+ // Empty constructor required for dialog fragment.
+ }
+
+ public static TimePickerDialog newInstance(OnTimeSetListener callback,
+ int hourOfDay, int minute, boolean is24HourMode) {
+ TimePickerDialog ret = new TimePickerDialog();
+ ret.initialize(callback, hourOfDay, minute, is24HourMode);
+ return ret;
+ }
+
+ public void initialize(OnTimeSetListener callback,
+ int hourOfDay, int minute, boolean is24HourMode) {
+ mCallback = callback;
+
+ mInitialHourOfDay = hourOfDay;
+ mInitialMinute = minute;
+ mIs24HourMode = is24HourMode;
+ mInKbMode = false;
+ }
+
+ public void setOnTimeSetListener(OnTimeSetListener callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null && savedInstanceState.containsKey(KEY_HOUR_OF_DAY)
+ && savedInstanceState.containsKey(KEY_MINUTE)
+ && savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) {
+ mInitialHourOfDay = savedInstanceState.getInt(KEY_HOUR_OF_DAY);
+ mInitialMinute = savedInstanceState.getInt(KEY_MINUTE);
+ mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW);
+ mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+
+ View view = inflater.inflate(R.layout.time_picker_dialog, null);
+ KeyboardListener keyboardListener = new KeyboardListener();
+ view.findViewById(R.id.time_picker_dialog).setOnKeyListener(keyboardListener);
+
+ Resources res = getResources();
+ mHourPickerDescription = res.getString(R.string.hour_picker_description);
+ mSelectHours = res.getString(R.string.select_hours);
+ mMinutePickerDescription = res.getString(R.string.minute_picker_description);
+ mSelectMinutes = res.getString(R.string.select_minutes);
+ mBlue = res.getColor(R.color.blue);
+ mBlack = res.getColor(R.color.black_80);
+
+ mHourView = (TextView) view.findViewById(R.id.hours);
+ mHourView.setOnKeyListener(keyboardListener);
+ mMinuteView = (TextView) view.findViewById(R.id.minutes);
+ mMinuteView.setOnKeyListener(keyboardListener);
+ mAmPmTextView = (TextView) view.findViewById(R.id.ampm_label);
+ mAmPmTextView.setOnKeyListener(keyboardListener);
+ String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
+ mAmText = amPmTexts[0];
+ mPmText = amPmTexts[1];
+
+ mTimePicker = (RadialPickerLayout) view.findViewById(R.id.time_picker);
+ mTimePicker.setOnValueSelectedListener(this);
+ mTimePicker.setOnKeyListener(keyboardListener);
+ mTimePicker.initialize(getActivity(), mInitialHourOfDay, mInitialMinute, mIs24HourMode);
+ int currentItemShowing = HOUR_INDEX;
+ if (savedInstanceState != null &&
+ savedInstanceState.containsKey(KEY_CURRENT_ITEM_SHOWING)) {
+ currentItemShowing = savedInstanceState.getInt(KEY_CURRENT_ITEM_SHOWING);
+ }
+ setCurrentItemShowing(currentItemShowing, false);
+ mTimePicker.invalidate();
+
+ mHourView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setCurrentItemShowing(HOUR_INDEX, true);
+ mTimePicker.tryVibrate();
+ }
+ });
+ mMinuteView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setCurrentItemShowing(MINUTE_INDEX, true);
+ mTimePicker.tryVibrate();
+ }
+ });
+
+ mDoneButton = (TextView) view.findViewById(R.id.done_button);
+ mDoneButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mInKbMode && isTypedTimeFullyLegal()) {
+ finishKbMode(false);
+ } else {
+ mTimePicker.tryVibrate();
+ }
+ if (mCallback != null) {
+ mCallback.onTimeSet(mTimePicker,
+ mTimePicker.getHours(), mTimePicker.getMinutes());
+ }
+ dismiss();
+ }
+ });
+ mDoneButton.setOnKeyListener(keyboardListener);
+
+ mAmPmHitspace = view.findViewById(R.id.ampm_hitspace);
+ if (mIs24HourMode) {
+ mAmPmTextView.setVisibility(View.GONE);
+
+ RelativeLayout.LayoutParams paramsSeparator = new RelativeLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ paramsSeparator.addRule(RelativeLayout.CENTER_IN_PARENT);
+ TextView separatorView = (TextView) view.findViewById(R.id.separator);
+ separatorView.setLayoutParams(paramsSeparator);
+ } else {
+ mAmPmTextView.setVisibility(View.VISIBLE);
+ updateAmPmDisplay(mInitialHourOfDay < 12? AM : PM);
+ mAmPmHitspace.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mTimePicker.tryVibrate();
+ int amOrPm = mTimePicker.getIsCurrentlyAmOrPm();
+ if (amOrPm == AM) {
+ amOrPm = PM;
+ } else if (amOrPm == PM){
+ amOrPm = AM;
+ }
+ updateAmPmDisplay(amOrPm);
+ mTimePicker.setAmOrPm(amOrPm);
+ }
+ });
+ }
+
+ mAllowAutoAdvance = true;
+ setHour(mInitialHourOfDay);
+ setMinute(mInitialMinute);
+
+ mDoublePlaceholderText = res.getString(R.string.time_placeholder);
+ mPlaceholderText = mDoublePlaceholderText.charAt(0);
+ mAmKeyCode = mPmKeyCode = -1;
+ generateLegalTimesTree();
+ if (mInKbMode) {
+ mTypedTimes = savedInstanceState.getIntegerArrayList(KEY_TYPED_TIMES);
+ tryStartingKbMode(-1);
+ mHourView.invalidate();
+ } else if (mTypedTimes == null) {
+ mTypedTimes = new ArrayList<Integer>();
+ }
+
+ return view;
+ }
+
+ private void updateAmPmDisplay(int amOrPm) {
+ if (amOrPm == AM) {
+ mAmPmTextView.setText(mAmText);
+ tryAccessibilityAnnounce(mAmText);
+ mAmPmHitspace.setContentDescription(mAmText);
+ } else if (amOrPm == PM){
+ mAmPmTextView.setText(mPmText);
+ tryAccessibilityAnnounce(mPmText);
+ mAmPmHitspace.setContentDescription(mPmText);
+ } else {
+ mAmPmTextView.setText(mDoublePlaceholderText);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ if (mTimePicker != null) {
+ outState.putInt(KEY_HOUR_OF_DAY, mTimePicker.getHours());
+ outState.putInt(KEY_MINUTE, mTimePicker.getMinutes());
+ outState.putBoolean(KEY_IS_24_HOUR_VIEW, mIs24HourMode);
+ outState.putInt(KEY_CURRENT_ITEM_SHOWING, mTimePicker.getCurrentItemShowing());
+ outState.putBoolean(KEY_IN_KB_MODE, mInKbMode);
+ if (mInKbMode) {
+ outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes);
+ }
+ }
+ }
+
+ @Override
+ public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
+ if (pickerIndex == HOUR_INDEX) {
+ setHour(newValue);
+ if (mAllowAutoAdvance && autoAdvance) {
+ setCurrentItemShowing(MINUTE_INDEX, true);
+ }
+ } else if (pickerIndex == MINUTE_INDEX){
+ setMinute(newValue);
+ } else if (pickerIndex == AMPM_INDEX) {
+ updateAmPmDisplay(newValue);
+ } else if (pickerIndex == ENABLE_PICKER_INDEX) {
+ if (!isTypedTimeFullyLegal()) {
+ mTypedTimes.clear();
+ }
+ finishKbMode(true);
+ }
+ }
+
+ private void setHour(int value) {
+ String format;
+ if (mIs24HourMode) {
+ format = "%02d";
+ } else {
+ format = "%d";
+ value = value % 12;
+ if (value == 0) {
+ value = 12;
+ }
+ }
+
+ CharSequence text = String.format(format, value);
+ tryAccessibilityAnnounce(text);
+ mHourView.setText(text);
+ }
+
+ private void setMinute(int value) {
+ if (value == 60) {
+ value = 0;
+ }
+ CharSequence text = String.format(Locale.getDefault(), "%02d", value);
+ tryAccessibilityAnnounce(text);
+ mMinuteView.setText(text);
+ }
+
+ private void setCurrentItemShowing(int index, boolean animate) {
+ mTimePicker.setCurrentItemShowing(index, animate);
+
+ if (index == HOUR_INDEX) {
+ int hours = mTimePicker.getHours();
+ if (!mIs24HourMode) {
+ hours = hours % 12;
+ }
+ mTimePicker.setContentDescription(mHourPickerDescription+": "+hours);
+ tryAccessibilityAnnounce(mSelectHours);
+ } else {
+ int minutes = mTimePicker.getMinutes();
+ mTimePicker.setContentDescription(mMinutePickerDescription+": "+minutes);
+ tryAccessibilityAnnounce(mSelectMinutes);
+ }
+
+ int hourColor = (index == HOUR_INDEX)? mBlue : mBlack;
+ int minuteColor = (index == MINUTE_INDEX)? mBlue : mBlack;
+ mHourView.setTextColor(hourColor);
+ mMinuteView.setTextColor(minuteColor);
+ }
+
+ @SuppressLint("NewApi")
+ private void tryAccessibilityAnnounce(CharSequence text) {
+ if (Utils.isJellybeanOrLater() && mTimePicker != null && text != null) {
+ mTimePicker.announceForAccessibility(text);
+ }
+ }
+
+ private boolean processKeyUp(int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
+ dismiss();
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+ if(mInKbMode) {
+ if (isTypedTimeFullyLegal()) {
+ finishKbMode(true);
+ }
+ return true;
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
+ if (mInKbMode) {
+ if (!isTypedTimeFullyLegal()) {
+ return true;
+ }
+ finishKbMode(false);
+ }
+ if (mCallback != null) {
+ mCallback.onTimeSet(mTimePicker,
+ mTimePicker.getHours(), mTimePicker.getMinutes());
+ }
+ dismiss();
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+ if (mInKbMode) {
+ if (!mTypedTimes.isEmpty()) {
+ deleteLastTypedKey();
+ updateDisplay(true);
+ }
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
+ || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
+ || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
+ || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
+ || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
+ || (!mIs24HourMode &&
+ (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
+ if (!mInKbMode) {
+ if (mTimePicker == null) {
+ // Something's wrong, because time picker should definitely not be null.
+ Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
+ return true;
+ }
+ mTypedTimes.clear();
+ tryStartingKbMode(keyCode);
+ return true;
+ }
+ // We're already in keyboard mode.
+ if (addKeyIfLegal(keyCode)) {
+ updateDisplay(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void tryStartingKbMode(int keyCode) {
+ if (mTimePicker.trySettingInputEnabled(false) && (keyCode == -1 || addKeyIfLegal(keyCode))) {
+ mInKbMode = true;
+ mDoneButton.setEnabled(false);
+ updateDisplay(false);
+ }
+ }
+
+ private boolean addKeyIfLegal(int keyCode) {
+ // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
+ // we'll need to see if AM/PM have been typed.
+ if ((mIs24HourMode && mTypedTimes.size() == 4) ||
+ (!mIs24HourMode && isTypedTimeFullyLegal())) {
+ return false;
+ }
+
+ mTypedTimes.add(keyCode);
+ if (!isTypedTimeLegalSoFar()) {
+ deleteLastTypedKey();
+ return false;
+ }
+
+ // Automatically fill in 0's if AM or PM was legally entered.
+ if (isTypedTimeFullyLegal()) {
+ if (!mIs24HourMode && mTypedTimes.size() <= 3) {
+ mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
+ mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
+ }
+ mDoneButton.setEnabled(true);
+ }
+
+ return true;
+ }
+
+ private boolean isTypedTimeLegalSoFar() {
+ Node node = mLegalTimesTree;
+ for (int keyCode : mTypedTimes) {
+ node = node.canReach(keyCode);
+ if (node == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isTypedTimeFullyLegal() {
+ // The time is legal if it contains an AM or PM, as those can only be legally added at
+ // specific times based on the tree's algorithm.
+ if (mIs24HourMode) {
+ // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
+ int[] values = getEnteredTime(null);
+ return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
+ } else {
+ return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
+ mTypedTimes.contains(getAmOrPmKeyCode(PM)));
+ }
+ }
+
+ private void deleteLastTypedKey() {
+ mTypedTimes.remove(mTypedTimes.size() - 1);
+ if (!isTypedTimeFullyLegal()) {
+ mDoneButton.setEnabled(false);
+ }
+ }
+
+ private void finishKbMode(boolean changeDisplays) {
+ mInKbMode = false;
+ if (!mTypedTimes.isEmpty()) {
+ int values[] = getEnteredTime(null);
+ mTimePicker.setTime(values[0], values[1]);
+ if (!mIs24HourMode) {
+ mTimePicker.setAmOrPm(values[2]);
+ }
+ mTypedTimes.clear();
+ }
+ if (changeDisplays) {
+ updateDisplay(false);
+ mTimePicker.trySettingInputEnabled(true);
+ }
+ }
+
+ private void updateDisplay(boolean allowEmpty) {
+ if (!allowEmpty && mTypedTimes.isEmpty()) {
+ int hour = mTimePicker.getHours();
+ int minute = mTimePicker.getMinutes();
+ setHour(hour);
+ setMinute(minute);
+ if (!mIs24HourMode) {
+ updateAmPmDisplay(hour < 12? AM : PM);
+ }
+ setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true);
+ mDoneButton.setEnabled(true);
+ } else {
+ Boolean[] enteredZeros = {false, false};
+ int[] values = getEnteredTime(enteredZeros);
+ String hourFormat = enteredZeros[0]? "%02d" : "%2d";
+ String minuteFormat = (enteredZeros[1])? "%02d" : "%2d";
+ String hourStr = (values[0] == -1)? mDoublePlaceholderText :
+ String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
+ String minuteStr = (values[1] == -1)? mDoublePlaceholderText :
+ String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
+ mHourView.setText(hourStr);
+ mHourView.setTextColor(mBlack);
+ mMinuteView.setText(minuteStr);
+ mMinuteView.setTextColor(mBlack);
+ if (!mIs24HourMode) {
+ updateAmPmDisplay(values[2]);
+ }
+ }
+ }
+
+ private int getValFromKeyCode(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_0:
+ return 0;
+ case KeyEvent.KEYCODE_1:
+ return 1;
+ case KeyEvent.KEYCODE_2:
+ return 2;
+ case KeyEvent.KEYCODE_3:
+ return 3;
+ case KeyEvent.KEYCODE_4:
+ return 4;
+ case KeyEvent.KEYCODE_5:
+ return 5;
+ case KeyEvent.KEYCODE_6:
+ return 6;
+ case KeyEvent.KEYCODE_7:
+ return 7;
+ case KeyEvent.KEYCODE_8:
+ return 8;
+ case KeyEvent.KEYCODE_9:
+ return 9;
+ default:
+ return -1;
+ }
+ }
+
+ private int[] getEnteredTime(Boolean[] enteredZeros) {
+ int amOrPm = -1;
+ int startIndex = 1;
+ if (!mIs24HourMode && isTypedTimeFullyLegal()) {
+ int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
+ if (keyCode == getAmOrPmKeyCode(AM)) {
+ amOrPm = AM;
+ } else if (keyCode == getAmOrPmKeyCode(PM)){
+ amOrPm = PM;
+ }
+ startIndex = 2;
+ }
+ int minute = -1;
+ int hour = -1;
+ for (int i = startIndex; i <= mTypedTimes.size(); i++) {
+ int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
+ if (i == startIndex) {
+ minute = val;
+ } else if (i == startIndex+1) {
+ minute += 10*val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[1] = true;
+ }
+ } else if (i == startIndex+2) {
+ hour = val;
+ } else if (i == startIndex+3) {
+ hour += 10*val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[0] = true;
+ }
+ }
+ }
+
+ int[] ret = {hour, minute, amOrPm};
+ return ret;
+ }
+
+ private int getAmOrPmKeyCode(int amOrPm) {
+ // Cache the codes.
+ if (mAmKeyCode == -1 || mPmKeyCode == -1) {
+ // Find the first character in the AM/PM text that is unique.
+ KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ char amChar;
+ char pmChar;
+ for (int i = 0; i < Math.max(mAmText.length(), mPmText.length()); i++) {
+ amChar = mAmText.toLowerCase(Locale.getDefault()).charAt(i);
+ pmChar = mPmText.toLowerCase(Locale.getDefault()).charAt(i);
+ if (amChar != pmChar) {
+ KeyEvent[] events = kcm.getEvents(new char[]{amChar, pmChar});
+ // There should be 4 events: a down and up for both AM and PM.
+ if (events != null && events.length == 4) {
+ mAmKeyCode = events[0].getKeyCode();
+ mPmKeyCode = events[2].getKeyCode();
+ Log.d(TAG, "am char: "+amChar+" keycode: "+mAmKeyCode);
+ Log.d(TAG, "pm char: "+pmChar+" keycode: "+mPmKeyCode);
+ } else {
+ Log.d(TAG, "am char: "+amChar+" keycode: "+mAmKeyCode);
+ Log.d(TAG, "pm char: "+pmChar+" keycode: "+mPmKeyCode);
+ if (events != null) {
+ for (int j = 0; j < events.length; j++) {
+ Log.d(TAG, "event code: "+events[j].getKeyCode()+" events: "+events[j]);
+ }
+ }
+ Log.e(TAG, "Unable to find keycodes for AM and PM.");
+ }
+ break;
+ }
+ }
+ }
+ if (amOrPm == AM) {
+ return mAmKeyCode;
+ } else if (amOrPm == PM) {
+ return mPmKeyCode;
+ }
+
+ return -1;
+ }
+
+ private void generateLegalTimesTree() {
+ // Create a quick cache of numbers to their keycodes.
+ int k0 = KeyEvent.KEYCODE_0;
+ int k1 = KeyEvent.KEYCODE_1;
+ int k2 = KeyEvent.KEYCODE_2;
+ int k3 = KeyEvent.KEYCODE_3;
+ int k4 = KeyEvent.KEYCODE_4;
+ int k5 = KeyEvent.KEYCODE_5;
+ int k6 = KeyEvent.KEYCODE_6;
+ int k7 = KeyEvent.KEYCODE_7;
+ int k8 = KeyEvent.KEYCODE_8;
+ int k9 = KeyEvent.KEYCODE_9;
+
+ // The root of the tree doesn't contain any numbers.
+ mLegalTimesTree = new Node();
+ if (mIs24HourMode) {
+ // We'll be re-using these nodes, so we'll save them.
+ Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
+ Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ // The first digit must be followed by the second digit.
+ minuteFirstDigit.addChild(minuteSecondDigit);
+
+ // The first digit may be 0-1.
+ Node firstDigit = new Node(k0, k1);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 0-1, the second digit may be 0-5.
+ Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+ // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
+ Node thirdDigit = new Node(k6, k7, k8, k9);
+ // The time must now be finished. E.g. 0:55, 1:08.
+ secondDigit.addChild(thirdDigit);
+
+ // When the first digit is 0-1, the second digit may be 6-9.
+ secondDigit = new Node(k6, k7, k8, k9);
+ firstDigit.addChild(secondDigit);
+ // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // The first digit may be 2.
+ firstDigit = new Node(k2);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 2, the second digit may be 0-3.
+ secondDigit = new Node(k0, k1, k2, k3);
+ firstDigit.addChild(secondDigit);
+ // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // When the first digit is 2, the second digit may be 4-5.
+ secondDigit = new Node(k4, k5);
+ firstDigit.addChild(secondDigit);
+ // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
+ secondDigit.addChild(minuteSecondDigit);
+
+ // The first digit may be 3-9.
+ firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
+ firstDigit.addChild(minuteFirstDigit);
+ } else {
+ // We'll need to use the AM/PM node a lot.
+ // Set up AM and PM to respond to "a" and "p".
+ Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
+
+ // The first hour digit may be 1.
+ Node firstDigit = new Node(k1);
+ mLegalTimesTree.addChild(firstDigit);
+ // We'll allow quick input of on-the-hour times. E.g. 1pm.
+ firstDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit may be 0-2.
+ Node secondDigit = new Node(k0, k1, k2);
+ firstDigit.addChild(secondDigit);
+ // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
+ secondDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
+ Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
+ secondDigit.addChild(thirdDigit);
+ // The time may be finished now. E.g. 1:02pm, 1:25am.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
+ // the fourth digit may be 0-9.
+ Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ thirdDigit.addChild(fourthDigit);
+ // The time must be finished now. E.g. 10:49am, 12:40pm.
+ fourthDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
+ thirdDigit = new Node(k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 1:08am, 1:26pm.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit may be 3-5.
+ secondDigit = new Node(k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+
+ // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
+ thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 1:39am, 1:50pm.
+ thirdDigit.addChild(ampm);
+
+ // The hour digit may be 2-9.
+ firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
+ firstDigit.addChild(ampm);
+
+ // When the first digit is 2-9, the second digit may be 0-5.
+ secondDigit = new Node(k0, k1, k2, k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+
+ // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
+ thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 2:57am, 9:30pm.
+ thirdDigit.addChild(ampm);
+ }
+ }
+
+ private class Node {
+ private int[] mLegalKeys;
+ private ArrayList<Node> mChildren;
+
+ public Node(int... legalKeys) {
+ mLegalKeys = legalKeys;
+ mChildren = new ArrayList<Node>();
+ }
+
+ public void addChild(Node child) {
+ mChildren.add(child);
+ }
+
+ public boolean containsKey(int key) {
+ for (int i = 0; i < mLegalKeys.length; i++) {
+ if (mLegalKeys[i] == key) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Node canReach(int key) {
+ if (mChildren == null) {
+ return null;
+ }
+ for (Node child : mChildren) {
+ if (child.containsKey(key)) {
+ return child;
+ }
+ }
+ return null;
+ }
+ }
+
+ private class KeyboardListener implements OnKeyListener {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ return processKeyUp(keyCode);
+ }
+ return false;
+ }
+ }
+}