diff options
author | hoffc <hoffc@codeaurora.org> | 2016-05-10 17:06:13 +0800 |
---|---|---|
committer | Linux Build Service Account <lnxbuild@localhost> | 2016-08-24 08:06:58 -0600 |
commit | 3f6f404180ea40416cf517838eec2744b6ec5a68 (patch) | |
tree | a07d3d2ff1d34bbb4568005bf327a68f70219bad | |
parent | a9879bf10566963bbdc6cb42a79356ad39b9fbd5 (diff) | |
download | android_packages_apps_DeskClock-3f6f404180ea40416cf517838eec2744b6ec5a68.tar.gz android_packages_apps_DeskClock-3f6f404180ea40416cf517838eec2744b6ec5a68.tar.bz2 android_packages_apps_DeskClock-3f6f404180ea40416cf517838eec2744b6ec5a68.zip |
DeskClock: Implement QTI Enchanced features for Android N
(1) support delay alarm feature when phone_hook state
(2) support force alarm alert when device volume is zero
(3) support missed the alarm feature when phone_hook state
(4) Ux enhance feature: support audio media in SDCard as the alarm alert feature
(5) Ux enhance feature: add default alarm alert setting item to Setting Actitiy
(6) Ux enhance feature: support manual add city&timezone pair to City DB
(7) Ux enhance feature: shake do specified action when alarm alerting
(8) Ux enhance feature: flip do specified action when alarm alerting
CRs-Fixed: 1014635
Change-Id: I8b027479025e535248dd3e575bc062cedb7c33ae
51 files changed, 1429 insertions, 491 deletions
diff --git a/res/drawable-hdpi/ic_actionbar_add.png b/res/drawable-hdpi/ic_actionbar_add.png Binary files differnew file mode 100755 index 000000000..e051e8c19 --- /dev/null +++ b/res/drawable-hdpi/ic_actionbar_add.png diff --git a/res/drawable-hdpi/ic_gps_disabled.png b/res/drawable-hdpi/ic_gps_disabled.png Binary files differindex fa0ab79d1..3c055e243 100644..100755 --- a/res/drawable-hdpi/ic_gps_disabled.png +++ b/res/drawable-hdpi/ic_gps_disabled.png diff --git a/res/drawable-hdpi/ic_gps_off.png b/res/drawable-hdpi/ic_gps_off.png Binary files differdeleted file mode 100644 index 1d123c481..000000000 --- a/res/drawable-hdpi/ic_gps_off.png +++ /dev/null diff --git a/res/drawable-hdpi/ic_gps_on.png b/res/drawable-hdpi/ic_gps_on.png Binary files differindex 5138b70a1..6dbfa43d1 100644..100755 --- a/res/drawable-hdpi/ic_gps_on.png +++ b/res/drawable-hdpi/ic_gps_on.png diff --git a/res/drawable-mdpi/ic_actionbar_add.png b/res/drawable-mdpi/ic_actionbar_add.png Binary files differnew file mode 100755 index 000000000..a5cec3ec2 --- /dev/null +++ b/res/drawable-mdpi/ic_actionbar_add.png diff --git a/res/drawable-mdpi/ic_gps_disabled.png b/res/drawable-mdpi/ic_gps_disabled.png Binary files differindex df16865c9..072df31d7 100644..100755 --- a/res/drawable-mdpi/ic_gps_disabled.png +++ b/res/drawable-mdpi/ic_gps_disabled.png diff --git a/res/drawable-mdpi/ic_gps_off.png b/res/drawable-mdpi/ic_gps_off.png Binary files differdeleted file mode 100644 index beb87734d..000000000 --- a/res/drawable-mdpi/ic_gps_off.png +++ /dev/null diff --git a/res/drawable-mdpi/ic_gps_on.png b/res/drawable-mdpi/ic_gps_on.png Binary files differindex 9e98ad9ed..6e258e081 100644..100755 --- a/res/drawable-mdpi/ic_gps_on.png +++ b/res/drawable-mdpi/ic_gps_on.png diff --git a/res/drawable-xhdpi/ic_actionbar_add.png b/res/drawable-xhdpi/ic_actionbar_add.png Binary files differnew file mode 100755 index 000000000..667e1fc1f --- /dev/null +++ b/res/drawable-xhdpi/ic_actionbar_add.png diff --git a/res/drawable-xhdpi/ic_gps_disabled.png b/res/drawable-xhdpi/ic_gps_disabled.png Binary files differindex fe612c4fd..27fb3e343 100644..100755 --- a/res/drawable-xhdpi/ic_gps_disabled.png +++ b/res/drawable-xhdpi/ic_gps_disabled.png diff --git a/res/drawable-xhdpi/ic_gps_off.png b/res/drawable-xhdpi/ic_gps_off.png Binary files differdeleted file mode 100644 index e34aecdbd..000000000 --- a/res/drawable-xhdpi/ic_gps_off.png +++ /dev/null diff --git a/res/drawable-xhdpi/ic_gps_on.png b/res/drawable-xhdpi/ic_gps_on.png Binary files differindex d1d97685d..49202a572 100644..100755 --- a/res/drawable-xhdpi/ic_gps_on.png +++ b/res/drawable-xhdpi/ic_gps_on.png diff --git a/res/drawable-xxhdpi/ic_actionbar_add.png b/res/drawable-xxhdpi/ic_actionbar_add.png Binary files differnew file mode 100755 index 000000000..55ddb4ba0 --- /dev/null +++ b/res/drawable-xxhdpi/ic_actionbar_add.png diff --git a/res/drawable-xxhdpi/ic_gps_disabled.png b/res/drawable-xxhdpi/ic_gps_disabled.png Binary files differnew file mode 100755 index 000000000..4e37f0010 --- /dev/null +++ b/res/drawable-xxhdpi/ic_gps_disabled.png diff --git a/res/drawable-xxhdpi/ic_gps_on.png b/res/drawable-xxhdpi/ic_gps_on.png Binary files differnew file mode 100755 index 000000000..d6c326f59 --- /dev/null +++ b/res/drawable-xxhdpi/ic_gps_on.png diff --git a/res/drawable/holo_selector_nonfocusable.xml b/res/drawable/holo_selector_nonfocusable.xml index c0ec9d5da..604eb910e 100644..100755 --- a/res/drawable/holo_selector_nonfocusable.xml +++ b/res/drawable/holo_selector_nonfocusable.xml @@ -14,14 +14,8 @@ ** See the License for the specific language governing permissions and ** limitations under the License. --> - <selector xmlns:android="http://schemas.android.com/apk/res/android" - android:exitFadeDuration="@android:integer/config_shortAnimTime"> - - <item - android:drawable="@android:color/holo_blue_dark" - android:state_pressed="true"/> - <item - android:drawable="@android:color/transparent"/> - + android:exitFadeDuration="@android:integer/config_shortAnimTime"> + <item android:drawable="@android:color/holo_blue_dark" android:state_pressed="true"/> + <item android:drawable="@android:color/transparent"/> </selector>
\ No newline at end of file diff --git a/res/drawable/ic_gps.xml b/res/drawable/ic_gps.xml index 9f6ead003..0d81791da 100644..100755 --- a/res/drawable/ic_gps.xml +++ b/res/drawable/ic_gps.xml @@ -13,8 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:drawable="@drawable/ic_gps_disabled" android:state_enabled="false"/> - <item android:drawable="@drawable/ic_gps_on"/> + <item android:drawable="@drawable/ic_gps_disabled" android:state_enabled="false"/> + <item android:drawable="@drawable/ic_gps_on"/> </selector>
\ No newline at end of file diff --git a/res/drawable/ic_gps_anim.xml b/res/drawable/ic_gps_anim.xml index d85d3559e..ac8c28794 100644..100755 --- a/res/drawable/ic_gps_anim.xml +++ b/res/drawable/ic_gps_anim.xml @@ -15,6 +15,6 @@ --> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> - <item android:drawable="@drawable/ic_gps_off" android:duration="500" /> + <item android:drawable="@drawable/ic_gps_disabled" android:duration="500" /> <item android:drawable="@drawable/ic_gps_on" android:duration="500" /> </animation-list>
\ No newline at end of file diff --git a/res/layout/city_add.xml b/res/layout/city_add.xml index 9521fcb81..731945ac5 100644..100755 --- a/res/layout/city_add.xml +++ b/res/layout/city_add.xml @@ -13,64 +13,69 @@ See the License for the specific language governing permissions and limitations under the License. --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:orientation="vertical" android:paddingLeft="8dip" - android:paddingRight="8dip" - android:orientation="vertical"> + android:paddingRight="8dip" > <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/cities_add_city_city" - android:layout_marginTop="16dp"/> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:paddingLeft="16dp" + android:text="@string/cities_add_city_city" /> <RelativeLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" > - <EditText - android:id="@+id/add_city_name" - android:layout_width="match_parent" - android:layout_height="48dp" - android:imeOptions="actionDone|flagNoFullscreen" - android:scrollHorizontally="true" - android:selectAllOnFocus="true" - android:paddingLeft="48dp" - android:inputType="textNoSuggestions"> - <requestFocus /> - </EditText> + <EditText + android:id="@+id/add_city_name" + android:layout_width="match_parent" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:layout_marginLeft="24dp" + android:imeOptions="actionDone|flagNoFullscreen" + android:inputType="textNoSuggestions" + android:maxLength="255" + android:paddingLeft="32dp" + android:scrollHorizontally="true" + android:selectAllOnFocus="true" > - <ImageButton - android:id="@+id/add_city_gps" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:contentDescription="@string/cities_add_city_gps_cd" - android:src="@drawable/ic_gps" - android:background="@drawable/holo_selector_nonfocusable" - android:scaleType="fitCenter" - android:layout_margin="5dp" - android:layout_alignLeft="@id/add_city_name" - android:layout_alignTop="@id/add_city_name" - android:layout_alignBottom="@id/add_city_name" /> + <requestFocus /> + </EditText> + <ImageButton + android:id="@+id/add_city_gps" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_alignBottom="@id/add_city_name" + android:layout_alignTop="@id/add_city_name" + android:layout_margin="8dp" + android:background="@drawable/holo_selector_nonfocusable" + android:contentDescription="@string/cities_add_city_gps_cd" + android:gravity="center" + android:src="@drawable/ic_gps" /> </RelativeLayout> <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/cities_add_city_timezone" - android:layout_marginTop="16dp"/> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:paddingLeft="16dp" + android:text="@string/cities_add_city_timezone" /> - <Spinner - android:id="@+id/add_city_tz" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="left|center_vertical" - android:spinnerMode="dropdown" - android:layout_marginBottom="16dp" /> + <com.android.deskclock.worldclock.TimeZoneSpinner + android:id="@+id/add_city_tz" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:gravity="left|center_vertical" + android:paddingTop="5dp" + android:spinnerMode="dropdown" /> </LinearLayout>
\ No newline at end of file diff --git a/res/layout/time_zone_list.xml b/res/layout/time_zone_list.xml new file mode 100755 index 000000000..5bc332c65 --- /dev/null +++ b/res/layout/time_zone_list.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (c) 2016, The Linux Foundation. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> + + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tz_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <ListView + android:id="@+id/tz_list" + android:layout_width="match_parent" + android:layout_height="336dp" + android:paddingLeft="14dp" + android:paddingRight="14dp" /> + +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/time_zone_list_item.xml b/res/layout/time_zone_list_item.xml new file mode 100755 index 000000000..9e6e1b5b0 --- /dev/null +++ b/res/layout/time_zone_list_item.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (c) 2016, The Linux Foundation. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <LinearLayout + android:id="@+id/tz_ll" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:id="@android:id/text1" + style="?android:attr/spinnerItemStyle" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:ellipsize="marquee" + android:gravity="left|center_vertical" + android:singleLine="true" + android:textAlignment="inherit" /> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/res/menu/desk_clock_menu.xml b/res/menu/desk_clock_menu.xml index 1e54ed4b9..17269c527 100644 --- a/res/menu/desk_clock_menu.xml +++ b/res/menu/desk_clock_menu.xml @@ -28,8 +28,9 @@ app:showAsAction="ifRoom"/> <item android:id="@+id/menu_item_add" android:title="@string/menu_item_add" - android:icon="@drawable/ic_menu_add" - android:showAsAction="ifRoom|withText"/> + android:icon="@drawable/ic_actionbar_add" + app:showAsAction="always" + android:orderInCategory="2"/> <item android:id="@+id/menu_item_sort" android:title="@string/menu_item_sort_by_gmt_offset" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 0e590390c..ebe1c4fdc 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -38,6 +38,10 @@ <string name="default_timer_ringtone_title" msgid="1370799536406862317">"计时结束"</string> <string name="silent_timer_ringtone_title" msgid="7588723137323327647">"静音"</string> <string name="ringtone" msgid="9110746249688559579">"铃声"</string> + <string name="default_alarm_tone_title">默认闹钟铃声</string> + <string name="cities_add_city_title">加城市</string> + <string name="cities_add_city_city">城市:</string> + <string name="cities_add_city_timezone">时区:</string> <string name="time" msgid="8067216534232296518">"时间"</string> <string name="alarm_tomorrow" msgid="131356848787643420">"明天"</string> <string name="alarm_today" msgid="7873594221106531654">"今天"</string> @@ -359,6 +363,11 @@ <string name="collapse_alarm" msgid="3561772046433483980">"折叠闹钟"</string> <string name="alarm_undo" msgid="5710042601177655254">"撤消"</string> <string name="alarm_deleted" msgid="6131529309389084785">"闹钟已删除"</string> + <string name="alarm_select">选择铃声</string> + <string name="alarm_select_ringtone">铃声</string> + <string name="alarm_select_external">音乐</string> + <string name="alarm_select_ok">确定</string> + <string name="alarm_select_cancel">取消</string> <string name="slash" msgid="2077577763821006919">"/"</string> <string name="world_day_of_week_label" msgid="5911196322328341288">"/ <xliff:g id="LABEL">%s</xliff:g>"</string> <string name="next_alarm_description" msgid="2650244835760747046">"下次闹钟时间:<xliff:g id="ALARM_TIME">%s</xliff:g>"</string> @@ -405,4 +414,12 @@ <string name="pick_alarm_to_dismiss" msgid="5408769235866082896">"请选择要关闭的闹钟"</string> <string name="no_firing_alarms" msgid="4986161963178722289">"目前没有正在响铃的闹钟"</string> <string name="alarm_is_snoozed" msgid="7044644119744928846">"<xliff:g id="ALARM_TIME">%s</xliff:g> 的闹钟已延后 10 分钟"</string> + <string name="flip_action_title">翻转行为</string> + <string name="shake_action_title">摇动行为</string> + <string name="shake_action_summary">摇动设备将<xliff:g id="action">%s</xliff:g></string> + <string name="flip_action_summary">翻转设备将<xliff:g id="action">%s</xliff:g></string> + <!-- Summary texts for shake and flip actions --> + <string name="action_summary_snooze">暂停闹铃</string> + <string name="action_summary_dismiss">停止闹铃</string> + <string name="action_summary_do_nothing">不产生任何作用</string> </resources> diff --git a/res/values/array.xml b/res/values/array.xml index 2898e15fa..511f10b72 100644 --- a/res/values/array.xml +++ b/res/values/array.xml @@ -954,4 +954,9 @@ <item>C299</item> <item>C300</item> </string-array> + <string-array name="action_summary_entries" translatable="false"> + <item>@string/action_summary_snooze</item> + <item>@string/action_summary_dismiss</item> + <item>@string/action_summary_do_nothing</item> + </string-array> </resources> diff --git a/res/values/colors.xml b/res/values/colors.xml index c1b5eaccc..1ca6960c7 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -39,6 +39,7 @@ <color name="color_accent">#DA4336</color> <color name="hairline">#28ffffff</color> <color name="bright_foreground_light_disabled">#80000000</color> + <color name="dialog_positive_button_disable">#4CDA4336</color> <color name="clock_white">#ffffff</color> <color name="clock_gray">#B3ffffff</color> diff --git a/res/values/config.xml b/res/values/config.xml index 89a417744..7e18ab426 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -28,6 +28,7 @@ <item type="integer" name="world_clocks_per_row">1</item> <!-- Number of world clocks in a row, for the digital appwidget. --> <item type="integer" name="appwidget_world_clocks_per_row">2</item> + <bool name="config_delayalarm">false</bool> <bool name="config_ring_alarm_force">false</bool> <bool name="config_silent_during_call">false</bool> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index ad7c46930..1e2f63e33 100644..100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -364,6 +364,8 @@ <!-- Title of the setting to change hardware button behavior. This string should be changed for each piece of hardware. [CHAR LIMIT=20] --> <string name="volume_button_setting_title">Volume buttons</string> + <!-- Title of the setting default alarm tone --> + <string name="default_alarm_tone_title">Default alarm tone</string> <!-- Dialog title of the volume and power setting. --> <string name="volume_button_dialog_title">Button effect</string> @@ -438,28 +440,30 @@ <string name="menu_item_settings">Settings</string> <!-- Menu item on most screens to get to the help information --> <string name="menu_item_help">Help</string> + <!-- Menu item on clock screen to add world city. --> + <string name="menu_item_add">Add</string> + <!-- Menu item on clock screen to enter night mode. --> <string name="menu_item_night_mode">Night mode</string> <!-- Menu item on Cities screen to sort by GMT offset --> <string name="menu_item_sort_by_gmt_offset">Sort by time</string> <!-- Menu item on Cities screen to sort by alphabetical order --> <string name="menu_item_sort_by_name">Sort by name</string> - - <!-- User-defined cities --> - <string name="menu_item_add">Add</string> + <string name="cities_add_gps_no_results">GPS query returns no location results</string> + <string name="cities_add_gps_not_available">GPS not available</string> + <string name="cities_add_searching">Searching\u2026</string> <string name="cities_add_city_title">Add city</string> - <string name="cities_add_city_city">City:</string> - <string name="cities_add_city_timezone">Timezone:</string> <string name="cities_add_city_gps_cd">Tap to localize your current city</string> <string name="cities_add_loading">Loading\u2026</string> - <string name="cities_add_searching">Searching\u2026</string> - <string name="cities_add_gps_no_results">GPS query returns no location results</string> - <string name="cities_add_gps_not_available">GPS not available</string> + <string name="cities_add_city_city">City:</string> + <string name="cities_add_city_timezone">Timezone:</string> <string name="cities_add_already_exists">The city already exists in the database</string> <string name="cities_add_city_failed">Can\'t create the city</string> - <string name="cities_delete_city_title">Delete city</string> - <string name="cities_delete_city_msg">Do you want to delete <xliff:g id="city">%s</xliff:g>?</string> <string name="cities_delete_city_failed">Can\'t delete the city</string> + <string name="cities_delete_city_msg">Do you want to delete + <xliff:g id="city">%s</xliff:g>? + </string> + <string name="cities_delete_city_title">Delete city</string> <!-- Label for selected cities in Cities list view --> <string name="selected_cities_label">Selected Cities</string> @@ -877,6 +881,16 @@ <string name="alarm_undo">undo</string> <!-- Toast content when an alarm was deleted --> <string name="alarm_deleted">Alarm deleted</string> + <!-- Select the source of the alarm --> + <string name="alarm_select">Select Alarm</string> + <!-- Select the ringtone as alarm --> + <string name="alarm_select_ringtone">Ringtone</string> + <!-- Select the alarm from external device --> + <string name="alarm_select_external">Music</string> + <!-- Ok for selection --> + <string name="alarm_select_ok">OK</string> + <!-- Cancel for selection --> + <string name="alarm_select_cancel">Cancel</string> <!-- slash between date and next alarm in the clock --> <string name="slash"> / </string> <!-- slash between date and next alarm in the clock --> @@ -1122,6 +1136,26 @@ [CHAR LIMIT=NONE] --> <string name="alarm_is_snoozed"><xliff:g id="alarm_time" example="14:20">%s</xliff:g> alarm snoozed for 10 minutes</string> + <!-- Setting title for the flip action setting. --> + <string name="flip_action_title">Flip action</string> + + <!-- Setting summary for the flip action setting. --> + <string name="flip_action_summary" product="default">Flipping the phone down will + <xliff:g id="action">%s</xliff:g> + </string> + + <!-- Setting title for the shake action setting. --> + <string name="shake_action_title">Shake action</string> + + <!-- Setting summary for the shake action setting. --> + <string name="shake_action_summary" product="default">Shaking the phone will + <xliff:g id="action">%s</xliff:g> + </string> + + <!-- Summary texts for shake and flip actions --> + <string name="action_summary_snooze">snooze the alarm</string> + <string name="action_summary_dismiss">dismiss the alarm</string> + <string name="action_summary_do_nothing">do nothing</string> </resources> diff --git a/res/xml/backup_scheme.xml b/res/xml/backup_scheme.xml index 36ee3e822..08c9a0b1a 100644 --- a/res/xml/backup_scheme.xml +++ b/res/xml/backup_scheme.xml @@ -17,4 +17,8 @@ <full-backup-content> <include domain="database" path="alarms.db" /> <include domain="sharedpref" path="com.android.deskclock_preferences.xml" /> + <include domain="database" path="cities.db" /> + <include domain="sharedpref" path="ce_shared_preferences.xml" /> + <include domain="sharedpref" path="de_shared_preferences.xml" /> + </full-backup-content>
\ No newline at end of file diff --git a/res/xml/settings.xml b/res/xml/settings.xml index 405ab4ae9..06d33b2cb 100644..100755 --- a/res/xml/settings.xml +++ b/res/xml/settings.xml @@ -47,8 +47,8 @@ </PreferenceCategory> <PreferenceCategory - android:title="@string/alarm_settings"> - + android:title="@string/alarm_settings" + android:key="key_alarm_settings"> <ListPreference android:key="auto_silence" android:title="@string/auto_silence_title" @@ -79,21 +79,24 @@ android:entryValues="@array/volume_button_setting_values" android:defaultValue="0" /> + <com.android.deskclock.settings.DefaultAlarmToneDialog + android:key="default_alarm_tone" + android:title="@string/default_alarm_tone_title" /> + <ListPreference + android:defaultValue="0" + android:dialogTitle="@string/flip_action_title" + android:entries="@array/volume_button_setting_entries" + android:entryValues="@array/volume_button_setting_values" android:key="flip_action" - android:title="@string/flip_action_title" - android:dialogTitle="@string/flip_action_dialog_title" - android:entries="@array/action_summary_entries" - android:entryValues="@array/action_summary_values" - android:defaultValue="0" /> - + android:title="@string/flip_action_title" /> <ListPreference + android:defaultValue="0" + android:dialogTitle="@string/shake_action_title" + android:entries="@array/volume_button_setting_entries" + android:entryValues="@array/volume_button_setting_values" android:key="shake_action" - android:title="@string/shake_action_title" - android:dialogTitle="@string/shake_action_dialog_title" - android:entries="@array/action_summary_entries" - android:entryValues="@array/action_summary_values" - android:defaultValue="0"/> + android:title="@string/shake_action_title" /> <ListPreference android:key="week_start" android:title="@string/week_start_title" diff --git a/src/com/android/deskclock/AlarmClockFragment.java b/src/com/android/deskclock/AlarmClockFragment.java index d443ba5a1..ac571debe 100644 --- a/src/com/android/deskclock/AlarmClockFragment.java +++ b/src/com/android/deskclock/AlarmClockFragment.java @@ -16,14 +16,20 @@ package com.android.deskclock; +import android.Manifest; import android.app.Activity; import android.app.LoaderManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.Loader; import android.database.Cursor; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; import android.support.design.widget.Snackbar; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -39,6 +45,7 @@ import com.android.deskclock.alarms.TimePickerCompat; import com.android.deskclock.alarms.dataadapter.AlarmTimeAdapter; import com.android.deskclock.data.DataModel; import com.android.deskclock.provider.Alarm; +import com.android.deskclock.settings.DefaultAlarmToneDialog; import com.android.deskclock.widget.EmptyViewController; import com.android.deskclock.widget.toast.SnackbarManager; import com.android.deskclock.widget.toast.ToastManager; @@ -57,6 +64,21 @@ public final class AlarmClockFragment extends DeskClockFragment implements // can not be found, and toast message will pop up that the alarm has be deleted. public static final String SCROLL_TO_ALARM_INTENT_EXTRA = "deskclock.scroll.to.alarm"; + public static final String SEL_AUDIO_SRC = "audio/*"; + public static final int SEL_SRC_RINGTONE = 0; + public static final int SEL_SRC_EXTERNAL = 1; + //extend request index is from 10 start + public static final int REQUEST_CODE_RINGTONE = 10; + public static final int REQUEST_CODE_PERMISSIONS = 11; + public static final int REQUEST_CODE_EXTERN_AUDIO = 12; + + //QC hoffc added begin feature6 + private static final String QUERY_URI = "content://com.android.deskclock/alarms"; + private String old_default_ringtone_uri; + private String new_default_ringtone_Uri; + private RefreshDefaultRingtoneBroadcastReceiver mReceiver; + //QC hoffc added end + // Views private ViewGroup mMainLayout; private RecyclerView mRecyclerView; @@ -81,6 +103,11 @@ public final class AlarmClockFragment extends DeskClockFragment implements public void onCreate(Bundle savedState) { super.onCreate(savedState); mCursorLoader = getLoaderManager().initLoader(0, null, this); + + mReceiver = new RefreshDefaultRingtoneBroadcastReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(DefaultAlarmToneDialog.REFRESH_DEFAULT_RINGTONE_ACTION); + getActivity().registerReceiver(mReceiver, filter); } @Override @@ -156,6 +183,8 @@ public final class AlarmClockFragment extends DeskClockFragment implements public void onDestroy() { super.onDestroy(); ToastManager.cancelToast(); + + getActivity().unregisterReceiver(mReceiver); } @Override @@ -227,15 +256,22 @@ public final class AlarmClockFragment extends DeskClockFragment implements switch (requestCode) { case R.id.request_code_ringtone: + case REQUEST_CODE_RINGTONE: + case REQUEST_CODE_EXTERN_AUDIO: // Extract the selected ringtone uri. - Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + + Uri uri = null; + if (requestCode == REQUEST_CODE_EXTERN_AUDIO) { + uri = data.getData(); + } else { + uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + } + LogUtils.d(LogUtils.LOGTAG, "AlarmClockFragment:onActivityResult: uri = " + uri); + if (uri == null) { uri = Alarm.NO_RINGTONE_URI; } - // Update the default ringtone for future new alarms. - DataModel.getDataModel().setDefaultAlarmRingtoneUri(uri); - // Set the ringtone uri on the alarm. final Alarm alarm = mAlarmTimeClickHandler.getSelectedAlarm(); if (alarm == null) { @@ -247,6 +283,13 @@ public final class AlarmClockFragment extends DeskClockFragment implements // Save the change to alarm. mAlarmUpdateHandler.asyncUpdateAlarm(alarm, false /* popToast */, true /* minorUpdate */); + + // If the user chose an external ringtone and has not yet granted the permission to read + // external storage, ask them for that permission now. + if (!AlarmUtils.hasPermissionToDisplayRingtoneTitle(getActivity(), uri)) { + final String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE}; + requestPermissions(perms, REQUEST_CODE_PERMISSIONS); + } break; default: LogUtils.w("Unhandled request code in onActivityResult: " + requestCode); @@ -289,4 +332,61 @@ public final class AlarmClockFragment extends DeskClockFragment implements TimePickerCompat.showTimeEditDialog(this, null /* alarm */, DateFormat.is24HourFormat(getActivity())); } + + private class RefreshDefaultRingtoneBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + old_default_ringtone_uri = intent + .getStringExtra(DefaultAlarmToneDialog.OLD_RING_TONE_URI_STRING); + new_default_ringtone_Uri = intent + .getStringExtra(DefaultAlarmToneDialog.NEW_RING_TONE_URI_STRING); + + LogUtils.d(LogUtils.LOGTAG, "old_default_ringtone_uri = " + old_default_ringtone_uri + + " new_default_ringtone_Uri = " + new_default_ringtone_Uri); + + updateAlarmRingTone(new_default_ringtone_Uri, old_default_ringtone_uri); + updateAlarmRingTone(new_default_ringtone_Uri, Settings.System.DEFAULT_ALARM_ALERT_URI.toString()); + + // Update the default ringtone for future new alarms. + DataModel.getDataModel().setDefaultAlarmRingtoneUri(Uri.parse(new_default_ringtone_Uri)); + + } + } + + private void updateAlarmRingTone(String newRingtone, String oldRingtone) { + Context accessContext = getActivity().getApplicationContext(); + ContentResolver cr = accessContext.getContentResolver(); + + String selection = "ringtone = ?"; + String[] selectionArgs; + + if(oldRingtone != null + && !oldRingtone.equals(Settings.System.DEFAULT_ALARM_ALERT_URI.toString())) { + selectionArgs = new String[] {oldRingtone}; + } else { + selectionArgs = new String[] {Settings.System.DEFAULT_ALARM_ALERT_URI.toString()}; + } + + LogUtils.d(LogUtils.LOGTAG, "updateAlarmRingTone: selectionArgs = " + selectionArgs[0]); + + Cursor cursor = cr.query( + Uri.parse(QUERY_URI), + new String[] { "*" }, selection, selectionArgs, null); + + Alarm a = null; + if (cursor != null && cursor.getCount() > 0) { + LogUtils.d(LogUtils.LOGTAG, "updateAlarmRingTone cursor.getCount() = " + cursor.getCount()); + while (cursor.moveToNext()) { + a = new Alarm(cursor, Uri.parse(newRingtone)); + LogUtils.d(LogUtils.LOGTAG, "The update alarm is " + a); + mAlarmUpdateHandler.asyncUpdateAlarm(a, false /* popToast */, + true /* minorUpdate */); + a = null; + } + } + + if (cursor != null || cursor.getCount() == 0) { + cursor.close(); + } + } } diff --git a/src/com/android/deskclock/AlarmUtils.java b/src/com/android/deskclock/AlarmUtils.java index 5f91a0066..4e3f1e2c6 100644 --- a/src/com/android/deskclock/AlarmUtils.java +++ b/src/com/android/deskclock/AlarmUtils.java @@ -16,7 +16,12 @@ package com.android.deskclock; +import android.Manifest; import android.content.Context; +import android.content.pm.PackageManager; +import android.media.RingtoneManager; +import android.net.Uri; +import android.provider.Settings; import android.support.annotation.VisibleForTesting; import android.support.design.widget.Snackbar; import android.text.format.DateFormat; @@ -24,6 +29,7 @@ import android.text.format.DateUtils; import android.view.View; import android.widget.Toast; +import com.android.deskclock.provider.Alarm; import com.android.deskclock.provider.AlarmInstance; import com.android.deskclock.widget.toast.SnackbarManager; import com.android.deskclock.widget.toast.ToastManager; @@ -101,4 +107,34 @@ public class AlarmUtils { SnackbarManager.show(Snackbar.make(snackbarAnchor, text, Snackbar.LENGTH_SHORT)); snackbarAnchor.announceForAccessibility(text); } + + /** + * @return {@code true} iff the user has granted permission to read the ringtone at the given + * uri or no permission is required to read the ringtone + */ + public static boolean hasPermissionToDisplayRingtoneTitle(Context context, Uri ringtoneUri) { + final PackageManager pm = context.getPackageManager(); + final String packageName = context.getPackageName(); + + // If the default alarm alert ringtone URI is given, resolve it to the actual URI. + if (Settings.System.DEFAULT_ALARM_ALERT_URI.equals(ringtoneUri)) { + ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, + RingtoneManager.TYPE_ALARM); + } + + // If no ringtone is specified, return true. + if (ringtoneUri == null || ringtoneUri == Alarm.NO_RINGTONE_URI) { + return true; + } + + // If the permission is already granted, return true. + if (pm.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE, packageName) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + // If the ringtone is internal, return true; + // external ringtones require the permission to see their title + return ringtoneUri.toString().startsWith("content://media/internal/"); + } } diff --git a/src/com/android/deskclock/AsyncRingtonePlayer.java b/src/com/android/deskclock/AsyncRingtonePlayer.java index 3fb8230e4..1ce3cb485 100644 --- a/src/com/android/deskclock/AsyncRingtonePlayer.java +++ b/src/com/android/deskclock/AsyncRingtonePlayer.java @@ -63,7 +63,7 @@ public final class AsyncRingtonePlayer { private PlaybackDelegate mPlaybackDelegate; /** The context. */ - private static Context mContext; + private final Context mContext; /** The key of the preference that controls the crescendo behavior when playing a ringtone. */ private final String mCrescendoPrefKey; @@ -367,6 +367,7 @@ public final class AsyncRingtonePlayer { .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build()); } + player.setAudioStreamType(AudioManager.STREAM_ALARM); player.setLooping(true); player.prepare(); @@ -375,7 +376,7 @@ public final class AsyncRingtonePlayer { player.start(); } else if (mContext.getResources().getBoolean(R.bool.config_ring_alarm_force)) { mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, - mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0); + mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0); player.setAudioStreamType(AudioManager.STREAM_ALARM); player.setLooping(true); player.prepare(); diff --git a/src/com/android/deskclock/Utils.java b/src/com/android/deskclock/Utils.java index 251b59924..fe6c29d1f 100644..100755 --- a/src/com/android/deskclock/Utils.java +++ b/src/com/android/deskclock/Utils.java @@ -68,13 +68,6 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.GregorianCalendar; - -import com.android.deskclock.worldclock.db.DbCities; -import com.android.deskclock.worldclock.db.DbCity; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import java.util.Locale; import java.util.TimeZone; @@ -96,6 +89,9 @@ public class Utils { */ private static final int[] TEMP_ARRAY = new int[1]; + public static final String DESKCLOCK_DE_SHARED_PREFERENCES = "de_sharedPreferences"; + public static final String DESKCLOCK_CE_SHARED_PREFERENCES = "ce_sharedPreferences"; + /** * The background colors of the app - it changes throughout out the day to mimic the sky. */ @@ -707,43 +703,6 @@ public class Utils { final ArraySet<E> arraySet = new ArraySet<>(collection.size()); arraySet.addAll(collection); return arraySet; - - public static CityObj[] loadCitiesDataBase(Context c) { - final Collator collator = Collator.getInstance(); - Resources r = c.getResources(); - - // Get list of cities defined by the app (App-defined has the prefix C) - // Read strings array of name,timezone, id - // make sure the list are the same length - String[] cities = r.getStringArray(R.array.cities_names); - String[] timezones = r.getStringArray(R.array.cities_tz); - String[] ids = r.getStringArray(R.array.cities_id); - if (cities.length != timezones.length || ids.length != cities.length) { - Log.wtf("City lists sizes are not the same, cannot use the data"); - return null; - } - List<CityObj> tempList = new ArrayList<CityObj>(cities.length); - for (int i = 0; i < cities.length; i++) { - tempList.add(new CityObj(cities[i], timezones[i], ids[i])); - } - - // Get the list of user-defined cities (User-defined has the prefix UD) - List<DbCity> dbcities = DbCities.getCities(c.getContentResolver()); - for (int i = 0; i < dbcities.size(); i++) { - DbCity dbCity = dbcities.get(i); - CityObj city = new CityObj(dbCity.name, dbCity.tz, "UD" + dbCity.id); - city.mUserDefined = true; - tempList.add(city); - } - - // Sort alphabetically - Collections.sort(tempList, new Comparator<CityObj> () { - @Override - public int compare(CityObj c1, CityObj c2) { - return collator.compare(c1.mCityName, c2.mCityName); - } - }); - return tempList.toArray(new CityObj[tempList.size()]); } /** @@ -767,4 +726,33 @@ public class Utils { return PreferenceManager.getDefaultSharedPreferences(storageContext); } + + /** + * Return the desk clock_de shared preferences. + */ + public static SharedPreferences getDESharedPreferences(Context context) { + final Context storageContext; + if (isNOrLater()) { + // All N devices have split storage areas, but we may need to + // migrate existing preferences into the new device protected + // storage area, which is where our data lives from now on. + final Context deviceContext = context.createDeviceProtectedStorageContext(); + storageContext = deviceContext; + } else { + storageContext = context; + } + + return storageContext.getSharedPreferences(DESKCLOCK_DE_SHARED_PREFERENCES, + Context.MODE_PRIVATE); + } + + + /** + * Return the desk clock_ce shared preferences. + */ + public static SharedPreferences getCESharedPreferences(Context context) { + SharedPreferences sharedPreferences = context.getSharedPreferences( + DESKCLOCK_CE_SHARED_PREFERENCES, Context.MODE_PRIVATE); + return sharedPreferences; + } } diff --git a/src/com/android/deskclock/alarms/AlarmService.java b/src/com/android/deskclock/alarms/AlarmService.java index 3f31c7f8b..b8f84d941 100644..100755 --- a/src/com/android/deskclock/alarms/AlarmService.java +++ b/src/com/android/deskclock/alarms/AlarmService.java @@ -18,32 +18,29 @@ */ package com.android.deskclock.alarms; -import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.Binder; -import android.os.IBinder; import android.content.SharedPreferences; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.preference.PreferenceManager; +import android.os.Binder; +import android.os.IBinder; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import com.android.deskclock.AlarmAlertWakeLock; import com.android.deskclock.LogUtils; - -import com.android.deskclock.SettingsActivity; -import com.android.deskclock.provider.AlarmInstance; import com.android.deskclock.R; +import com.android.deskclock.Utils; import com.android.deskclock.events.Events; import com.android.deskclock.provider.AlarmInstance; +import com.android.deskclock.settings.SettingsActivity; /** * This service is in charge of starting/stopping the alarm. It will bring up and manage the @@ -75,36 +72,24 @@ public class AlarmService extends Service { /** Private action used to stop an alarm with this service. */ public static final String STOP_ALARM_ACTION = "STOP_ALARM"; - /** Binder given to AlarmActivity */ private final IBinder mBinder = new Binder(); - // default action for flip and shake - private static final String DEFAULT_ACTION = "0"; - - // constants for no action/snooze/dismiss - private static final int ALARM_NO_ACTION = 0; - private static final int ALARM_SNOOZE = 1; - private static final int ALARM_DISMISS = 2; - - /** - * Utility method to help start alarm properly. If alarm is already firing, it - * will mark it as missed and start the new one. - * - * @param context application context - * @param instance to trigger alarm - */ - public static void startAlarm(Context context, AlarmInstance instance) { - Intent intent = AlarmInstance.createIntent(context, AlarmService.class, instance.mId); - intent.setAction(START_ALARM_ACTION); - } - /** Whether the service is currently bound to AlarmActivity */ private boolean mIsBound = false; /** Whether the receiver is currently registered */ private boolean mIsRegistered = false; + private SensorManager mSensorManager; + private int mFlipAction; + private int mShakeAction; + private static final int ALARM_NO_ACTION = 0; + private static final int ALARM_SNOOZE = 1; + private static final int ALARM_DISMISS = 2; + // default action for flip and shake + private static final String DEFAULT_ACTION = "0"; + @Override public IBinder onBind(Intent intent) { mIsBound = true; @@ -135,9 +120,6 @@ public class AlarmService extends Service { private TelephonyManager mTelephonyManager; private int mInitialCallState; private AlarmInstance mCurrentAlarm = null; - private SensorManager mSensorManager; - private int mFlipAction; - private int mShakeAction; private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override @@ -146,11 +128,15 @@ public class AlarmService extends Service { // we register onCallStateChanged, we get the initial in-call state // which kills the alarm. Check against the initial call state so // we don't kill the alarm during a call. + if (AlarmService.this.getResources().getBoolean(R.bool.config_silent_during_call) && state != TelephonyManager.CALL_STATE_IDLE) { - sendBroadcast(AlarmStateManager.createStateChangeIntent(AlarmService.this, + startService(AlarmStateManager.createStateChangeIntent(AlarmService.this, "AlarmService", mCurrentAlarm, AlarmInstance.MISSED_STATE)); - } else if (state != TelephonyManager.CALL_STATE_IDLE && state != mInitialCallState) { + return; + } + + if (state != TelephonyManager.CALL_STATE_IDLE && state != mInitialCallState) { startService(AlarmStateManager.createStateChangeIntent(AlarmService.this, "AlarmService", mCurrentAlarm, AlarmInstance.MISSED_STATE)); } @@ -172,6 +158,7 @@ public class AlarmService extends Service { mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); AlarmKlaxon.start(this, mCurrentAlarm); sendBroadcast(new Intent(ALARM_ALERT_ACTION)); + attachListeners(); } @@ -198,8 +185,9 @@ public class AlarmService extends Service { AlarmNotifications.updateNotification(this, mCurrentAlarm); } - mCurrentAlarm = null; detachListeners(); + + mCurrentAlarm = null; AlarmAlertWakeLock.releaseCpuLock(); } @@ -247,7 +235,7 @@ public class AlarmService extends Service { mIsRegistered = true; mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences prefs = Utils.getDefaultSharedPreferences(this); mFlipAction = Integer.parseInt(prefs.getString( SettingsActivity.KEY_FLIP_ACTION, DEFAULT_ACTION)); mShakeAction = Integer.parseInt(prefs.getString( @@ -445,8 +433,8 @@ public class AlarmService extends Service { break; case ALARM_DISMISS: // Setup Dismiss Action - Intent dismissIntent = AlarmStateManager.createStateChangeIntent(this, "DISMISS_TAG", - mCurrentAlarm, AlarmInstance.DISMISSED_STATE); + Intent dismissIntent = AlarmStateManager.createStateChangeIntent(this, + "DISMISS_TAG", mCurrentAlarm, AlarmInstance.DISMISSED_STATE); sendBroadcast(dismissIntent); break; case ALARM_NO_ACTION: @@ -454,5 +442,4 @@ public class AlarmService extends Service { break; } } - } diff --git a/src/com/android/deskclock/alarms/AlarmStateManager.java b/src/com/android/deskclock/alarms/AlarmStateManager.java index d540f77a8..fbbcbc613 100644..100755 --- a/src/com/android/deskclock/alarms/AlarmStateManager.java +++ b/src/com/android/deskclock/alarms/AlarmStateManager.java @@ -30,6 +30,7 @@ import android.os.PowerManager; import android.preference.PreferenceManager; import android.provider.Settings; import android.support.v4.app.NotificationManagerCompat; +import android.telephony.TelephonyManager; import android.text.format.DateFormat; import android.widget.Toast; @@ -131,15 +132,15 @@ public final class AlarmStateManager extends BroadcastReceiver { public static final String ALARM_SNOOZE_TAG = "SNOOZE_TAG"; public static final String ALARM_DELETE_TAG = "DELETE_TAG"; - // key to to retrieve pending alarm set - public static final String ALARM_PENDING_ALARM_KEY = "pending.alarm.key"; - // Intent category tag used when schedule state change intents in alarm manager. private static final String ALARM_MANAGER_TAG = "ALARM_MANAGER"; // Buffer time in seconds to fire alarm instead of marking it missed. public static final int ALARM_FIRE_BUFFER = 15; + // key to to retrieve pending alarm set + public static final String ALARM_PENDING_ALARM_KEY = "pending.alarm.key"; + // A factory for the current time; can be mocked for testing purposes. private static CurrentTimeFactory sCurrentTimeFactory; @@ -168,16 +169,17 @@ public final class AlarmStateManager extends BroadcastReceiver { } public static void updateGlobalIntentId(Context context) { - SharedPreferences prefs = Utils.getDefaultSharedPreferences(context); + SharedPreferences dePrefs = Utils.getDESharedPreferences(context); // If there are any pending alarms, do not update the global id - pending alarms // will be ignored by the receivers when the user tries to dismiss or snooze - Set<String> alarms = prefs.getStringSet(AlarmStateManager.ALARM_PENDING_ALARM_KEY, + Set<String> alarms = dePrefs.getStringSet(AlarmStateManager.ALARM_PENDING_ALARM_KEY, new HashSet<String>()); if (alarms.size() > 0) { return; } + SharedPreferences prefs = Utils.getDefaultSharedPreferences(context); int globalId = prefs.getInt(ALARM_GLOBAL_ID_EXTRA, -1) + 1; prefs.edit().putInt(ALARM_GLOBAL_ID_EXTRA, globalId).commit(); } @@ -848,7 +850,7 @@ public final class AlarmStateManager extends BroadcastReceiver { * @param instance to change state on * @param state to change to */ - public static void setAlarmState(Context context, AlarmInstance instance, int state) { + private static void setAlarmState(Context context, AlarmInstance instance, int state) { if (instance == null) { LogUtils.e("Null alarm instance while setting state to %d", state); return; @@ -909,7 +911,49 @@ public final class AlarmStateManager extends BroadcastReceiver { final String action = intent.getAction(); LogUtils.v("AlarmStateManager received intent " + intent); if (CHANGE_STATE_ACTION.equals(action)) { - handleChangeStateIntent(context, intent); + Uri uri = intent.getData(); + AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(), + AlarmInstance.getId(uri)); + if (instance == null) { + LogUtils.e("Can not change state for unknown instance: " + uri); + return; + } + + int globalId = getGlobalIntentId(context); + int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1); + int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1); + if (intentId != globalId) { + LogUtils.i("IntentId: " + intentId + " GlobalId: " + globalId + " AlarmState: " + + alarmState); + // Allows dismiss/snooze requests to go through + if (!intent.hasCategory(ALARM_DISMISS_TAG) && + !intent.hasCategory(ALARM_SNOOZE_TAG)) { + LogUtils.i("Ignoring old Intent"); + return; + } + } + + if (intent.getBooleanExtra(FROM_NOTIFICATION_EXTRA, false)) { + if (intent.hasCategory(ALARM_DISMISS_TAG)) { + Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_notification); + } else if (intent.hasCategory(ALARM_SNOOZE_TAG)) { + Events.sendAlarmEvent(R.string.action_snooze, R.string.label_notification); + } + } + + //If the phone is busy, add the alarm to a string set in shared preferenecs that will be + // cleared when the call is ended. + if (context.getResources().getBoolean(R.bool.config_delayalarm)) { + TelephonyManager mTelephonyManager = (TelephonyManager) context + .getSystemService(Context.TELEPHONY_SERVICE); + if ((mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) + && (alarmState == AlarmInstance.FIRED_STATE)) { + pendAlarm(context, uri, alarmState); + return; + } + } + + setChangeAlarmState(context, instance, alarmState); } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) { Uri uri = intent.getData(); AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(), @@ -935,76 +979,6 @@ public final class AlarmStateManager extends BroadcastReceiver { } } - private static void handleChangeStateIntent(Context context, Intent intent) { - Uri uri = intent.getData(); - AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(), - AlarmInstance.getId(uri)); - if (instance == null) { - // Not a big deal, but it shouldn't happen - LogUtils.e("Can not change state for unknown instance: " + uri); - return; - } - - int globalId = getGlobalIntentId(context); - int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1); - int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1); - if (intentId != globalId) { - LogUtils.i("Ignoring old Intent. IntentId: " + intentId + " GlobalId: " + globalId + - " AlarmState: " + alarmState); - return; - } - - // If the phone is busy, add the alarm to a string set in shared preferenecs that will be - // cleared when the call is ended. - if (context.getResources().getBoolean(R.bool.config_delayalarm)) { - TelephonyManager mTelephonyManager = (TelephonyManager) context - .getSystemService(Context.TELEPHONY_SERVICE); - if ((mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) - && (alarmState == AlarmInstance.FIRED_STATE)) { - pendAlarm(context, uri, alarmState); - return; - } - } - - setChangeAlarmState(context, instance, alarmState); - } - - public static void setChangeAlarmState(Context context, AlarmInstance instance, - int alarmState) { - - if (alarmState >= 0) { - setAlarmState(context, instance, alarmState); - } else { - // No need to register instance again when alarmState - // equals POWER_OFF_ALARM_STATE. POWER_OFF_ALARM_STATE - // is an invalid state for rtc power off alarm. - if (alarmState == AlarmInstance.POWER_OFF_ALARM_STATE) { - return; - } - registerInstance(context, instance, true); - } - } - - /** - * Add the alarm to list of pending Alarms to be fired after the call is complete. - */ - private static void pendAlarm(Context context, Uri uri, int alarmState) { - LogUtils.v("Pending alarm: " + uri); - - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - Set<String> alarms = sp.getStringSet(ALARM_PENDING_ALARM_KEY, new HashSet<String>()); - - // Alarms are stored as "<uri>|<alarmState>" - String alarm = uri.toString() + "|" + alarmState; - - HashSet<String> newSet = new HashSet<String>(); - newSet.addAll(alarms); - newSet.add(alarm); - - sp.edit().putStringSet(ALARM_PENDING_ALARM_KEY, newSet).commit(); - } - - /** * Creates an intent that can be used to set an AlarmManager alarm to set the next alarm * indicators. @@ -1071,4 +1045,32 @@ public final class AlarmStateManager extends BroadcastReceiver { } } } + + /** + * Add the alarm to list of pending Alarms to be fired after the call is complete. + */ + private static void pendAlarm(Context context, Uri uri, int alarmState) { + LogUtils.v("Pending alarm: " + uri); + + SharedPreferences sp = Utils.getDESharedPreferences(context); + Set<String> alarms = sp.getStringSet(ALARM_PENDING_ALARM_KEY, new HashSet<String>()); + + // Alarms are stored as "<uri>|<alarmState>" + String alarm = uri.toString() + "|" + alarmState; + + HashSet<String> newSet = new HashSet<String>(); + newSet.addAll(alarms); + newSet.add(alarm); + + sp.edit().putStringSet(ALARM_PENDING_ALARM_KEY, newSet).commit(); + } + + public static void setChangeAlarmState(Context context, AlarmInstance instance, + int alarmState) { + if (alarmState >= 0) { + setAlarmState(context, instance, alarmState); + } else { + registerInstance(context, instance, true); + } + } } diff --git a/src/com/android/deskclock/alarms/AlarmTimeClickHandler.java b/src/com/android/deskclock/alarms/AlarmTimeClickHandler.java index 524900381..0f898d766 100644 --- a/src/com/android/deskclock/alarms/AlarmTimeClickHandler.java +++ b/src/com/android/deskclock/alarms/AlarmTimeClickHandler.java @@ -16,9 +16,11 @@ package com.android.deskclock.alarms; +import android.app.AlertDialog; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; @@ -26,6 +28,7 @@ import android.os.Bundle; import android.os.Vibrator; import android.text.format.DateFormat; +import com.android.deskclock.AlarmClockFragment; import com.android.deskclock.LabelDialogFragment; import com.android.deskclock.LogUtils; import com.android.deskclock.R; @@ -43,6 +46,9 @@ public final class AlarmTimeClickHandler { private static final String TAG = "AlarmTimeClickHandler"; private static final String KEY_PREVIOUS_DAY_MAP = "previousDayMap"; + private int mSelectSource = AlarmClockFragment.SEL_SRC_RINGTONE; + private static final String KEY_SELECT_SOURCE = "selectedSource"; + private final Fragment mFragment; private final AlarmUpdateHandler mAlarmUpdateHandler; private final ScrollHandler mScrollHandler; @@ -75,6 +81,7 @@ public final class AlarmTimeClickHandler { public void saveInstance(Bundle outState) { outState.putBundle(KEY_PREVIOUS_DAY_MAP, mPreviousDaysOfWeekMap); + outState.putInt(KEY_SELECT_SOURCE, mSelectSource); } public void setAlarmEnabled(Alarm alarm, boolean newState) { @@ -162,14 +169,7 @@ public final class AlarmTimeClickHandler { } public void onRingtoneClicked(Alarm alarm) { - mSelectedAlarm = alarm; - final Uri oldRingtone = Alarm.NO_RINGTONE_URI.equals(alarm.alert) ? null : alarm.alert; - final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, oldRingtone); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); - LogUtils.d(TAG, "Showing ringtone picker."); - mFragment.startActivityForResult(intent, R.id.request_code_ringtone); + launchRingTonePicker(alarm); } public void onEditLabelClicked(Alarm alarm) { @@ -203,4 +203,57 @@ public final class AlarmTimeClickHandler { mSelectedAlarm = null; } } + + private void launchRingTonePicker(Alarm alarm) { + mSelectedAlarm = alarm; + RingTonePickerDialogListener listener = new RingTonePickerDialogListener((AlarmClockFragment)mFragment); + new AlertDialog.Builder(mFragment.getActivity()) + .setTitle(mFragment.getResources().getString(R.string.alarm_select)) + .setItems( + new String[] { + mFragment.getResources().getString( + R.string.alarm_select_ringtone), + mFragment.getResources().getString( + R.string.alarm_select_external) }, + listener).show(); + } + + private class RingTonePickerDialogListener implements DialogInterface.OnClickListener { + private AlarmClockFragment alarm; + + public RingTonePickerDialogListener(AlarmClockFragment clock) { + alarm = clock; + } + + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case AlarmClockFragment.SEL_SRC_RINGTONE: + case AlarmClockFragment.SEL_SRC_EXTERNAL: + mSelectSource = which; + sendPickIntent(); + break; + default: + dialog.dismiss(); + break; + } + } + } + + private void sendPickIntent() { + LogUtils.d(TAG, "sendPickIntent is called, mSelectSource = " + mSelectSource); + if (mSelectSource == AlarmClockFragment.SEL_SRC_RINGTONE) { + final Uri oldRingtone = Alarm.NO_RINGTONE_URI.equals(mSelectedAlarm.alert) ? null : mSelectedAlarm.alert; + final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, oldRingtone); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); + LogUtils.d(TAG, "Showing ringtone picker."); + mFragment.startActivityForResult(intent, AlarmClockFragment.REQUEST_CODE_RINGTONE); + } else { + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, mSelectedAlarm.alert); + intent.setType(AlarmClockFragment.SEL_AUDIO_SRC); + mFragment.startActivityForResult(intent, AlarmClockFragment.REQUEST_CODE_EXTERN_AUDIO); + } + } } diff --git a/src/com/android/deskclock/alarms/PhoneStateReceiver.java b/src/com/android/deskclock/alarms/PhoneStateReceiver.java index 2ddcfd3a4..2479e4a59 100644..100755 --- a/src/com/android/deskclock/alarms/PhoneStateReceiver.java +++ b/src/com/android/deskclock/alarms/PhoneStateReceiver.java @@ -9,6 +9,7 @@ import android.preference.PreferenceManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import com.android.deskclock.LogUtils; +import com.android.deskclock.Utils; import com.android.deskclock.provider.AlarmInstance; import java.util.HashSet; @@ -30,7 +31,7 @@ public class PhoneStateReceiver extends BroadcastReceiver { .getSystemService(Context.TELEPHONY_SERVICE); if (mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_IDLE) { // New call state is idle, update state for any pending alarms - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences sp = Utils.getDESharedPreferences(context); Set<String> alarms = sp.getStringSet(AlarmStateManager.ALARM_PENDING_ALARM_KEY, new HashSet<String>()); if (alarms.size() <= 0) { diff --git a/src/com/android/deskclock/data/CityDAO.java b/src/com/android/deskclock/data/CityDAO.java index 544bc298d..fa37a92e5 100644 --- a/src/com/android/deskclock/data/CityDAO.java +++ b/src/com/android/deskclock/data/CityDAO.java @@ -24,8 +24,11 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArrayMap; +import com.android.deskclock.LogUtils; import com.android.deskclock.R; import com.android.deskclock.Utils; +import com.android.deskclock.worldclock.db.DbCities; +import com.android.deskclock.worldclock.db.DbCity; import java.util.ArrayList; import java.util.Collection; @@ -116,7 +119,10 @@ final class CityDAO { throw new IllegalStateException(message); } - final Map<String, City> cities = new ArrayMap<>(ids.length); + //add the clock db data to all cities list + List<DbCity> dbCities = DbCities.getCities(context.getContentResolver()); + final Map<String, City> cities = new ArrayMap<>(ids.length + dbCities.size()); + for (int i = 0; i < ids.length; i++) { final String id = ids[i]; if ("C0".equals(id)) { @@ -124,6 +130,16 @@ final class CityDAO { } cities.put(id, createCity(id, names[i], timezones[i])); } + + //add the clock db data to all cities list + for (int i = 0; i < dbCities.size(); i++) { + DbCity dbCity = dbCities.get(i); + String formatName = dbCity.name.charAt(0) + "=" + dbCity.name; + LogUtils.d(LogUtils.LOGTAG, "getCities: formatName = " + formatName); + cities.put("UD" + dbCity.id, CityDAO.createCity("UD" + dbCity.id, + formatName, dbCity.tz)); + } + return Collections.unmodifiableMap(cities); } @@ -135,7 +151,7 @@ final class CityDAO { * @param timeZoneId identifies the timezone in which the city is located */ @VisibleForTesting - static City createCity(String id, String formattedName, String timeZoneId) { + public static City createCity(String id, String formattedName, String timeZoneId) { final String[] parts = formattedName.split("[=:]"); final String name = parts[1]; // Extract index string from input, use the first character of city name as index string diff --git a/src/com/android/deskclock/data/CityModel.java b/src/com/android/deskclock/data/CityModel.java index 535867299..472f7dc0c 100644 --- a/src/com/android/deskclock/data/CityModel.java +++ b/src/com/android/deskclock/data/CityModel.java @@ -24,10 +24,13 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.preference.PreferenceManager; +import com.android.deskclock.LogUtils; import com.android.deskclock.R; import com.android.deskclock.Utils; import com.android.deskclock.data.DataModel.CitySort; import com.android.deskclock.settings.SettingsActivity; +import com.android.deskclock.worldclock.db.DbCities; +import com.android.deskclock.worldclock.db.DbCity; import java.util.ArrayList; import java.util.Collection; @@ -100,6 +103,17 @@ final class CityModel { final List<City> allCities = new ArrayList<>(getCityMap().size()); allCities.addAll(selected); allCities.addAll(getUnselectedCities()); + + //add the clock db data to all cities list + List<DbCity> dbCities = DbCities.getCities(mContext.getContentResolver()); + for (int i = 0; i < dbCities.size(); i++) { + DbCity dbCity = dbCities.get(i); + String formatName = dbCity.name.charAt(0) + "=" + dbCity.name; + LogUtils.d(LogUtils.LOGTAG, "getCities: formatName = " + formatName); + allCities.add(CityDAO.createCity("UD" + dbCity.id, + formatName, dbCity.tz)); + } + mAllCities = Collections.unmodifiableList(allCities); } @@ -219,10 +233,7 @@ final class CityModel { } private Map<String, City> getCityMap() { - if (mCityMap == null) { - mCityMap = CityDAO.getCities(mContext); - } - + mCityMap = CityDAO.getCities(mContext); return mCityMap; } diff --git a/src/com/android/deskclock/provider/Alarm.java b/src/com/android/deskclock/provider/Alarm.java index 7fac18b7d..c1e64dd4d 100644 --- a/src/com/android/deskclock/provider/Alarm.java +++ b/src/com/android/deskclock/provider/Alarm.java @@ -307,6 +307,18 @@ public final class Alarm implements Parcelable, ClockContract.AlarmsColumns { deleteAfterUse = p.readInt() == 1; } + public Alarm(Cursor c, Uri defaultRingtoneUri) { + id = c.getLong(ID_INDEX); + enabled = c.getInt(ENABLED_INDEX) == 1; + hour = c.getInt(HOUR_INDEX); + minutes = c.getInt(MINUTES_INDEX); + daysOfWeek = new DaysOfWeek(c.getInt(DAYS_OF_WEEK_INDEX)); + vibrate = c.getInt(VIBRATE_INDEX) == 1; + label = c.getString(LABEL_INDEX); + deleteAfterUse = c.getInt(DELETE_AFTER_USE_INDEX) == 1; + alert = defaultRingtoneUri; + } + public String getLabelOrDefault(Context context) { return label.isEmpty() ? context.getString(R.string.default_label) : label; } diff --git a/src/com/android/deskclock/provider/ClockDatabaseHelper.java b/src/com/android/deskclock/provider/ClockDatabaseHelper.java index b75670d01..64f1df0fd 100644 --- a/src/com/android/deskclock/provider/ClockDatabaseHelper.java +++ b/src/com/android/deskclock/provider/ClockDatabaseHelper.java @@ -56,11 +56,12 @@ class ClockDatabaseHelper extends SQLiteOpenHelper { */ private static final int VERSION_8 = 8; + //Setting default alarm to system setting alarm_alert // This creates a default alarm at 8:30 for every Mon,Tue,Wed,Thu,Fri - private static final String DEFAULT_ALARM_1 = "(8, 30, 31, 0, 1, '', NULL, 0);"; + private static final String DEFAULT_ALARM_1 = "(8, 30, 31, 0, 1, '', 'content://settings/system/alarm_alert', 0);"; // This creates a default alarm at 9:30 for every Sat,Sun - private static final String DEFAULT_ALARM_2 = "(9, 00, 96, 0, 1, '', NULL, 0);"; + private static final String DEFAULT_ALARM_2 = "(9, 00, 96, 0, 1, '', 'content://settings/system/alarm_alert', 0);"; // Database and table names static final String DATABASE_NAME = "alarms.db"; diff --git a/src/com/android/deskclock/settings/DefaultAlarmToneDialog.java b/src/com/android/deskclock/settings/DefaultAlarmToneDialog.java new file mode 100755 index 000000000..d2479d21d --- /dev/null +++ b/src/com/android/deskclock/settings/DefaultAlarmToneDialog.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.deskclock.settings; + +import com.android.deskclock.AlarmClockFragment; +import com.android.deskclock.LogUtils; +import com.android.deskclock.R; +import com.android.deskclock.Utils; +import com.android.deskclock.data.DataModel; +import com.android.deskclock.provider.Alarm; +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.DialogInterface.OnClickListener; +import android.media.RingtoneManager; +import android.net.Uri; +import android.preference.DialogPreference; +import android.util.AttributeSet; + +public class DefaultAlarmToneDialog extends DialogPreference { + private int mSelectSource; + private String mRingtone; + private Boolean showDefaultRingtone; + private static Uri oldRingTone; + // set default of default_ring_tone + public static final String DEFAULT_RING_TONE_DEFAULT = "content://media/internal/audio/media/9"; + // the key of default_ring_tone‘default from sharedpreference + public static final String DEFAULT_RING_TONE_URI_KEY = "defaultRingTone_uri"; + // the key of defaultRingTone to show (set summary) from sharepredference + public static final String DEFAULT_RING_TONE_NAME_KEY = "defaultRingTone_name"; + public static final String DEFAULT_RING_TONE_NAME = "Cesium"; + public static final String OLD_RING_TONE_URI_STRING = "old_uri_toString"; + public static final String NEW_RING_TONE_URI_STRING = "new_uri_toString"; + public static final String REFRESH_DEFAULT_RINGTONE_ACTION = + "com.android.deskclock.action.REFRSH_DEFAULT_RING_TONE"; + public static final String SETTING_SYSTEM_RINGTONE = "content://settings/system/ringtone"; + public SharedPreferences sp; + public Context mcontext; + + public DefaultAlarmToneDialog(Context context, AttributeSet attrs) { + super(context, attrs); + this.mcontext = context; + sp = Utils.getCESharedPreferences(context); + + if (sp.getString(DEFAULT_RING_TONE_NAME_KEY, null) == null) { + LogUtils.d(LogUtils.LOGTAG, "DefaultAlarmToneDialog: summary = null"); + setSummary(DEFAULT_RING_TONE_NAME); + } + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + super.onPrepareDialogBuilder(builder); + builder.setTitle( + mcontext.getResources().getString(R.string.alarm_select)) + .setItems( + new String[] { + mcontext.getResources().getString( + R.string.alarm_select_ringtone), + mcontext.getResources().getString( + R.string.alarm_select_external) }, + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + switch (which) { + case AlarmClockFragment.SEL_SRC_RINGTONE: + case AlarmClockFragment.SEL_SRC_EXTERNAL: + mSelectSource = which; + sendPickIntent(which); + break; + default: + dialog.dismiss(); + break; + } + } + }).setPositiveButton(null, null) + .setNegativeButton(null, null).setCancelable(true); + } + + private void sendPickIntent(int selectSource) { + SettingsActivity activity = (SettingsActivity) mcontext; + String defaultRingTone = sp.getString(DEFAULT_RING_TONE_URI_KEY, DEFAULT_RING_TONE_DEFAULT); + if ("".equalsIgnoreCase(defaultRingTone)) { + oldRingTone = null; + } else { + oldRingTone = Uri.parse(defaultRingTone); + } + + LogUtils.d(LogUtils.LOGTAG, "defaultAlarmToneDialog sendPickIntent oldRingTone = " + oldRingTone); + if (selectSource == AlarmClockFragment.SEL_SRC_RINGTONE) { + final Intent intent = new Intent( + RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, + oldRingTone); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, + RingtoneManager.TYPE_ALARM); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); + activity.startActivityForResult(intent, + AlarmClockFragment.REQUEST_CODE_RINGTONE); + } else { + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, + oldRingTone); + intent.setType(AlarmClockFragment.SEL_AUDIO_SRC); + activity.startActivityForResult(intent, + AlarmClockFragment.REQUEST_CODE_EXTERN_AUDIO); + } + } + + public void saveRingtoneUri(Intent intent) { + Uri uri = getRingtoneUri(intent); + sp.edit().putString(DEFAULT_RING_TONE_URI_KEY, uri.toString()).apply(); + String displayNameString = getRingtoneString(uri); + sp.edit().putString(DEFAULT_RING_TONE_NAME_KEY, displayNameString) + .apply(); + + LogUtils.d(LogUtils.LOGTAG, "saveRingtoneUri: uri = " + uri + + " displayNameString = " + displayNameString); + + if(oldRingTone == null){ + oldRingTone = Uri.parse(""); + } + + if (uri != oldRingTone) { + // sendBroadcast to refresh Alarm's ringtone which is default + sendRefreshBroadcast(uri); + } + + if (uri.toString().equals(SETTING_SYSTEM_RINGTONE)) { + LogUtils.d(LogUtils.LOGTAG, "saveRingtoneUri: remove SP DEFAULT_RING_TONE_URI_KEY"); + sp.edit().remove(DEFAULT_RING_TONE_URI_KEY).apply(); + } + } + + private void sendRefreshBroadcast(Uri uri) { + Intent uriIntent = new Intent(); + uriIntent.putExtra(OLD_RING_TONE_URI_STRING, oldRingTone.toString()); + uriIntent.putExtra(NEW_RING_TONE_URI_STRING, uri.toString()); + uriIntent.setAction(REFRESH_DEFAULT_RINGTONE_ACTION); + mcontext.sendBroadcast(uriIntent); + } + + private String getRingtoneString(Uri uri) { + if (Alarm.NO_RINGTONE_URI.equals(uri)) { + mRingtone = mcontext.getResources().getString(R.string.silent_ringtone_title); + } else { + mRingtone = DataModel.getDataModel().getAlarmRingtoneTitle(uri); + } + return mRingtone; + } + + private Uri getRingtoneUri(Intent intent) { + Uri uri; + if (mSelectSource == AlarmClockFragment.SEL_SRC_RINGTONE) { + uri = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + } else { + uri = intent.getData(); + } + + if (uri == null) { + uri = Alarm.NO_RINGTONE_URI; + } + return uri; + } + +} diff --git a/src/com/android/deskclock/settings/SettingsActivity.java b/src/com/android/deskclock/settings/SettingsActivity.java index 82f4fa5dd..de306b2b2 100644..100755 --- a/src/com/android/deskclock/settings/SettingsActivity.java +++ b/src/com/android/deskclock/settings/SettingsActivity.java @@ -16,9 +16,11 @@ package com.android.deskclock.settings; +import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorManager; @@ -26,7 +28,10 @@ import android.media.AudioManager; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceCategory; import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; import android.preference.RingtonePreference; import android.preference.SwitchPreference; import android.provider.Settings; @@ -34,6 +39,8 @@ import android.text.format.DateUtils; import android.view.Menu; import android.view.MenuItem; +import com.android.deskclock.AlarmClockFragment; +import com.android.deskclock.AlarmUtils; import com.android.deskclock.BaseActivity; import com.android.deskclock.LogUtils; import com.android.deskclock.R; @@ -67,11 +74,14 @@ public final class SettingsActivity extends BaseActivity { public static final String KEY_VOLUME_BUTTONS = "volume_button_setting"; public static final String KEY_WEEK_START = "week_start"; - public static final String KEY_FLIP_ACTION = "flip_action"; - public static final String KEY_SHAKE_ACTION = "shake_action"; - public static final String TIMEZONE_LOCALE = "tz_locale"; + public static final String KEY_DEFAULT_ALARM_TONE = "default_alarm_tone"; + private static DefaultAlarmToneDialog mdefaultAlarmTone; + + public static final String KEY_ALARM_SETTINGS = "key_alarm_settings"; + public static final String KEY_FLIP_ACTION = "flip_action"; + public static final String KEY_SHAKE_ACTION = "shake_action"; public static final String DEFAULT_VOLUME_BEHAVIOR = "0"; public static final String VOLUME_BEHAVIOR_SNOOZE = "1"; @@ -115,6 +125,11 @@ public final class SettingsActivity extends BaseActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + //fix google native issue, cannot save and effect settingActivity's setting item + if(Utils.isNOrLater()) { + getPreferenceManager().setStorageDeviceProtected(); + } + addPreferencesFromResource(R.xml.settings); loadTimeZoneList(); } @@ -176,12 +191,22 @@ public final class SettingsActivity extends BaseActivity { timerRingtonePref.setSummary(DataModel.getDataModel().getTimerRingtoneTitle()); break; case KEY_FLIP_ACTION: - final ListPreference listPref = (ListPreference) pref; - updateActionSummary(listPref, (String) newValue, R.string.flip_action_summary); + LogUtils.d(LogUtils.LOGTAG, "onPreferenceChange: flip"); + final ListPreference flipPref = (ListPreference) pref; + int i = flipPref.findIndexOfValue((String) newValue); + flipPref.setSummary(getString( + R.string.flip_action_summary, + getResources().getStringArray( + R.array.action_summary_entries)[i])); break; case KEY_SHAKE_ACTION: - final ListPreference listPref = (ListPreference) pref; - updateActionSummary(listPref, (String) newValue, R.string.shake_action_summary); + LogUtils.d(LogUtils.LOGTAG, "onPreferenceChange: shake"); + final ListPreference shakePref = (ListPreference) pref; + int ii = shakePref.findIndexOfValue((String) newValue); + shakePref.setSummary(getString( + R.string.shake_action_summary, + getResources().getStringArray( + R.array.action_summary_entries)[ii])); break; } // Set result so DeskClock knows to refresh itself @@ -189,12 +214,6 @@ public final class SettingsActivity extends BaseActivity { return true; } - private void updateActionSummary(ListPreference listPref, String action, int summaryResId) { - int i = Integer.parseInt(action); - listPref.setSummary(getString(summaryResId, - getResources().getStringArray(R.array.action_summary_entries)[i])); - } - @Override public boolean onPreferenceClick(Preference pref) { final Activity activity = getActivity(); @@ -294,6 +313,15 @@ public final class SettingsActivity extends BaseActivity { final Preference volumePref = findPreference(KEY_ALARM_VOLUME); volumePref.setOnPreferenceClickListener(this); + mdefaultAlarmTone = + (DefaultAlarmToneDialog) findPreference(KEY_DEFAULT_ALARM_TONE); + SharedPreferences sharedPreferences = Utils.getCESharedPreferences(getContext()); + LogUtils.d(LogUtils.LOGTAG, "refresh: summary = " + sharedPreferences.getString( + DefaultAlarmToneDialog.DEFAULT_RING_TONE_NAME_KEY,null)); + mdefaultAlarmTone.setSummary(sharedPreferences.getString( + DefaultAlarmToneDialog.DEFAULT_RING_TONE_NAME_KEY, + DefaultAlarmToneDialog.DEFAULT_RING_TONE_NAME)); + final SnoozeLengthDialog snoozePref = (SnoozeLengthDialog) findPreference(KEY_ALARM_SNOOZE); snoozePref.setSummary(); @@ -323,13 +351,46 @@ public final class SettingsActivity extends BaseActivity { timerRingtonePref.setSummary(DataModel.getDataModel().getTimerRingtoneTitle()); timerRingtonePref.setOnPreferenceChangeListener(this); - listPref = (ListPreference) findPreference(KEY_FLIP_ACTION); - updateActionSummary(listPref, listPref.getValue(), R.string.flip_action_summary); - listPref.setOnPreferenceChangeListener(this); + PreferenceCategory category = (PreferenceCategory) findPreference( + KEY_ALARM_SETTINGS); + SensorManager sensorManager = (SensorManager) getActivity() + .getSystemService(Context.SENSOR_SERVICE); + ListPreference flipPreference = (ListPreference) findPreference(KEY_FLIP_ACTION); + if (flipPreference != null) { + List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ORIENTATION); + if (sensorList.size() < 1) { // This will be true if no orientation sensor + flipPreference.setValue("0"); // Turn it off + if (category != null) { + LogUtils.d(LogUtils.LOGTAG, "filpPreference is removed"); + category.removePreference(flipPreference); + } + } else { + int i = flipPreference.findIndexOfValue(flipPreference.getValue()); + flipPreference.setSummary(getString( + R.string.flip_action_summary, + getResources().getStringArray(R.array.action_summary_entries)[i])); + flipPreference.setOnPreferenceChangeListener(this); + } + } - listPref = (ListPreference) findPreference(KEY_SHAKE_ACTION); - updateActionSummary(listPref, listPref.getValue(), R.string.shake_action_summary); - listPref.setOnPreferenceChangeListener(this); + ListPreference shakePreference = (ListPreference) findPreference(KEY_SHAKE_ACTION); + if (shakePreference != null) { + List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); + if (sensorList.size() < 1) { // This will be true if no accelerometer sensor + shakePreference.setValue("0"); // Turn it off + if (category != null) { + LogUtils.d(LogUtils.LOGTAG, "shakePreference is removed"); + category.removePreference(shakePreference); + } + } else { + int i = shakePreference.findIndexOfValue(shakePreference.getValue()); + shakePreference.setSummary(getString( + R.string.shake_action_summary, + getResources().getStringArray( + R.array.action_summary_entries)[i])); + shakePreference.setOnPreferenceChangeListener(this); + } + } } private void updateAutoSnoozeSummary(ListPreference listPref, String delay) { @@ -387,4 +448,25 @@ public final class SettingsActivity extends BaseActivity { } } } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK) { + switch (requestCode) { + case AlarmClockFragment.REQUEST_CODE_RINGTONE: + mdefaultAlarmTone.saveRingtoneUri(data); + break; + case AlarmClockFragment.REQUEST_CODE_EXTERN_AUDIO: + if (!AlarmUtils.hasPermissionToDisplayRingtoneTitle(this, data.getData())) { + final String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE}; + requestPermissions(perms, AlarmClockFragment.REQUEST_CODE_PERMISSIONS); + } + mdefaultAlarmTone.saveRingtoneUri(data); + break; + default: + LogUtils.w("Unhandled request code in onActivityResult: " + + requestCode); + } + } + } } diff --git a/src/com/android/deskclock/worldclock/AddCityDialog.java b/src/com/android/deskclock/worldclock/AddCityDialog.java index c7174aa89..9612eb4a5 100644..100755 --- a/src/com/android/deskclock/worldclock/AddCityDialog.java +++ b/src/com/android/deskclock/worldclock/AddCityDialog.java @@ -1,4 +1,6 @@ -/* +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a Contribution. + * * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +18,7 @@ package com.android.deskclock.worldclock; +import android.Manifest; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; @@ -25,6 +28,7 @@ import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.graphics.drawable.AnimationDrawable; @@ -51,10 +55,11 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; -import android.widget.Spinner; import android.widget.Toast; +import com.android.deskclock.LogUtils; import com.android.deskclock.R; +import com.android.deskclock.Utils; import com.android.deskclock.worldclock.CityAndTimeZoneLocator.OnCityAndTimeZoneLocatedCallback; import com.android.deskclock.worldclock.CityAndTimeZoneLocator.TZ; @@ -64,6 +69,7 @@ import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.TimeZone; public class AddCityDialog implements OnClickListener, @@ -71,9 +77,14 @@ public class AddCityDialog implements OnClickListener, private static final int HOURS_1 = 60 * 60000; private static final long GPS_TIMEOUT = 30000L; - private static final String STATE_CITY_NAME = "city_name"; private static final String STATE_CITY_TIMEZONE = "city_tz"; + private static final String ISGPSREQUESTING = "city_dialog"; + private int mCityDialogWidth = -1; + private int mCityDialogHeight = -1; + public static CityTimeZone[] mZones; + + static final int REQUEST_FINE_LOCATION_PERMISSIONS = 20; public interface OnCitySelected { public void onCitySelected(String city, String tz); @@ -81,27 +92,22 @@ public class AddCityDialog implements OnClickListener, } private final AsyncTask<Void, Void, Void> mTzLoadTask = new AsyncTask<Void, Void, Void>() { - private String[] mZones; + @Override protected Void doInBackground(Void... params) { List<CityTimeZone> zones = loadTimeZones(); Collections.sort(zones); - List<String> tzLabels = new ArrayList<String>(); - for (CityTimeZone zone : zones) { - tzLabels.add(zone.toString()); - } - - mZones = tzLabels.toArray(new String[tzLabels.size()]); - mDefaultTimeZoneId = zones.indexOf(mDefaultTimeZoneLabel); + mZones = zones.toArray(new CityTimeZone[zones.size()]); + mDefaultTimeZonePos = zones.indexOf(mDefaultTimeZone); return null; } @Override protected void onPostExecute(Void result) { if (!isCancelled()) { - int id = mSavedTimeZonePos != -1 ? mSavedTimeZonePos : mDefaultTimeZoneId; + int id = mSavedTimeZonePos != -1 ? mSavedTimeZonePos : mDefaultTimeZonePos; setTimeZoneData(mZones, id, true); mLoadingTz = false; } @@ -128,11 +134,17 @@ public class AddCityDialog implements OnClickListener, stopGpsAnimation(); mGps.setImageResource(R.drawable.ic_gps); checkSelectionStatus(); - mLocationMgr.removeUpdates(AddCityDialog.this); + try { + mLocationMgr.removeUpdates(AddCityDialog.this); + } catch (SecurityException e){ + LogUtils.d(LogUtils.LOGTAG, "Runnable mGpsTimeout:occur security exception"); + } + } }; - private static class CityTimeZone implements Comparable<CityTimeZone> { + public static class CityTimeZone implements Comparable<CityTimeZone> { + String mId; int mSign; int mHours; int mMinutes; @@ -141,6 +153,10 @@ public class AddCityDialog implements OnClickListener, @Override public String toString() { + if (mId == null) { + // Loading + return mLabel; + } return String.format("GMT%s%02d:%02d - %s", (mSign == -1 ? "-" : "+"), mHours, mMinutes, mLabel); } @@ -155,8 +171,8 @@ public class AddCityDialog implements OnClickListener, @Override public int compareTo(CityTimeZone other) { - int offset = getOffset(); - int otherOffset = other.getOffset(); + long offset = getOffset(); + long otherOffset = other.getOffset(); if (offset != otherOffset) { return offset < otherOffset ? -1 : 1; } @@ -166,8 +182,8 @@ public class AddCityDialog implements OnClickListener, return mLabel.compareTo(other.mLabel); } - private int getOffset() { - return mSign * (mHours + mMinutes * 60); + private long getOffset() { + return mSign * (mHours * HOURS_1 + mMinutes * 60000); } } @@ -175,50 +191,47 @@ public class AddCityDialog implements OnClickListener, private final Handler mHandler; private final OnCitySelected mListener; private final AlertDialog mDialog; - private final EditText mCityName; + private final View dlgView; + private EditText mCityName; private final ImageButton mGps; - private final Spinner mTimeZones; private Button mButton; - - private List<String> mCurrentTimeZones; + public static TimeZoneSpinner mTimeZones; private LocationManager mLocationMgr; private ConnectivityManager mConnectivityMgr; private CityAndTimeZoneLocator mLocator; - private boolean mGpsRequesting; private boolean mLoadingTz; + public boolean mGpsRequesting; - private int mDefaultTimeZoneId; + private int mDefaultTimeZonePos; private int mSavedTimeZonePos; - private CityTimeZone mDefaultTimeZoneLabel; + private CityTimeZone mDefaultTimeZone; public AddCityDialog(Context context, LayoutInflater inflater, OnCitySelected listener) { mContext = context; mHandler = new Handler(); mListener = listener; - mDefaultTimeZoneId = 0; - mDefaultTimeZoneLabel = null; + mDefaultTimeZonePos = 0; + mDefaultTimeZone = null; mSavedTimeZonePos = -1; + mLocationMgr = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - mLocationMgr.addGpsStatusListener(new GpsStatus.Listener() { - @Override - public void onGpsStatusChanged(int event) { - } - }); - mConnectivityMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + mConnectivityMgr = (ConnectivityManager)context + .getSystemService(Context.CONNECTIVITY_SERVICE); mGpsRequesting = false; mLoadingTz = true; // Initialize dialog - View dlgView = inflater.inflate(R.layout.city_add, null); + dlgView = inflater.inflate(R.layout.city_add, null); mCityName = (EditText) dlgView.findViewById(R.id.add_city_name); mCityName.addTextChangedListener(this); - - mTimeZones = (Spinner) dlgView.findViewById(R.id.add_city_tz); - setTimeZoneData(new String[]{ context.getString(R.string.cities_add_loading) }, 0, false); + mTimeZones = (TimeZoneSpinner) dlgView.findViewById(R.id.add_city_tz); + CityTimeZone loading = new CityTimeZone(); + loading.mId = null; + loading.mLabel = context.getString(R.string.cities_add_loading); + setTimeZoneData(new CityTimeZone[]{ loading }, 0, false); mTimeZones.setEnabled(false); mTimeZones.setOnItemSelectedListener(this); - mGps = (ImageButton)dlgView.findViewById(R.id.add_city_gps); mGps.setOnClickListener(new View.OnClickListener() { @Override @@ -242,7 +255,17 @@ public class AddCityDialog implements OnClickListener, return true; } }); + checkGpsAvailability(); + try { + mLocationMgr.addGpsStatusListener(new GpsStatus.Listener() { + @Override + public void onGpsStatusChanged(int event) { + } + }); + } catch (SecurityException e) { + LogUtils.d(LogUtils.LOGTAG, "AddCityDialog:occur security exception"); + } // Create the dialog AlertDialog.Builder builder = new AlertDialog.Builder(context); @@ -284,11 +307,14 @@ public class AddCityDialog implements OnClickListener, filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); context.registerReceiver(mReceiver, filter); mReceiverRegistered = true; + mCityDialogWidth = dlgView.getWidth(); + mCityDialogHeight = dlgView.getHeight(); } - private void setTimeZoneData(String[] data, int selected, boolean enabled) { - ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext, - android.R.layout.simple_spinner_item, data); + public void setTimeZoneData(CityTimeZone[] data, int selected, + boolean enabled) { + ArrayAdapter<CityTimeZone> adapter = new ArrayAdapter<CityTimeZone>( + mContext, android.R.layout.simple_spinner_item, data); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mTimeZones.setAdapter(adapter); mTimeZones.setSelection(selected); @@ -296,15 +322,13 @@ public class AddCityDialog implements OnClickListener, if (mButton != null) { checkSelectionStatus(); } - - mCurrentTimeZones = Arrays.asList(data); } private List<CityTimeZone> loadTimeZones() { ArrayList<CityTimeZone> timeZones = new ArrayList<CityTimeZone>(); Resources res = mContext.getResources(); final long date = Calendar.getInstance().getTimeInMillis(); - mDefaultTimeZoneLabel = buildCityTimeZone(TimeZone.getDefault().getID(), date); + mDefaultTimeZone = buildCityTimeZone(TimeZone.getDefault().getID(), date); String[] ids = res.getStringArray(R.array.cities_tz); for (String id : ids) { CityTimeZone zone = buildCityTimeZone(id, date); @@ -325,6 +349,7 @@ public class AddCityDialog implements OnClickListener, final boolean inDst = tz.inDaylightTime(new Date(date)); CityTimeZone timeZone = new CityTimeZone(); + timeZone.mId = tz.getID(); timeZone.mLabel = tz.getDisplayName(inDst, TimeZone.LONG); timeZone.mSign = offset < 0 ? -1 : 1; timeZone.mHours = p / (HOURS_1); @@ -346,10 +371,13 @@ public class AddCityDialog implements OnClickListener, } else { id = info.name; } - return buildCityTimeZone(id, date); } + public static void setSelectItem(int itemId) { + mTimeZones.setSelection(itemId); + } + private void checkSelectionStatus() { String name = mCityName.getText().toString().toLowerCase(); String tz = null; @@ -361,12 +389,32 @@ public class AddCityDialog implements OnClickListener, mCityName.isEnabled() && !TextUtils.isEmpty(name) && mTimeZones.isEnabled() && !TextUtils.isEmpty(tz); mButton.setEnabled(enabled); + if (enabled) { + mButton.setTextColor(mContext.getResources().getColor( + R.color.color_accent, null)); + } else { + mButton.setTextColor(mContext.getResources().getColor( + R.color.dialog_positive_button_disable, null)); + } } private void checkGpsAvailability() { boolean gpsEnabled = mLocationMgr.isProviderEnabled(LocationManager.GPS_PROVIDER); boolean networkEnabled = mLocationMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER); mGps.setEnabled(gpsEnabled || (networkEnabled && isNetworkStatusAvailable())); + LogUtils.d(LogUtils.LOGTAG, "checkGpsAvailability: gpsEnabled = " + + gpsEnabled + " networkEnabled = " + networkEnabled); + + //check & request gps permission when GPS enable, if gps disable, don't + if(gpsEnabled || (networkEnabled && isNetworkStatusAvailable())) { + if(!hasPermissionOfFineLocation(mContext) + && Utils.isMOrLater()) { + LogUtils.d(LogUtils.LOGTAG, "checkGpsAvailability:request fine location permission"); + final String[] perms = {Manifest.permission.ACCESS_FINE_LOCATION}; + ((CitySelectionActivity) mContext).requestPermissions(perms, + REQUEST_FINE_LOCATION_PERMISSIONS); + } + } } private boolean isNetworkStatusAvailable() { @@ -381,7 +429,6 @@ public class AddCityDialog implements OnClickListener, Criteria criteria = new Criteria(); criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW); Looper looper = mContext.getMainLooper(); - mHandler.postDelayed(mGpsTimeout, GPS_TIMEOUT); mGpsRequesting = true; mCityName.setEnabled(false); @@ -397,18 +444,26 @@ public class AddCityDialog implements OnClickListener, // We have to use the network to locate the city and tz, so user also the network to detect // location (we not need to much accuracy and this method is faster). Otherwise, use // the GPS to locate the coordinates - if (mLocationMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { - mLocationMgr.requestLocationUpdates( - LocationManager.NETWORK_PROVIDER, 5000, 0, this, looper); - } else { - mLocationMgr.requestLocationUpdates( - LocationManager.GPS_PROVIDER, 5000, 0, this, looper); + try { + if (mLocationMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + mLocationMgr.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, 5000, 0, this, looper); + } else { + mLocationMgr.requestLocationUpdates( + LocationManager.GPS_PROVIDER, 5000, 0, this, looper); + } + } catch (SecurityException e) { + LogUtils.d(LogUtils.LOGTAG, "requestGpsLocation:occur security exception"); } } private void cancelRequestGpsLocation() { mHandler.removeCallbacks(mGpsTimeout); - mLocationMgr.removeUpdates(this); + try { + mLocationMgr.removeUpdates(this); + } catch (SecurityException e){ + LogUtils.d(LogUtils.LOGTAG, "cancelRequestGpsLocation:occur security exception"); + } mGpsRequesting = false; mCityName.setText(""); mCityName.setEnabled(true); @@ -425,13 +480,13 @@ public class AddCityDialog implements OnClickListener, } public void onClick(DialogInterface dialog, int which) { - String name = mCityName.getText().toString(); - String tz = null; + String name = mCityName.getText().toString().toUpperCase(Locale.getDefault()); + CityTimeZone ctz = null; if (mTimeZones.getSelectedItem() != null) { - tz = mTimeZones.getSelectedItem().toString(); + ctz = (CityTimeZone)mTimeZones.getSelectedItem(); } - if (tz != null && mListener != null) { - mListener.onCitySelected(name, tz.substring(tz.indexOf(" - ") + 3)); + if (ctz != null && mListener != null) { + mListener.onCitySelected(name, ctz.mId); } } @@ -458,19 +513,24 @@ public class AddCityDialog implements OnClickListener, if (mTimeZones.getSelectedItem() != null) { tz = mTimeZones.getSelectedItemPosition(); } - + outState.putBoolean(ISGPSREQUESTING, mGpsRequesting); outState.putString(STATE_CITY_NAME, name); outState.putInt(STATE_CITY_TIMEZONE, tz); } protected void onRestoreInstanceState(Bundle savedInstanceState) { String name = savedInstanceState.getString(STATE_CITY_NAME); + Boolean isGpsRequesting = savedInstanceState.getBoolean(ISGPSREQUESTING); if (name != null) { mCityName.setText(name); } + if(isGpsRequesting){ + mCityName.setText(""); + } mSavedTimeZonePos = savedInstanceState.getInt(STATE_CITY_TIMEZONE, -1); } + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { checkSelectionStatus(); } @@ -496,31 +556,26 @@ public class AddCityDialog implements OnClickListener, if (!mGpsRequesting) return; mGpsRequesting = false; mHandler.removeCallbacks(mGpsTimeout); - mLocationMgr.removeUpdates(this); - + try { + mLocationMgr.removeUpdates(this); + } catch (SecurityException e) { + LogUtils.d(LogUtils.LOGTAG, "onLocationChanged:occur security exception"); + } CityAndTimeZoneLocator mLocator = new CityAndTimeZoneLocator( mContext, location, mConnectivityMgr, new OnCityAndTimeZoneLocatedCallback() { @Override + @SuppressWarnings("unchecked") public void onCityAndTimeZoneLocated(String city, TZ timezone) { - // Now we need to resolve the timezone info into an existing timezone - // First try to resolve the id, otherwise select the first occurrence - // with the same offset - CityTimeZone ctzInfo = toCityTimeZone(timezone); - int tz = mCurrentTimeZones.indexOf(ctzInfo.toString()); - if (tz == -1) { - String offset = ctzInfo.toString().substring(0, 9); //xe: GMT+12:00 - int cc = mCurrentTimeZones.size(); - for (int i = 0; i < cc; i++) { - String ctz = mCurrentTimeZones.get(i); - if (ctz.startsWith(offset)) { - tz = i; - break; - } - } + CityTimeZone ctz = toCityTimeZone(timezone); + int pos = ((ArrayAdapter<CityTimeZone>)mTimeZones.getAdapter()).getPosition(ctz); + if (pos == -1) { + // This mean you are in the middle of the ocean and Android doesn't have + // a timezone definition for you. + pos = mDefaultTimeZonePos; } // Update the views with the new information - updateViews(city, tz); + updateViews(city, pos); } @Override @@ -567,4 +622,18 @@ public class AddCityDialog implements OnClickListener, public void onProviderDisabled(String provider) { checkGpsAvailability(); } + + + public static boolean hasPermissionOfFineLocation(Context context) { + final PackageManager pm = context.getPackageManager(); + final String packageName = context.getPackageName(); + + // If the permission is already granted, return true. + if (pm.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION, packageName) + == PackageManager.PERMISSION_GRANTED) { + return true; + } else { + return false; + } + } } diff --git a/src/com/android/deskclock/worldclock/CityAndTimeZoneLocator.java b/src/com/android/deskclock/worldclock/CityAndTimeZoneLocator.java index afb1cb7ee..e5c46ab16 100644..100755 --- a/src/com/android/deskclock/worldclock/CityAndTimeZoneLocator.java +++ b/src/com/android/deskclock/worldclock/CityAndTimeZoneLocator.java @@ -1,4 +1,6 @@ -/* +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a Contribution. + * * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,19 +29,16 @@ import android.os.AsyncTask; import android.util.Log; import android.util.Xml; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.util.List; import java.util.Locale; @@ -172,20 +171,20 @@ public class CityAndTimeZoneLocator { private TZ resolveTimeZone() { BufferedReader br = null; try { - HttpClient client = new DefaultHttpClient(); - boolean gps = mLocation.getProvider().compareTo(LocationManager.GPS_PROVIDER) == 0; + boolean gps = mLocation.getProvider().compareTo( + LocationManager.GPS_PROVIDER) == 0; final URI uri = new URI(String.format(TIMEZONE_SERVICE_URI, String.valueOf(mLocation.getLatitude()), String.valueOf(mLocation.getLongitude()), String.valueOf(System.currentTimeMillis() / 1000L), String.valueOf(gps))); - HttpGet request = new HttpGet(); - request.setURI(uri); - HttpResponse response = client.execute(request); - int status = response.getStatusLine().getStatusCode(); - if (status == HttpStatus.SC_OK) { - // Read the response into XML - br = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); + URL url = new URL(uri.toString()); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + int status = conn.getResponseCode(); + if (status == HttpURLConnection.HTTP_OK) { + br = new BufferedReader(new InputStreamReader( + conn.getInputStream())); return parseTimeZoneResponse(br); } } catch (URISyntaxException e) { diff --git a/src/com/android/deskclock/worldclock/CitySelectionActivity.java b/src/com/android/deskclock/worldclock/CitySelectionActivity.java index 2c8e39874..d6f62fa28 100644 --- a/src/com/android/deskclock/worldclock/CitySelectionActivity.java +++ b/src/com/android/deskclock/worldclock/CitySelectionActivity.java @@ -41,6 +41,7 @@ import android.widget.TextView; import android.widget.Toast; import com.android.deskclock.BaseActivity; +import com.android.deskclock.LogUtils; import com.android.deskclock.R; import com.android.deskclock.Utils; import com.android.deskclock.actionbarmenu.AbstractMenuItemController; @@ -92,15 +93,16 @@ public final class CitySelectionActivity extends BaseActivity /** Menu item controller for search view. */ private SearchMenuItemController mSearchMenuItemController; - private static final String STATE_CITY_DIALOG = "city_dialog"; - - private AddCityDialog mAddCityDialog; + private LayoutInflater mFactory; + private AddCityDialog mAddCityDialog = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setVolumeControlStream(AudioManager.STREAM_ALARM); + mFactory = LayoutInflater.from(this); + setContentView(R.layout.cities_activity); mSearchMenuItemController = new SearchMenuItemController(new SearchView.OnQueryTextListener() { @@ -119,6 +121,7 @@ public final class CitySelectionActivity extends BaseActivity mCitiesAdapter = new CityAdapter(this, mSearchMenuItemController); mActionBarMenuManager.addMenuItemController(new NavUpMenuItemController(this)) .addMenuItemController(mSearchMenuItemController) + .addMenuItemController(new AddCityMenuItemController()) .addMenuItemController(new SortOrderMenuItemController()) .addMenuItemController(new SettingMenuItemController(this)) .addMenuItemController(MenuItemControllerFactory.getInstance() @@ -209,7 +212,7 @@ public final class CitySelectionActivity extends BaseActivity * </pre> */ private static final class CityAdapter extends BaseAdapter implements View.OnClickListener, - CompoundButton.OnCheckedChangeListener, SectionIndexer { + CompoundButton.OnCheckedChangeListener, SectionIndexer, View.OnLongClickListener { /** The type of the single optional "Selected Cities" header entry. */ private static final int VIEW_TYPE_SELECTED_CITIES_HEADER = 0; @@ -251,9 +254,8 @@ public final class CitySelectionActivity extends BaseActivity /** Menu item controller for search. Search query is maintained here. */ private final SearchMenuItemController mSearchMenuItemController; - private final int mClockWhiteColor; - private final int mClockBlueColor; - + private final int mNormalCityFgColor; + private final int mUserDefinedCityFgColor; public CityAdapter(Context context, SearchMenuItemController searchMenuItemController) { mContext = context; @@ -276,8 +278,8 @@ public final class CitySelectionActivity extends BaseActivity mPattern12 = pattern12; Resources res = context.getResources(); - mClockWhiteColor = res.getColor(R.color.clock_white); - mClockBlueColor = res.getColor(R.color.clock_blue); + mNormalCityFgColor = res.getColor(R.color.clock_white); + mUserDefinedCityFgColor = res.getColor(R.color.red); } @Override @@ -307,30 +309,6 @@ public final class CitySelectionActivity extends BaseActivity return position; } - public int getPosition(CityObj o) { - int count = mAllTheCitiesList.length; - for (int i = 0; i < count; i++) { - CityObj c = (CityObj) mAllTheCitiesList[i]; - if (c.mCityId != null && o.mCityId.compareTo(c.mCityId) == 0) { - return i; - } - } - return -1; - } - - public int getPosition(String name, String tz) { - int count = mAllTheCitiesList.length; - for (int i = 0; i < count; i++) { - CityObj c = (CityObj) mAllTheCitiesList[i]; - if (c.mCityId != null && - name.compareToIgnoreCase(c.mCityName) == 0 && - tz.compareToIgnoreCase(c.mTimeZone) == 0) { - return i; - } - } - return -1; - } - @Override public synchronized View getView(int position, View view, ViewGroup parent) { final int itemViewType = getItemViewType(position); @@ -364,6 +342,11 @@ public final class CitySelectionActivity extends BaseActivity holder.name.setText(city.getName(), TextView.BufferType.SPANNABLE); holder.time.setText(getTimeCharSequence(timeZone)); + view.setOnLongClickListener(this); + boolean bIsUserAdded = city.getId().length()> 2 + && "UD".equalsIgnoreCase(city.getId().substring(0,2)); + holder.name.setTextColor(bIsUserAdded ? mUserDefinedCityFgColor : mNormalCityFgColor); + final boolean showIndex = getShowIndex(position); holder.index.setVisibility(showIndex ? View.VISIBLE : View.INVISIBLE); if (showIndex) { @@ -419,6 +402,46 @@ public final class CitySelectionActivity extends BaseActivity b.setChecked(!b.isChecked()); } + public boolean onLongClick(View v) { + final CityItemHolder holder = (CityItemHolder) v.getTag(); + City c = (City)holder.selected.getTag(); + if (c != null && "UD".equalsIgnoreCase(c.getId().substring(0,2))) { + deleteCity(c); + return true; + } + return false; + } + + private void deleteCity(final City c) { + AlertDialog.Builder builder = new AlertDialog.Builder(mContext); + builder.setTitle(R.string.cities_delete_city_title); + builder.setMessage(mContext.getString(R.string.cities_delete_city_msg, c.getName())); + builder.setPositiveButton(mContext.getString(android.R.string.ok), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int id = Integer.parseInt(c.getId().substring(2)); + if (DbCities.deleteCity(mContext, id) > 0) { + // Remove from the list and from the selection + mUserSelectedCities.remove(c.getId()); + + // set the selected cities in order to recreate list content + DataModel.getDataModel().setSelectedCities(getSelectedCities()); + + //refresh the list with the new data + refresh(); + } else { + // Something went wrong + Toast.makeText(mContext, R.string.cities_delete_city_failed, + Toast.LENGTH_SHORT).show(); + } + } + }); + builder.setNegativeButton(mContext.getString(android.R.string.cancel), null); + AlertDialog dialog = builder.create(); + dialog.show(); + } + @Override public Object[] getSections() { if (mSectionHeaders == null) { @@ -640,111 +663,120 @@ public final class CitySelectionActivity extends BaseActivity } } - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); + private class AddCityMenuItemController extends AbstractMenuItemController { + public int getId() { + return R.id.menu_item_add; + } - if (mAddCityDialog != null) { - outState.putBoolean(STATE_CITY_DIALOG, true); - mAddCityDialog.onSaveInstanceState(outState); + public void showMenuItem(Menu menu) { + final MenuItem addMenuItem = menu.findItem(R.id.menu_item_add); + addMenuItem.setVisible(true); } - } - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); + public boolean handleMenuItemClick(MenuItem item) { + LogUtils.d(LogUtils.LOGTAG, "AddCityMenuItemController: add city menu is clicked"); + mAddCityDialog = new AddCityDialog(CitySelectionActivity.this, + mFactory, CitySelectionActivity.this); + try { + mAddCityDialog.show(); + } catch (Exception e) { + e.printStackTrace(); + } - if (savedInstanceState.getBoolean(STATE_CITY_DIALOG, false)) { - showAddCityDialog(savedInstanceState); + return true; } } @Override - protected void onDestroy() { - super.onDestroy(); - - if (mAddCityDialog != null) { - mAddCityDialog.dismiss(); - } - } + public void onCitySelected(String name, String tz) { + LogUtils.d(LogUtils.LOGTAG, "onCitySelected: name = " + name + " tz = " + tz); - private void showAddCityDialog(Bundle savedInstance) { - mAddCityDialog = new AddCityDialog(this, mFactory, this); - if (savedInstance != null) { - mAddCityDialog.onRestoreInstanceState(savedInstance); + //validity check + if(name == null || name.isEmpty() + || tz == null || tz.isEmpty()) { + LogUtils.d(LogUtils.LOGTAG, "onCitySelected:value is invalid"); + return; } - mAddCityDialog.show(); - } - @Override - public boolean onLongClick(View v) { - CompoundButton b = (CompoundButton) v.findViewById(R.id.city_onoff); - final CityObj c = (CityObj) b.getTag(); - if (c != null && c.mUserDefined) { - deleteCity(c); - return true; + //dismiss the dialog + if(mAddCityDialog != null) { + mAddCityDialog.dismiss(); + mAddCityDialog = null; } - return false; - } - @Override - public void onCitySelected(String name, String tz) { - // If city name and timezone exists, then don't add it - int pos = mAdapter.getPosition(name, tz); - if (pos != -1) { - // The city already exists + // If city name and timezone already exists in DB, give toast prompt + if (cityRecordIsExistInDB(name, tz)) { + //prompt the toast Toast.makeText(this, R.string.cities_add_already_exists, Toast.LENGTH_SHORT).show(); - mCitiesList.setSelection(pos); return; } - DbCity dbCity = new DbCity(); - dbCity.name = name; - dbCity.tz = tz; - long id = DbCities.addCity(this, dbCity); - if (id < 0) { - // Something went wrong - Toast.makeText(this, R.string.cities_add_city_failed, - Toast.LENGTH_SHORT).show(); + //insert new record to DB + if (insertNewRecordToClockDB(name, tz)) { + //recreate allCities selectedCities list + DataModel.getDataModel().setSelectedCities(mCitiesAdapter.getSelectedCities()); + + //refresh the list with the new data + mCitiesAdapter.refresh(); + mCitiesList.invalidate(); } else { - mAdapter.loadCitiesDataBase(this); - mAdapter.notifyDataSetChanged(); - CityObj o = new CityObj(name, tz, "UD" + id); - mCitiesList.setSelection(mAdapter.getPosition(o)); + // Something went wrong + Toast.makeText(this, R.string.cities_add_city_failed, + Toast.LENGTH_SHORT).show(); } - - mAddCityDialog = null; } @Override public void onCancelCitySelection() { - mAddCityDialog = null; + //Dismiss Dialog + if(mAddCityDialog != null) { + mAddCityDialog.dismiss(); + mAddCityDialog = null; + } } - private void deleteCity(final CityObj c) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.cities_delete_city_title); - builder.setMessage(getString(R.string.cities_delete_city_msg, c.mCityName)); - builder.setPositiveButton(getString(android.R.string.ok), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - int id = Integer.parseInt(c.mCityId.substring(2)); - if (DbCities.deleteCity(CitiesActivity.this, id) > 0) { - // Remove from the list and from the selection - mUserSelectedCities.remove(c.mCityId); - mAdapter.loadCitiesDataBase(CitiesActivity.this); - mAdapter.notifyDataSetChanged(); - } else { - // Something went wrong - Toast.makeText(CitiesActivity.this, R.string.cities_delete_city_failed, - Toast.LENGTH_SHORT).show(); + private boolean cityRecordIsExistInDB(String name, String timezone) { + //param check + if (name == null || name.isEmpty() + || timezone == null || timezone.isEmpty()) { + LogUtils.d(LogUtils.LOGTAG, "cityRecordIsExistInDB: param is invalid" + + " name = " + name + " timezone = " + timezone); + return false; + } + + List<City> allCityList = DataModel.getDataModel().getAllCities(); + for (int i = 0; i < allCityList.size(); i++) { + City c = allCityList.get(i); + if(c != null && c.getId() != null) { + if (c.getName().equalsIgnoreCase(name) + && c.getTimeZone().getID().equalsIgnoreCase(timezone)) { + return true; } } - }); - builder.setNegativeButton(getString(android.R.string.cancel), null); - AlertDialog dialog = builder.create(); - dialog.show(); + } + + return false; } + //insert new record to DB, return whether succeed or not + private boolean insertNewRecordToClockDB(String name, String tz) { + //param check + if (name == null || name.isEmpty() + || tz == null || tz.isEmpty()) { + LogUtils.d(LogUtils.LOGTAG, "insertNewRecordToClockDB: param is invalid" + + " name = " + name + " timezone = " + tz); + return false; + } + + DbCity dbCity = new DbCity(); + dbCity.name = name; + dbCity.tz = tz; + long id = DbCities.addCity(this, dbCity); + if (id < 0) { + return false; + } else { + return true; + } + } } diff --git a/src/com/android/deskclock/worldclock/TimeZoneSpinner.java b/src/com/android/deskclock/worldclock/TimeZoneSpinner.java new file mode 100755 index 000000000..90c454725 --- /dev/null +++ b/src/com/android/deskclock/worldclock/TimeZoneSpinner.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.android.deskclock.worldclock; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; + +import com.android.deskclock.R; +import com.android.deskclock.Utils; +import com.android.deskclock.worldclock.AddCityDialog.CityTimeZone; + +public class TimeZoneSpinner extends Spinner { + private Context mContext; + private CityTimeZone[] data; + public AlertDialog mdialog; + private ViewHolder holder; + private LinearLayout ll_item; + private LayoutParams linearParams; + private SharedPreferences sp; + public ListView listView; + private int scrolledY; + private int mposition; + private static final int ITEM_COUNT_LAND = 5; + private static final int ITEM_COUNT_PORT = 7; + private static final String SELECTPOSITION = "select_position"; + ArrayAdapter<CityTimeZone> adapter; + + public TimeZoneSpinner(Context context) { + super(context); + mContext = context; + sp = Utils.getCESharedPreferences(mContext); + } + + public TimeZoneSpinner(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + if (sp == null) { + sp = Utils.getCESharedPreferences(mContext); + } + } + + @Override + public boolean performClick() { + data = AddCityDialog.mZones; + final LayoutInflater inflater = LayoutInflater.from(mContext); + final View view = inflater.inflate(R.layout.time_zone_list, null); + // init listview + initListView(view); + creatDialog(view); + return true; + } + + private void initListView(View view) { + listView = (ListView) view.findViewById(R.id.tz_list); + // set ListView'height According to spinner'location + int[] location = new int[2]; + this.getLocationOnScreen(location); + final int spinnerLocationY = location[1]; + ViewGroup.LayoutParams params = listView.getLayoutParams(); + params.height = spinnerLocationY; + listView.setLayoutParams(params); + adapter = new ArrayAdapter<CityTimeZone>(getContext(), + R.layout.time_zone_list_item, data) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(mContext).inflate( + R.layout.time_zone_list_item, null); + holder = new ViewHolder(); + + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + ll_item = (LinearLayout) convertView.findViewById(R.id.tz_ll); + holder.tView = (TextView) convertView + .findViewById(android.R.id.text1); + holder.tView.setText(data[position].toString()); + + linearParams = (LayoutParams) ll_item.getLayoutParams(); + + if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + linearParams.height = spinnerLocationY / ITEM_COUNT_LAND; + } else { + linearParams.height = spinnerLocationY / ITEM_COUNT_PORT; + } + + ll_item.setLayoutParams(linearParams); + return convertView; + } + }; + listView.setAdapter(adapter); + listView.setDivider(null); + listView.setSelection(sp.getInt(SELECTPOSITION, 0)); + listView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView<?> parent, View view, + int position, long id) { + AddCityDialog.setSelectItem(position); + mdialog.dismiss(); + } + }); + listView.setOnScrollListener(new OnScrollListener() { + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) { + mposition = listView.getFirstVisiblePosition(); + sp.edit().putInt(SELECTPOSITION, mposition).commit(); + + } + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + + } + }); + } + + private class ViewHolder { + TextView tView; + } + + private void creatDialog(View view) { + mdialog = new AlertDialog.Builder(mContext).create(); + Window mWindow = mdialog.getWindow(); + mWindow.setGravity(Gravity.TOP); + WindowManager wm = (WindowManager) mContext + .getSystemService(Context.WINDOW_SERVICE); + WindowManager.LayoutParams lp = mWindow.getAttributes(); + mdialog.setCanceledOnTouchOutside(true); + mdialog.show(); + mdialog.addContentView(view, lp); + } +} diff --git a/src/com/android/deskclock/worldclock/db/DbCities.java b/src/com/android/deskclock/worldclock/db/DbCities.java index fb496f8cf..87b92955f 100644..100755 --- a/src/com/android/deskclock/worldclock/db/DbCities.java +++ b/src/com/android/deskclock/worldclock/db/DbCities.java @@ -1,4 +1,6 @@ -/* +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a Contribution. + * * Copyright (C) 2013 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/com/android/deskclock/worldclock/db/DbCity.java b/src/com/android/deskclock/worldclock/db/DbCity.java index 6fa4bbad5..8ebccf938 100644..100755 --- a/src/com/android/deskclock/worldclock/db/DbCity.java +++ b/src/com/android/deskclock/worldclock/db/DbCity.java @@ -1,4 +1,6 @@ -/* +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a Contribution. + * * Copyright (C) 2013 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/com/android/deskclock/worldclock/db/DbCityDatabaseHelper.java b/src/com/android/deskclock/worldclock/db/DbCityDatabaseHelper.java index ca2f4c6df..e813464f3 100644..100755 --- a/src/com/android/deskclock/worldclock/db/DbCityDatabaseHelper.java +++ b/src/com/android/deskclock/worldclock/db/DbCityDatabaseHelper.java @@ -1,4 +1,6 @@ -/* +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a Contribution. + * * Copyright (C) 2013 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/com/android/deskclock/worldclock/db/DbCityProvider.java b/src/com/android/deskclock/worldclock/db/DbCityProvider.java index 710344053..d2bb9a13b 100644..100755 --- a/src/com/android/deskclock/worldclock/db/DbCityProvider.java +++ b/src/com/android/deskclock/worldclock/db/DbCityProvider.java @@ -1,4 +1,6 @@ -/* +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Not a Contribution. + * * Copyright (C) 2013 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -167,7 +169,8 @@ public class DbCityProvider extends ContentProvider { int mpid = android.os.Process.myPid(); int cpid = android.os.Binder.getCallingPid(); if (mpid != cpid) { - throw new SecurityException("Permission Denial: writing is only allowed to DeskClock app."); + throw new SecurityException("Permission Denial:" + + "writing is only allowed to DeskClock app."); } } } |