summaryrefslogtreecommitdiffstats
path: root/fmapp2
diff options
context:
space:
mode:
Diffstat (limited to 'fmapp2')
-rw-r--r--fmapp2/Android.mk25
-rw-r--r--fmapp2/AndroidManifest.xml148
-rw-r--r--fmapp2/fmtransmitter.xml223
-rw-r--r--fmapp2/res/anim/preset_select.xml38
-rw-r--r--fmapp2/res/drawable-hdpi/bg.pngbin0 -> 103618 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/bg_landscape.pngbin0 -> 71074 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_arrow_left_default.pngbin0 -> 3951 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_arrow_left_press.pngbin0 -> 4065 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_arrow_left_selected.pngbin0 -> 3881 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_arrow_right_default.pngbin0 -> 3955 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_arrow_right_press.pngbin0 -> 4055 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_arrow_right_selected.pngbin0 -> 3843 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_left_normal.9.pngbin0 -> 588 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_left_pressed.9.pngbin0 -> 593 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_left_selected.9.pngbin0 -> 610 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_middle_normal.9.pngbin0 -> 708 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_middle_pressed.9.pngbin0 -> 715 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_middle_selected.9.pngbin0 -> 762 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_right_normal.9.pngbin0 -> 576 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_right_pressed.9.pngbin0 -> 587 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/btn_strip_trans_right_selected.9.pngbin0 -> 614 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/count_down.pngbin0 -> 3152 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_btn_earphone_default.pngbin0 -> 4612 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_btn_earphone_diable.pngbin0 -> 4589 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_btn_earphone_select.pngbin0 -> 4651 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_btn_onoff_default.pngbin0 -> 5218 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_btn_onoff_disable.pngbin0 -> 5184 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_btn_onoff_press.pngbin0 -> 5289 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_btn_spaker.pngbin0 -> 5699 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_btn_spaker_select.pngbin0 -> 5798 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/ic_launcher_fmradio.pngbin0 -> 7052 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/recorder_start.pngbin0 -> 4791 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/recorder_stop.pngbin0 -> 4930 bytes
-rw-r--r--fmapp2/res/drawable-hdpi/stat_notify_fm.pngbin0 -> 3328 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/bg.pngbin0 -> 103618 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/bg_landscape.pngbin0 -> 71074 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_arrow_left_default.pngbin0 -> 3743 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_arrow_left_press.pngbin0 -> 3824 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_arrow_left_selected.pngbin0 -> 3676 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_arrow_right_default.pngbin0 -> 3760 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_arrow_right_press.pngbin0 -> 3847 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_arrow_right_selected.pngbin0 -> 3678 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_left_normal.9.pngbin0 -> 519 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_left_pressed.9.pngbin0 -> 491 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_left_selected.9.pngbin0 -> 511 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_middle_normal.9.pngbin0 -> 604 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_middle_pressed.9.pngbin0 -> 581 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_middle_selected.9.pngbin0 -> 616 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_right_normal.9.pngbin0 -> 483 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_right_pressed.9.pngbin0 -> 488 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/btn_strip_trans_right_selected.9.pngbin0 -> 515 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_btn_earphone_default.pngbin0 -> 4317 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_btn_earphone_diable.pngbin0 -> 4220 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_btn_earphone_select.pngbin0 -> 4254 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_btn_onoff_default.pngbin0 -> 4775 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_btn_onoff_disable.pngbin0 -> 4663 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_btn_onoff_press.pngbin0 -> 4696 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_btn_spaker.pngbin0 -> 4879 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_btn_spaker_select.pngbin0 -> 4809 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/ic_launcher_fmradio.pngbin0 -> 5782 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/recorder_selected.pngbin0 -> 4016 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/recorder_start.pngbin0 -> 4791 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/recorder_stop.pngbin0 -> 4930 bytes
-rw-r--r--fmapp2/res/drawable-mdpi/stat_notify_fm.pngbin0 -> 3136 bytes
-rw-r--r--fmapp2/res/drawable/album_border_large.1.pngbin0 -> 1349 bytes
-rw-r--r--fmapp2/res/drawable/alert_dialog_icon.pngbin0 -> 3645 bytes
-rw-r--r--fmapp2/res/drawable/application_border.xml33
-rw-r--r--fmapp2/res/drawable/bg.pngbin0 -> 103618 bytes
-rw-r--r--fmapp2/res/drawable/bg_landscape.pngbin0 -> 71074 bytes
-rw-r--r--fmapp2/res/drawable/btn_arrow_left.xml40
-rw-r--r--fmapp2/res/drawable/btn_arrow_left_default.pngbin0 -> 1203 bytes
-rw-r--r--fmapp2/res/drawable/btn_arrow_left_press.pngbin0 -> 1545 bytes
-rw-r--r--fmapp2/res/drawable/btn_arrow_left_selected.pngbin0 -> 1581 bytes
-rw-r--r--fmapp2/res/drawable/btn_arrow_right.xml41
-rw-r--r--fmapp2/res/drawable/btn_arrow_right_default.pngbin0 -> 1228 bytes
-rw-r--r--fmapp2/res/drawable/btn_arrow_right_press.pngbin0 -> 1634 bytes
-rw-r--r--fmapp2/res/drawable/btn_arrow_right_selected.pngbin0 -> 1663 bytes
-rw-r--r--fmapp2/res/drawable/btn_earphone.xml39
-rw-r--r--fmapp2/res/drawable/btn_onoff.xml39
-rw-r--r--fmapp2/res/drawable/btn_recorder.xml39
-rw-r--r--fmapp2/res/drawable/btn_speaker.xml37
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_left.xml43
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_left_normal.9.pngbin0 -> 588 bytes
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_left_pressed.9.pngbin0 -> 593 bytes
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_left_selected.9.pngbin0 -> 610 bytes
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_middle.xml43
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_middle_normal.9.pngbin0 -> 708 bytes
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_middle_pressed.9.pngbin0 -> 715 bytes
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_middle_selected.9.pngbin0 -> 762 bytes
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_right.xml43
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_right_normal.9.pngbin0 -> 576 bytes
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_right_pressed.9.pngbin0 -> 587 bytes
-rw-r--r--fmapp2/res/drawable/btn_strip_trans_right_selected.9.pngbin0 -> 614 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_earphone_default.pngbin0 -> 4612 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_earphone_diable.pngbin0 -> 4589 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_earphone_select.pngbin0 -> 4651 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_forward.pngbin0 -> 1228 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_forward_dis.pngbin0 -> 1182 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_onoff.pngbin0 -> 1709 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_onoff_default.pngbin0 -> 5218 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_onoff_disable.pngbin0 -> 5184 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_onoff_press.pngbin0 -> 5289 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_preset.pngbin0 -> 807 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_search.pngbin0 -> 1747 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_search_stop.pngbin0 -> 2290 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_spaker.pngbin0 -> 5699 bytes
-rw-r--r--fmapp2/res/drawable/ic_btn_spaker_select.pngbin0 -> 5798 bytes
-rw-r--r--fmapp2/res/drawable/ic_launcher_fm_tx.pngbin0 -> 3612 bytes
-rw-r--r--fmapp2/res/drawable/ic_launcher_fmradio.pngbin0 -> 1289 bytes
-rw-r--r--fmapp2/res/drawable/ic_menu_record.pngbin0 -> 798 bytes
-rw-r--r--fmapp2/res/drawable/ic_signal_2.pngbin0 -> 1358 bytes
-rw-r--r--fmapp2/res/drawable/ic_silent_mode.pngbin0 -> 1362 bytes
-rw-r--r--fmapp2/res/drawable/ic_silent_mode_off.pngbin0 -> 1555 bytes
-rw-r--r--fmapp2/res/drawable/ic_status_fm_tx.pngbin0 -> 802 bytes
-rw-r--r--fmapp2/res/drawable/ic_stereo.pngbin0 -> 742 bytes
-rw-r--r--fmapp2/res/drawable/icon.pngbin0 -> 3180 bytes
-rw-r--r--fmapp2/res/drawable/image_border_bg_normal.9.pngbin0 -> 3423 bytes
-rw-r--r--fmapp2/res/drawable/preset_page_button.xml35
-rw-r--r--fmapp2/res/drawable/recorder_selected.pngbin0 -> 4239 bytes
-rw-r--r--fmapp2/res/drawable/recorder_start.pngbin0 -> 4006 bytes
-rw-r--r--fmapp2/res/drawable/recorder_stop.pngbin0 -> 4172 bytes
-rw-r--r--fmapp2/res/drawable/stat_notify_fm.pngbin0 -> 350 bytes
-rw-r--r--fmapp2/res/drawable/station_border.xml33
-rw-r--r--fmapp2/res/layout-800x480/fmradio.xml202
-rw-r--r--fmapp2/res/layout-800x480/fmtags.xml40
-rw-r--r--fmapp2/res/layout-800x480/fmtransmitter.xml220
-rw-r--r--fmapp2/res/layout-800x480/navigate.xml188
-rw-r--r--fmapp2/res/layout-800x480/station_info.xml185
-rw-r--r--fmapp2/res/layout-land-finger-800x480/fmradio.xml199
-rw-r--r--fmapp2/res/layout-land-finger-800x480/fmtransmitter.xml222
-rw-r--r--fmapp2/res/layout-land-finger-800x480/navigate.xml128
-rw-r--r--fmapp2/res/layout-land-finger-800x480/station_info.xml174
-rw-r--r--fmapp2/res/layout-land-finger/fmradio.xml190
-rw-r--r--fmapp2/res/layout-land-finger/fmtransmitter.xml223
-rw-r--r--fmapp2/res/layout-land-finger/navigate.xml128
-rw-r--r--fmapp2/res/layout-land-finger/station_info.xml172
-rw-r--r--fmapp2/res/layout/alert_dialog_list.xml39
-rw-r--r--fmapp2/res/layout/alert_dialog_text_entry.xml50
-rw-r--r--fmapp2/res/layout/delete_dialog.xml67
-rw-r--r--fmapp2/res/layout/fmradio.xml202
-rw-r--r--fmapp2/res/layout/fmstats.xml107
-rw-r--r--fmapp2/res/layout/fmtags.xml40
-rw-r--r--fmapp2/res/layout/fmtransmitter.xml223
-rw-r--r--fmapp2/res/layout/frequency_picker.xml46
-rw-r--r--fmapp2/res/layout/frequency_picker_dialog.xml27
-rw-r--r--fmapp2/res/layout/navigate.xml158
-rw-r--r--fmapp2/res/layout/rename_dialog.xml67
-rw-r--r--fmapp2/res/layout/station_info.xml192
-rw-r--r--fmapp2/res/layout/station_list.xml43
-rw-r--r--fmapp2/res/layout/station_list_item.xml76
-rw-r--r--fmapp2/res/layout/statusbar.xml56
-rw-r--r--fmapp2/res/values-zh-rCN/arrays.xml203
-rw-r--r--fmapp2/res/values-zh-rCN/strings.xml119
-rw-r--r--fmapp2/res/values-zh-rTW/strings.xml101
-rw-r--r--fmapp2/res/values/arrays.xml354
-rw-r--r--fmapp2/res/values/strings.xml235
-rw-r--r--fmapp2/res/values/styles.xml61
-rw-r--r--fmapp2/src/com/caf/fmradio/FMAdapterApp.java64
-rw-r--r--fmapp2/src/com/caf/fmradio/FMMediaButtonIntentReceiver.java71
-rw-r--r--fmapp2/src/com/caf/fmradio/FMRadio.java3037
-rw-r--r--fmapp2/src/com/caf/fmradio/FMRadioService.java3009
-rw-r--r--fmapp2/src/com/caf/fmradio/FMStats.java1346
-rw-r--r--fmapp2/src/com/caf/fmradio/FMTransmitterActivity.java1563
-rw-r--r--fmapp2/src/com/caf/fmradio/FMTransmitterConfigReceiver.java81
-rw-r--r--fmapp2/src/com/caf/fmradio/FMTransmitterService.java1221
-rw-r--r--fmapp2/src/com/caf/fmradio/FmSharedPreferences.java1114
-rw-r--r--fmapp2/src/com/caf/fmradio/FmTags.java99
-rw-r--r--fmapp2/src/com/caf/fmradio/HorizontalNumberPicker.java2223
-rw-r--r--fmapp2/src/com/caf/fmradio/IFMRadioService.aidl57
-rw-r--r--fmapp2/src/com/caf/fmradio/IFMRadioServiceCallbacks.aidl48
-rw-r--r--fmapp2/src/com/caf/fmradio/IFMTransmitterService.aidl23
-rw-r--r--fmapp2/src/com/caf/fmradio/IFMTransmitterServiceCallbacks.aidl42
-rw-r--r--fmapp2/src/com/caf/fmradio/PresetList.java323
-rw-r--r--fmapp2/src/com/caf/fmradio/PresetStation.java951
-rw-r--r--fmapp2/src/com/caf/fmradio/Settings.java397
-rw-r--r--fmapp2/src/com/caf/fmradio/StationListActivity.java341
-rw-r--r--fmapp2/src/com/caf/hc_utils/A2dpDeviceStatus.java105
-rw-r--r--fmapp2/src/com/caf/hc_utils/FrequencyPicker.java429
-rw-r--r--fmapp2/src/com/caf/hc_utils/FrequencyPickerDialog.java186
-rw-r--r--fmapp2/src/com/caf/utils/A2dpDeviceStatus.java88
-rw-r--r--fmapp2/src/com/caf/utils/FrequencyPicker.java407
-rw-r--r--fmapp2/src/com/caf/utils/FrequencyPickerDialog.java159
182 files changed, 23020 insertions, 0 deletions
diff --git a/fmapp2/Android.mk b/fmapp2/Android.mk
new file mode 100644
index 0000000..3985572
--- /dev/null
+++ b/fmapp2/Android.mk
@@ -0,0 +1,25 @@
+ifeq ($(BOARD_HAVE_QCOM_FM),true)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/com/caf/fmradio) \
+ src/com/caf/fmradio/IFMRadioServiceCallbacks.aidl \
+ src/com/caf/fmradio/IFMRadioService.aidl \
+ src/com/caf/fmradio/IFMTransmitterServiceCallbacks.aidl \
+ src/com/caf/fmradio/IFMTransmitterService.aidl \
+
+
+ifeq ($(call is-android-codename-in-list,JELLY_BEAN ICECREAM_SANDWICH HONEYCOMB),true)
+LOCAL_SRC_FILES += $(call all-java-files-under, src/com/caf/hc_utils)
+else
+LOCAL_SRC_FILES += $(call all-java-files-under, src/com/caf/utils)
+endif
+LOCAL_PACKAGE_NAME := FM2
+LOCAL_CERTIFICATE := platform
+LOCAL_JNI_SHARED_LIBRARIES := libqcomfm_jni
+LOCAL_JAVA_LIBRARIES := qcom.fmradio
+
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/fmapp2/AndroidManifest.xml b/fmapp2/AndroidManifest.xml
new file mode 100644
index 0000000..4cef8c0
--- /dev/null
+++ b/fmapp2/AndroidManifest.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.caf.fmradio"
+ android:sharedUserId="android.uid.system" >
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+
+ <application
+ android:icon="@drawable/ic_launcher_fmradio"
+ android:label="@string/app_name"
+ android:name=".FMAdapterApp">
+ <activity android:icon="@drawable/ic_launcher_fmradio"
+ android:name=".FMRadio"
+ android:label="@string/app_name"
+ android:clearTaskOnLaunch="true"
+ android:taskAffinity=""
+ android:launchMode="singleTop"
+ android:excludeFromRecents="true" >
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ <intent-filter>
+ <action android:name="com.caf.fmradio.FMRADIO_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+
+ </activity>
+
+ <receiver android:name="com.caf.fmradio.FMMediaButtonIntentReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.MEDIA_BUTTON" />
+ </intent-filter>
+ </receiver>
+
+ <activity android:name=".Settings"
+ android:label="@string/settings_menu">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+
+ </activity>
+
+ <activity android:icon="@drawable/ic_launcher_fm_tx"
+ android:name=".FMTransmitterActivity"
+ android:label="@string/tx_app_name"
+ android:clearTaskOnLaunch="true"
+ android:taskAffinity=""
+ android:excludeFromRecents="true">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ <intent-filter>
+ <action android:name="com.caf.fmradio.FMTRANSMITTER_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+
+ </activity>
+
+ <receiver android:name="FMTransmitterConfigReceiver">
+
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+
+ </receiver>
+
+ <service android:name=".FMTransmitterService"
+ android:exported="true" />
+
+ <activity android:name=".FMStats"
+ android:label="@string/test_menu">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+
+ </activity>
+
+ <activity android:name=".FmTags"
+ android:label="@string/rt_plus_tags">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+
+ </activity>
+
+ <activity android:name=".StationListActivity"
+ android:label="@string/app_label_all_channels"
+ android:configChanges="orientation|keyboardHidden|screenSize|fontScale">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+
+ </activity>
+
+ <service android:name=".FMRadioService"
+ android:exported="true" />
+
+ </application>
+
+</manifest>
diff --git a/fmapp2/fmtransmitter.xml b/fmapp2/fmtransmitter.xml
new file mode 100644
index 0000000..5e65620
--- /dev/null
+++ b/fmapp2/fmtransmitter.xml
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <!--
+ This LinearLayout contains Station Information display and the
+ function buttons
+ -->
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="horizontal">
+
+ <LinearLayout android:background="@drawable/station_border"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="top|fill_vertical">
+
+ <RelativeLayout android:id="@+id/station_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <!-- This included layout contains Station Information to display -->
+ <LinearLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5px"
+ android:layout_marginLeft="5px"
+ android:layout_marginRight="5px"
+ android:orientation="vertical"
+ android:gravity="fill_vertical">
+
+ <LinearLayout android:id="@+id/station_frequency_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="25px"
+ android:paddingBottom="25px"
+ android:gravity="fill_vertical|center">
+
+ <TextView android:id="@+id/transmit_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15px"
+ android:layout_centerHorizontal="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:text="@string/transmit_msg_string" />
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:textSize="56px"
+ android:text="@string/frequency_string" />
+ </LinearLayout>
+
+ <!-- Station Radio Text information display -->
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingBottom="2px"
+ android:paddingLeft="5px"
+ android:paddingRight="5px"
+ android:gravity="center_horizontal"
+ android:text="@string/radio_text_string" />
+ <!-- android:gravity="center_horizontal" -->
+ </LinearLayout>
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:paddingLeft="10px"
+ android:paddingRight="2px"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:visibility="visible"
+ android:src="@drawable/btn_arrow_left" />
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:paddingLeft="2px"
+ android:paddingRight="10px"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:visibility="visible"
+ android:src="@drawable/btn_arrow_right" />
+
+ </RelativeLayout>
+ </LinearLayout>
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:layout_marginLeft="5px"
+ android:layout_marginRight="5px"
+ android:layout_marginBottom="2px">
+ <!-- On-Off button -->
+ <ImageButton android:id="@+id/btn_onoff"
+ android:layout_width="75px"
+ android:layout_height="75px"
+ android:src="@drawable/ic_btn_onoff"
+ android:layout_marginTop="15px"
+ android:layout_marginBottom="15px" />
+ </LinearLayout>
+
+ </LinearLayout>
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1px"
+ android:background="#ffffffff" />
+
+ <!--
+ Layout contains the Next/Previous Presets and Tune buttons and status
+ msg text
+ -->
+ <LinearLayout android:id="@+id/presets_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_weight="0"
+ android:layout_gravity="center"
+ android:layout_marginTop="15dip"
+ android:layout_marginBottom="10dip"
+ android:gravity="center_horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ android:text="@string/default_station"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:textSize="24sp"
+ android:singleLine="true"
+ android:layout_width="110dip"
+ android:layout_height="65dip" />
+
+ <Button android:id="@+id/presets_button_2"
+ android:text="@string/default_station"
+ android:layout_marginRight="20dip"
+ android:textSize="24sp"
+ android:singleLine="true"
+ android:layout_width="110dip"
+ android:layout_height="65dip" />
+
+ <Button android:id="@+id/presets_button_3"
+ android:text="@string/default_station"
+ android:layout_marginRight="20dip"
+ android:textSize="24sp"
+ android:singleLine="true"
+ android:layout_width="110dip"
+ android:layout_height="65dip" />
+
+ <Button android:id="@+id/presets_button_4"
+ android:text="@string/default_blank"
+ android:layout_marginRight="20dip"
+ android:textSize="24sp"
+ android:singleLine="true"
+ android:layout_width="110dip"
+ android:layout_height="65dip" />
+
+ <Button android:id="@+id/presets_button_5"
+ android:text="@string/default_blank"
+ android:layout_marginRight="20dip"
+ android:textSize="24sp"
+ android:singleLine="true"
+ android:layout_width="110dip"
+ android:layout_height="65dip" />
+
+ <Button android:id="@+id/presets_button_6"
+ android:text="@string/default_blank"
+ android:layout_marginRight="20dip"
+ android:textSize="24sp"
+ android:singleLine="true"
+ android:layout_width="110dip"
+ android:layout_height="65dip"/>
+
+ </LinearLayout>
+
+
+</LinearLayout>
diff --git a/fmapp2/res/anim/preset_select.xml b/fmapp2/res/anim/preset_select.xml
new file mode 100644
index 0000000..8fcf768
--- /dev/null
+++ b/fmapp2/res/anim/preset_select.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<scale xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromXScale="0.75"
+ android:toXScale="1.5"
+ android:fromYScale="0.75"
+ android:toYScale="1.5"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:startOffset="0"
+ android:duration="500"
+ android:fillBefore="true" />
diff --git a/fmapp2/res/drawable-hdpi/bg.png b/fmapp2/res/drawable-hdpi/bg.png
new file mode 100644
index 0000000..fd10f0b
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/bg.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/bg_landscape.png b/fmapp2/res/drawable-hdpi/bg_landscape.png
new file mode 100644
index 0000000..83bfb23
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/bg_landscape.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_arrow_left_default.png b/fmapp2/res/drawable-hdpi/btn_arrow_left_default.png
new file mode 100644
index 0000000..857fb7a
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_arrow_left_default.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_arrow_left_press.png b/fmapp2/res/drawable-hdpi/btn_arrow_left_press.png
new file mode 100644
index 0000000..438e392
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_arrow_left_press.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_arrow_left_selected.png b/fmapp2/res/drawable-hdpi/btn_arrow_left_selected.png
new file mode 100644
index 0000000..fab8ee4
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_arrow_left_selected.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_arrow_right_default.png b/fmapp2/res/drawable-hdpi/btn_arrow_right_default.png
new file mode 100644
index 0000000..547e505
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_arrow_right_default.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_arrow_right_press.png b/fmapp2/res/drawable-hdpi/btn_arrow_right_press.png
new file mode 100644
index 0000000..96eacae
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_arrow_right_press.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_arrow_right_selected.png b/fmapp2/res/drawable-hdpi/btn_arrow_right_selected.png
new file mode 100644
index 0000000..67a86f5
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_arrow_right_selected.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_left_normal.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_left_normal.9.png
new file mode 100644
index 0000000..31b3970
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_left_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_left_pressed.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_left_pressed.9.png
new file mode 100644
index 0000000..3b13559
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_left_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_left_selected.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_left_selected.9.png
new file mode 100644
index 0000000..0607d23
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_left_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_normal.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_normal.9.png
new file mode 100644
index 0000000..d9f4f84
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_pressed.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_pressed.9.png
new file mode 100644
index 0000000..3cc35e7
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_selected.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_selected.9.png
new file mode 100644
index 0000000..736e9b2
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_middle_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_right_normal.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_right_normal.9.png
new file mode 100644
index 0000000..3e83fd4
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_right_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_right_pressed.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_right_pressed.9.png
new file mode 100644
index 0000000..1076681
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_right_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/btn_strip_trans_right_selected.9.png b/fmapp2/res/drawable-hdpi/btn_strip_trans_right_selected.9.png
new file mode 100644
index 0000000..b0cf219
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/btn_strip_trans_right_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/count_down.png b/fmapp2/res/drawable-hdpi/count_down.png
new file mode 100644
index 0000000..3c5f921
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/count_down.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_btn_earphone_default.png b/fmapp2/res/drawable-hdpi/ic_btn_earphone_default.png
new file mode 100644
index 0000000..6a5b8db
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_btn_earphone_default.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_btn_earphone_diable.png b/fmapp2/res/drawable-hdpi/ic_btn_earphone_diable.png
new file mode 100644
index 0000000..f32b96e
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_btn_earphone_diable.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_btn_earphone_select.png b/fmapp2/res/drawable-hdpi/ic_btn_earphone_select.png
new file mode 100644
index 0000000..67afd3e
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_btn_earphone_select.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_btn_onoff_default.png b/fmapp2/res/drawable-hdpi/ic_btn_onoff_default.png
new file mode 100644
index 0000000..1c0f511
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_btn_onoff_default.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_btn_onoff_disable.png b/fmapp2/res/drawable-hdpi/ic_btn_onoff_disable.png
new file mode 100644
index 0000000..12b2d28
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_btn_onoff_disable.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_btn_onoff_press.png b/fmapp2/res/drawable-hdpi/ic_btn_onoff_press.png
new file mode 100644
index 0000000..efeebd3
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_btn_onoff_press.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_btn_spaker.png b/fmapp2/res/drawable-hdpi/ic_btn_spaker.png
new file mode 100644
index 0000000..f439e2e
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_btn_spaker.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_btn_spaker_select.png b/fmapp2/res/drawable-hdpi/ic_btn_spaker_select.png
new file mode 100644
index 0000000..bd5d57b
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_btn_spaker_select.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/ic_launcher_fmradio.png b/fmapp2/res/drawable-hdpi/ic_launcher_fmradio.png
new file mode 100644
index 0000000..4815bce
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/ic_launcher_fmradio.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/recorder_start.png b/fmapp2/res/drawable-hdpi/recorder_start.png
new file mode 100644
index 0000000..a4425e2
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/recorder_start.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/recorder_stop.png b/fmapp2/res/drawable-hdpi/recorder_stop.png
new file mode 100644
index 0000000..bb8b32b
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/recorder_stop.png
Binary files differ
diff --git a/fmapp2/res/drawable-hdpi/stat_notify_fm.png b/fmapp2/res/drawable-hdpi/stat_notify_fm.png
new file mode 100644
index 0000000..d729ffb
--- /dev/null
+++ b/fmapp2/res/drawable-hdpi/stat_notify_fm.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/bg.png b/fmapp2/res/drawable-mdpi/bg.png
new file mode 100644
index 0000000..fd10f0b
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/bg.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/bg_landscape.png b/fmapp2/res/drawable-mdpi/bg_landscape.png
new file mode 100644
index 0000000..83bfb23
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/bg_landscape.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_arrow_left_default.png b/fmapp2/res/drawable-mdpi/btn_arrow_left_default.png
new file mode 100644
index 0000000..da077e6
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_arrow_left_default.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_arrow_left_press.png b/fmapp2/res/drawable-mdpi/btn_arrow_left_press.png
new file mode 100644
index 0000000..b18c859
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_arrow_left_press.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_arrow_left_selected.png b/fmapp2/res/drawable-mdpi/btn_arrow_left_selected.png
new file mode 100644
index 0000000..aa5ebe4
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_arrow_left_selected.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_arrow_right_default.png b/fmapp2/res/drawable-mdpi/btn_arrow_right_default.png
new file mode 100644
index 0000000..696f53a
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_arrow_right_default.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_arrow_right_press.png b/fmapp2/res/drawable-mdpi/btn_arrow_right_press.png
new file mode 100644
index 0000000..069df33
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_arrow_right_press.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_arrow_right_selected.png b/fmapp2/res/drawable-mdpi/btn_arrow_right_selected.png
new file mode 100644
index 0000000..d7d797b
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_arrow_right_selected.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_left_normal.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_left_normal.9.png
new file mode 100644
index 0000000..2d7db2b
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_left_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_left_pressed.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_left_pressed.9.png
new file mode 100644
index 0000000..ceddd76
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_left_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_left_selected.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_left_selected.9.png
new file mode 100644
index 0000000..bc40887
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_left_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_normal.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_normal.9.png
new file mode 100644
index 0000000..77ee4be
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_pressed.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_pressed.9.png
new file mode 100644
index 0000000..19b33af
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_selected.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_selected.9.png
new file mode 100644
index 0000000..33ca95c
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_middle_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_right_normal.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_right_normal.9.png
new file mode 100644
index 0000000..2de79b5
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_right_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_right_pressed.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_right_pressed.9.png
new file mode 100644
index 0000000..41d2650
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_right_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/btn_strip_trans_right_selected.9.png b/fmapp2/res/drawable-mdpi/btn_strip_trans_right_selected.9.png
new file mode 100644
index 0000000..a65e65f
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/btn_strip_trans_right_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_btn_earphone_default.png b/fmapp2/res/drawable-mdpi/ic_btn_earphone_default.png
new file mode 100644
index 0000000..c729ed0
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_btn_earphone_default.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_btn_earphone_diable.png b/fmapp2/res/drawable-mdpi/ic_btn_earphone_diable.png
new file mode 100644
index 0000000..c0929ba
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_btn_earphone_diable.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_btn_earphone_select.png b/fmapp2/res/drawable-mdpi/ic_btn_earphone_select.png
new file mode 100644
index 0000000..fb51ce1
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_btn_earphone_select.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_btn_onoff_default.png b/fmapp2/res/drawable-mdpi/ic_btn_onoff_default.png
new file mode 100644
index 0000000..9303cfb
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_btn_onoff_default.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_btn_onoff_disable.png b/fmapp2/res/drawable-mdpi/ic_btn_onoff_disable.png
new file mode 100644
index 0000000..57e060a
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_btn_onoff_disable.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_btn_onoff_press.png b/fmapp2/res/drawable-mdpi/ic_btn_onoff_press.png
new file mode 100644
index 0000000..1228a66
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_btn_onoff_press.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_btn_spaker.png b/fmapp2/res/drawable-mdpi/ic_btn_spaker.png
new file mode 100644
index 0000000..b72fab9
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_btn_spaker.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_btn_spaker_select.png b/fmapp2/res/drawable-mdpi/ic_btn_spaker_select.png
new file mode 100644
index 0000000..250baea
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_btn_spaker_select.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/ic_launcher_fmradio.png b/fmapp2/res/drawable-mdpi/ic_launcher_fmradio.png
new file mode 100644
index 0000000..3361ad5
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/ic_launcher_fmradio.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/recorder_selected.png b/fmapp2/res/drawable-mdpi/recorder_selected.png
new file mode 100644
index 0000000..1886ba8
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/recorder_selected.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/recorder_start.png b/fmapp2/res/drawable-mdpi/recorder_start.png
new file mode 100644
index 0000000..a4425e2
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/recorder_start.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/recorder_stop.png b/fmapp2/res/drawable-mdpi/recorder_stop.png
new file mode 100644
index 0000000..bb8b32b
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/recorder_stop.png
Binary files differ
diff --git a/fmapp2/res/drawable-mdpi/stat_notify_fm.png b/fmapp2/res/drawable-mdpi/stat_notify_fm.png
new file mode 100644
index 0000000..6861a44
--- /dev/null
+++ b/fmapp2/res/drawable-mdpi/stat_notify_fm.png
Binary files differ
diff --git a/fmapp2/res/drawable/album_border_large.1.png b/fmapp2/res/drawable/album_border_large.1.png
new file mode 100644
index 0000000..e5ffbc7
--- /dev/null
+++ b/fmapp2/res/drawable/album_border_large.1.png
Binary files differ
diff --git a/fmapp2/res/drawable/alert_dialog_icon.png b/fmapp2/res/drawable/alert_dialog_icon.png
new file mode 100644
index 0000000..0a7de04
--- /dev/null
+++ b/fmapp2/res/drawable/alert_dialog_icon.png
Binary files differ
diff --git a/fmapp2/res/drawable/application_border.xml b/fmapp2/res/drawable/application_border.xml
new file mode 100644
index 0000000..eaf0918
--- /dev/null
+++ b/fmapp2/res/drawable/application_border.xml
@@ -0,0 +1,33 @@
+<!--
+ * Copyright (c) 2009, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <stroke android:width="1dp" android:color="#A0000000" />
+ <padding android:left="1dp" android:top="1dp"
+ android:right="1dp" android:bottom="1dp" />
+ <corners android:radius="1dp" />
+</shape>
diff --git a/fmapp2/res/drawable/bg.png b/fmapp2/res/drawable/bg.png
new file mode 100644
index 0000000..fd10f0b
--- /dev/null
+++ b/fmapp2/res/drawable/bg.png
Binary files differ
diff --git a/fmapp2/res/drawable/bg_landscape.png b/fmapp2/res/drawable/bg_landscape.png
new file mode 100644
index 0000000..83bfb23
--- /dev/null
+++ b/fmapp2/res/drawable/bg_landscape.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_arrow_left.xml b/fmapp2/res/drawable/btn_arrow_left.xml
new file mode 100644
index 0000000..e64966c
--- /dev/null
+++ b/fmapp2/res/drawable/btn_arrow_left.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_arrow_left_press" />
+
+ <item android:state_window_focused="true" android:state_focused="true"
+ android:drawable="@drawable/btn_arrow_left_selected" />
+
+ <item
+ android:drawable="@drawable/btn_arrow_left_default" />
+
+</selector>
diff --git a/fmapp2/res/drawable/btn_arrow_left_default.png b/fmapp2/res/drawable/btn_arrow_left_default.png
new file mode 100644
index 0000000..2a35952
--- /dev/null
+++ b/fmapp2/res/drawable/btn_arrow_left_default.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_arrow_left_press.png b/fmapp2/res/drawable/btn_arrow_left_press.png
new file mode 100644
index 0000000..8b72f0a
--- /dev/null
+++ b/fmapp2/res/drawable/btn_arrow_left_press.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_arrow_left_selected.png b/fmapp2/res/drawable/btn_arrow_left_selected.png
new file mode 100644
index 0000000..6cd3c9f
--- /dev/null
+++ b/fmapp2/res/drawable/btn_arrow_left_selected.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_arrow_right.xml b/fmapp2/res/drawable/btn_arrow_right.xml
new file mode 100644
index 0000000..e4ab6f9
--- /dev/null
+++ b/fmapp2/res/drawable/btn_arrow_right.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_arrow_right_press" />
+
+ <item android:state_window_focused="true" android:state_focused="true"
+ android:drawable="@drawable/btn_arrow_right_selected" />
+
+ <item
+ android:drawable="@drawable/btn_arrow_right_default" />
+
+</selector>
diff --git a/fmapp2/res/drawable/btn_arrow_right_default.png b/fmapp2/res/drawable/btn_arrow_right_default.png
new file mode 100644
index 0000000..ae6fd53
--- /dev/null
+++ b/fmapp2/res/drawable/btn_arrow_right_default.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_arrow_right_press.png b/fmapp2/res/drawable/btn_arrow_right_press.png
new file mode 100644
index 0000000..a4ae45b
--- /dev/null
+++ b/fmapp2/res/drawable/btn_arrow_right_press.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_arrow_right_selected.png b/fmapp2/res/drawable/btn_arrow_right_selected.png
new file mode 100644
index 0000000..93bcc77
--- /dev/null
+++ b/fmapp2/res/drawable/btn_arrow_right_selected.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_earphone.xml b/fmapp2/res/drawable/btn_earphone.xml
new file mode 100644
index 0000000..5a185ed
--- /dev/null
+++ b/fmapp2/res/drawable/btn_earphone.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:drawable="@drawable/ic_btn_earphone_select" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/ic_btn_earphone_default" />
+ <item android:state_enabled="false"
+ android:drawable="@drawable/ic_btn_earphone_diable" />
+</selector>
diff --git a/fmapp2/res/drawable/btn_onoff.xml b/fmapp2/res/drawable/btn_onoff.xml
new file mode 100644
index 0000000..a024238
--- /dev/null
+++ b/fmapp2/res/drawable/btn_onoff.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:drawable="@drawable/ic_btn_onoff_press" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/ic_btn_onoff_default" />
+ <item android:state_enabled="false"
+ android:drawable="@drawable/ic_btn_onoff_disable" />
+</selector>
diff --git a/fmapp2/res/drawable/btn_recorder.xml b/fmapp2/res/drawable/btn_recorder.xml
new file mode 100644
index 0000000..88a5302
--- /dev/null
+++ b/fmapp2/res/drawable/btn_recorder.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:drawable="@drawable/recorder_selected" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/recorder_stop" />
+ <item android:state_enabled="false"
+ android:drawable="@drawable/recorder_start" />
+</selector>
diff --git a/fmapp2/res/drawable/btn_speaker.xml b/fmapp2/res/drawable/btn_speaker.xml
new file mode 100644
index 0000000..a22e62a
--- /dev/null
+++ b/fmapp2/res/drawable/btn_speaker.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:drawable="@drawable/ic_btn_spaker_select" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/ic_btn_spaker" />
+</selector>
diff --git a/fmapp2/res/drawable/btn_strip_trans_left.xml b/fmapp2/res/drawable/btn_strip_trans_left.xml
new file mode 100644
index 0000000..0a30f34
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_left.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_strip_trans_left_pressed" />
+
+ <item android:state_focused="true"
+ android:drawable="@drawable/btn_strip_trans_left_selected" />
+
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_strip_trans_left_normal" />
+
+</selector>
diff --git a/fmapp2/res/drawable/btn_strip_trans_left_normal.9.png b/fmapp2/res/drawable/btn_strip_trans_left_normal.9.png
new file mode 100644
index 0000000..31b3970
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_left_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_strip_trans_left_pressed.9.png b/fmapp2/res/drawable/btn_strip_trans_left_pressed.9.png
new file mode 100644
index 0000000..3b13559
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_left_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_strip_trans_left_selected.9.png b/fmapp2/res/drawable/btn_strip_trans_left_selected.9.png
new file mode 100644
index 0000000..0607d23
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_left_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_strip_trans_middle.xml b/fmapp2/res/drawable/btn_strip_trans_middle.xml
new file mode 100644
index 0000000..b6b95a6
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_middle.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_strip_trans_middle_pressed" />
+
+ <item android:state_focused="true"
+ android:drawable="@drawable/btn_strip_trans_middle_selected" />
+
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_strip_trans_middle_normal" />
+
+</selector>
diff --git a/fmapp2/res/drawable/btn_strip_trans_middle_normal.9.png b/fmapp2/res/drawable/btn_strip_trans_middle_normal.9.png
new file mode 100644
index 0000000..d9f4f84
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_middle_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_strip_trans_middle_pressed.9.png b/fmapp2/res/drawable/btn_strip_trans_middle_pressed.9.png
new file mode 100644
index 0000000..3cc35e7
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_middle_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_strip_trans_middle_selected.9.png b/fmapp2/res/drawable/btn_strip_trans_middle_selected.9.png
new file mode 100644
index 0000000..736e9b2
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_middle_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_strip_trans_right.xml b/fmapp2/res/drawable/btn_strip_trans_right.xml
new file mode 100644
index 0000000..512c963
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_right.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_strip_trans_right_pressed" />
+
+ <item android:state_focused="true"
+ android:drawable="@drawable/btn_strip_trans_right_selected" />
+
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_strip_trans_right_normal" />
+
+</selector>
diff --git a/fmapp2/res/drawable/btn_strip_trans_right_normal.9.png b/fmapp2/res/drawable/btn_strip_trans_right_normal.9.png
new file mode 100644
index 0000000..3e83fd4
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_right_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_strip_trans_right_pressed.9.png b/fmapp2/res/drawable/btn_strip_trans_right_pressed.9.png
new file mode 100644
index 0000000..1076681
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_right_pressed.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/btn_strip_trans_right_selected.9.png b/fmapp2/res/drawable/btn_strip_trans_right_selected.9.png
new file mode 100644
index 0000000..b0cf219
--- /dev/null
+++ b/fmapp2/res/drawable/btn_strip_trans_right_selected.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_earphone_default.png b/fmapp2/res/drawable/ic_btn_earphone_default.png
new file mode 100644
index 0000000..6a5b8db
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_earphone_default.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_earphone_diable.png b/fmapp2/res/drawable/ic_btn_earphone_diable.png
new file mode 100644
index 0000000..f32b96e
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_earphone_diable.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_earphone_select.png b/fmapp2/res/drawable/ic_btn_earphone_select.png
new file mode 100644
index 0000000..67afd3e
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_earphone_select.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_forward.png b/fmapp2/res/drawable/ic_btn_forward.png
new file mode 100644
index 0000000..0936fac
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_forward.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_forward_dis.png b/fmapp2/res/drawable/ic_btn_forward_dis.png
new file mode 100644
index 0000000..cd9a2f5
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_forward_dis.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_onoff.png b/fmapp2/res/drawable/ic_btn_onoff.png
new file mode 100644
index 0000000..4405b43
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_onoff.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_onoff_default.png b/fmapp2/res/drawable/ic_btn_onoff_default.png
new file mode 100644
index 0000000..1c0f511
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_onoff_default.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_onoff_disable.png b/fmapp2/res/drawable/ic_btn_onoff_disable.png
new file mode 100644
index 0000000..12b2d28
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_onoff_disable.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_onoff_press.png b/fmapp2/res/drawable/ic_btn_onoff_press.png
new file mode 100644
index 0000000..efeebd3
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_onoff_press.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_preset.png b/fmapp2/res/drawable/ic_btn_preset.png
new file mode 100644
index 0000000..35449b3
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_preset.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_search.png b/fmapp2/res/drawable/ic_btn_search.png
new file mode 100644
index 0000000..7eea584
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_search.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_search_stop.png b/fmapp2/res/drawable/ic_btn_search_stop.png
new file mode 100644
index 0000000..0b8c4e8
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_search_stop.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_spaker.png b/fmapp2/res/drawable/ic_btn_spaker.png
new file mode 100644
index 0000000..f439e2e
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_spaker.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_btn_spaker_select.png b/fmapp2/res/drawable/ic_btn_spaker_select.png
new file mode 100644
index 0000000..bd5d57b
--- /dev/null
+++ b/fmapp2/res/drawable/ic_btn_spaker_select.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_launcher_fm_tx.png b/fmapp2/res/drawable/ic_launcher_fm_tx.png
new file mode 100644
index 0000000..c6e41b2
--- /dev/null
+++ b/fmapp2/res/drawable/ic_launcher_fm_tx.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_launcher_fmradio.png b/fmapp2/res/drawable/ic_launcher_fmradio.png
new file mode 100644
index 0000000..2b1250c
--- /dev/null
+++ b/fmapp2/res/drawable/ic_launcher_fmradio.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_menu_record.png b/fmapp2/res/drawable/ic_menu_record.png
new file mode 100644
index 0000000..60f7a01
--- /dev/null
+++ b/fmapp2/res/drawable/ic_menu_record.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_signal_2.png b/fmapp2/res/drawable/ic_signal_2.png
new file mode 100644
index 0000000..efe4b5f
--- /dev/null
+++ b/fmapp2/res/drawable/ic_signal_2.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_silent_mode.png b/fmapp2/res/drawable/ic_silent_mode.png
new file mode 100644
index 0000000..c89291a
--- /dev/null
+++ b/fmapp2/res/drawable/ic_silent_mode.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_silent_mode_off.png b/fmapp2/res/drawable/ic_silent_mode_off.png
new file mode 100644
index 0000000..4748b9e
--- /dev/null
+++ b/fmapp2/res/drawable/ic_silent_mode_off.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_status_fm_tx.png b/fmapp2/res/drawable/ic_status_fm_tx.png
new file mode 100644
index 0000000..29383c9
--- /dev/null
+++ b/fmapp2/res/drawable/ic_status_fm_tx.png
Binary files differ
diff --git a/fmapp2/res/drawable/ic_stereo.png b/fmapp2/res/drawable/ic_stereo.png
new file mode 100644
index 0000000..b9bfec6
--- /dev/null
+++ b/fmapp2/res/drawable/ic_stereo.png
Binary files differ
diff --git a/fmapp2/res/drawable/icon.png b/fmapp2/res/drawable/icon.png
new file mode 100644
index 0000000..7502484
--- /dev/null
+++ b/fmapp2/res/drawable/icon.png
Binary files differ
diff --git a/fmapp2/res/drawable/image_border_bg_normal.9.png b/fmapp2/res/drawable/image_border_bg_normal.9.png
new file mode 100644
index 0000000..18e3607
--- /dev/null
+++ b/fmapp2/res/drawable/image_border_bg_normal.9.png
Binary files differ
diff --git a/fmapp2/res/drawable/preset_page_button.xml b/fmapp2/res/drawable/preset_page_button.xml
new file mode 100644
index 0000000..cb1457b
--- /dev/null
+++ b/fmapp2/res/drawable/preset_page_button.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:drawable="@drawable/ic_btn_forward_dis" /> <!-- disabled -->
+ <item android:state_enabled="true"
+ android:drawable="@drawable/ic_btn_forward" /> <!-- enabled -->
+ <item android:drawable="@drawable/ic_btn_forward" /> <!-- default -->
+ </selector>
diff --git a/fmapp2/res/drawable/recorder_selected.png b/fmapp2/res/drawable/recorder_selected.png
new file mode 100644
index 0000000..24e79ad
--- /dev/null
+++ b/fmapp2/res/drawable/recorder_selected.png
Binary files differ
diff --git a/fmapp2/res/drawable/recorder_start.png b/fmapp2/res/drawable/recorder_start.png
new file mode 100644
index 0000000..5297a31
--- /dev/null
+++ b/fmapp2/res/drawable/recorder_start.png
Binary files differ
diff --git a/fmapp2/res/drawable/recorder_stop.png b/fmapp2/res/drawable/recorder_stop.png
new file mode 100644
index 0000000..6017be9
--- /dev/null
+++ b/fmapp2/res/drawable/recorder_stop.png
Binary files differ
diff --git a/fmapp2/res/drawable/stat_notify_fm.png b/fmapp2/res/drawable/stat_notify_fm.png
new file mode 100644
index 0000000..16ed9bc
--- /dev/null
+++ b/fmapp2/res/drawable/stat_notify_fm.png
Binary files differ
diff --git a/fmapp2/res/drawable/station_border.xml b/fmapp2/res/drawable/station_border.xml
new file mode 100644
index 0000000..2aee777
--- /dev/null
+++ b/fmapp2/res/drawable/station_border.xml
@@ -0,0 +1,33 @@
+<!--
+ * Copyright (c) 2009, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <stroke android:width="1dp" android:color="#FFFFFFFF" />
+ <padding android:left="1dp" android:top="1dp"
+ android:right="1dp" android:bottom="1dp" />
+ <corners android:radius="1dp" />
+</shape>
diff --git a/fmapp2/res/layout-800x480/fmradio.xml b/fmapp2/res/layout-800x480/fmradio.xml
new file mode 100644
index 0000000..83b9f7c
--- /dev/null
+++ b/fmapp2/res/layout-800x480/fmradio.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fmradio"
+ android:background="@drawable/bg"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout android:id="@+id/layout_on_off"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="3dp"
+ android:layout_alignParentTop="true">
+
+ <ImageView android:id="@+id/btn_onoff"
+ android:src="@drawable/btn_onoff"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/count_down"
+ android:layout_toRightOf="@+id/btn_onoff"
+ android:textSize="14sp"
+ android:layout_marginLeft="5dp"
+ android:textStyle="bold"
+ android:visibility="invisible"
+ android:text="@string/default_sleep" />
+
+ <TextView android:id="@+id/prog_service_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_marginLeft="2dp"
+ android:layout_marginRight="2dp"
+ android:layout_toLeftOf="@+id/btn_silent"
+ android:layout_toRightOf="@+id/sleep_msg_tv"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:text="@string/prog_service_string"/>
+
+ <ImageView android:id="@+id/btn_silent"
+ android:src="@drawable/ic_silent_mode_off"
+ android:layout_toLeftOf="@+id/btn_speaker_earphone"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="10dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_height="wrap_content" />
+
+ <ImageView android:id="@+id/btn_speaker_earphone"
+ android:src="@drawable/btn_earphone"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </RelativeLayout>
+
+ <LinearLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="5dp"
+ android:layout_below="@+id/layout_on_off">
+
+ <!-- This included layout contains Station Information to display -->
+ <include layout="@layout/station_info" />
+
+ </LinearLayout>
+
+ <com.caf.fmradio.HorizontalNumberPicker
+ android:id="@+id/fm_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="200dp"
+ android:layout_marginBottom="50dp"
+ android:layout_below="@+id/stationinfo_layout"/>
+
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:layout_alignParentBottom="true"
+ android:scrollbars="none">
+
+ <LinearLayout android:id="@+id/favorite_list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ style="@style/ButtonStripLeft"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_2"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_3"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_4"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_5"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_6"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_7"
+ style="@style/ButtonStripRight"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+</RelativeLayout>
diff --git a/fmapp2/res/layout-800x480/fmtags.xml b/fmapp2/res/layout-800x480/fmtags.xml
new file mode 100644
index 0000000..352320f
--- /dev/null
+++ b/fmapp2/res/layout-800x480/fmtags.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent">
+
+ <ListView
+ android:id="@+id/TagListView"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textSize="30sp"/>
+
+</RelativeLayout>
diff --git a/fmapp2/res/layout-800x480/fmtransmitter.xml b/fmapp2/res/layout-800x480/fmtransmitter.xml
new file mode 100644
index 0000000..f12e808
--- /dev/null
+++ b/fmapp2/res/layout-800x480/fmtransmitter.xml
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fmtransmitter"
+ android:background="@drawable/bg"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <RelativeLayout android:id="@+id/on_off_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dp"
+ android:layout_marginBottom="2dp"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true" >
+
+ <ImageView android:id="@+id/btn_onoff"
+ android:src="@drawable/btn_onoff"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/transmit_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:layout_centerInParent="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:text="@string/transmit_msg_string"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_marginTop="150dp" >
+
+ <RelativeLayout android:id="@+id/frequency_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_alignParentLeft="true"
+ android:paddingLeft="15dp"
+ android:visibility="visible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/btn_arrow_left"/>
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textSize="25dp"
+ android:layout_centerInParent="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/frequency_string"/>
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:paddingRight="15dp"
+ android:layout_alignParentRight="true"
+ android:visibility="visible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/btn_arrow_right"/>
+
+ </RelativeLayout>
+
+ <!-- Station Radio Text information display -->
+ <RelativeLayout android:id="@+id/radio_text_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/frequency_layout">
+
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="20dp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="5dp"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="10dp"
+ android:layout_centerInParent="true"
+ android:text="@string/radio_text_string"/>
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+ <com.caf.fmradio.HorizontalNumberPicker
+ android:id="@+id/fm_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="200dp"
+ android:layout_marginBottom="50dp"
+ android:layout_below="@id/stationinfo_layout"/>
+
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:layout_alignParentBottom="true"
+ android:scrollbars="none">
+
+ <LinearLayout android:id="@+id/favorite_list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ style="@style/ButtonStripLeft"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_2"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_3"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_4"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_5"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_6"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_7"
+ style="@style/ButtonStripRight"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+</RelativeLayout>
diff --git a/fmapp2/res/layout-800x480/navigate.xml b/fmapp2/res/layout-800x480/navigate.xml
new file mode 100644
index 0000000..7e60d32
--- /dev/null
+++ b/fmapp2/res/layout-800x480/navigate.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <RelativeLayout android:id="@+id/message_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dp"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp">
+
+ <TextView android:id="@+id/record_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:textSize="28dp"
+ android:textStyle="bold"
+ android:gravity="fill"
+ android:text="@string/default_record" />
+
+ <Button android:id="@+id/btn_presets_list"
+ android:text="@string/default_preset_list_name"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textSize="24dp"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentTop="true"
+ android:layout_gravity="center"
+ android:layout_marginLeft="10dp"
+ android:layout_marginRight="10dp"
+ android:layout_marginBottom="10dp"
+ android:gravity="fill"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@+id/btn_presets_list"
+ android:textSize="28dp"
+ android:textStyle="bold"
+ android:gravity="fill"
+ android:text="@string/default_sleep" />
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/presets_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/message_row"
+ android:layout_centerInParent="true">
+
+ <!-- Upper Presets -->
+ <RelativeLayout android:id="@+id/presets_upper_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="top">
+
+ <Button android:id="@+id/presets_button_1"
+ android:text="@string/default_blank"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="24dp"
+ android:singleLine="true"
+ android:gravity="fill"
+ android:layout_width="120dp"
+ android:layout_height="65dp" />
+
+ <Button android:id="@+id/presets_button_2"
+ android:text="@string/default_blank"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="24dp"
+ android:singleLine="true"
+ android:layout_toRightOf="@id/presets_button_1"
+ android:layout_alignTop="@id/presets_button_1"
+ android:layout_alignBottom="@id/presets_button_1"
+ android:gravity="fill"
+ android:layout_width="120dp"
+ android:layout_height="65dp" />
+
+ <Button android:id="@+id/presets_button_3"
+ android:text="@string/default_blank"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="24dp"
+ android:singleLine="true"
+ android:layout_toRightOf="@id/presets_button_2"
+ android:layout_alignTop="@id/presets_button_2"
+ android:layout_alignBottom="@id/presets_button_2"
+ android:layout_width="120dp"
+ android:gravity="fill"
+ android:layout_height="65dp" />
+ </RelativeLayout>
+
+<!-- Lower Presets -->
+ <RelativeLayout android:id="@+id/presets_lower_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/presets_upper_layout"
+ android:layout_alignLeft="@id/presets_upper_layout"
+ android:layout_alignRight="@id/presets_upper_layout">
+
+ <Button android:id="@+id/presets_button_4"
+ android:text="@string/default_blank"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="24dp"
+ android:singleLine="true"
+ android:layout_width="120dp"
+ android:gravity="fill"
+ android:layout_alignLeft="@id/presets_button_1"
+ android:layout_alignRight="@id/presets_button_1"
+ android:layout_height="65dp" />
+
+ <Button android:id="@+id/presets_button_5"
+ android:text="@string/default_blank"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="24dp"
+ android:singleLine="true"
+ android:layout_toRightOf="@id/presets_button_4"
+ android:layout_alignTop="@id/presets_button_4"
+ android:layout_alignBottom="@id/presets_button_4"
+ android:layout_alignLeft="@id/presets_button_2"
+ android:layout_alignRight="@id/presets_button_2"
+ android:gravity="fill"
+ android:layout_width="120dp"
+ android:layout_height="65dp" />
+
+ <ImageButton android:id="@+id/btn_preset_page"
+ android:src="@drawable/ic_btn_forward"
+ android:layout_toRightOf="@id/presets_button_5"
+ android:layout_alignTop="@id/presets_button_5"
+ android:layout_alignBottom="@id/presets_button_5"
+ android:layout_alignLeft="@id/presets_button_3"
+ android:layout_alignRight="@id/presets_button_3"
+ android:gravity="fill"
+ android:layout_width="120dp"
+ android:layout_height="65dp" />
+ </RelativeLayout>
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/scanning_stoplayout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:gravity="center">
+
+ <Button android:id="@+id/btn_scanning_stop"
+ android:clickable="true"
+ android:text="@string/stop_scanning"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="32dip"
+ android:singleLine="true"
+ android:padding="20dip"
+ android:gravity="fill"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+ </RelativeLayout>
+
+</merge>
diff --git a/fmapp2/res/layout-800x480/station_info.xml b/fmapp2/res/layout-800x480/station_info.xml
new file mode 100644
index 0000000..87a8f7e
--- /dev/null
+++ b/fmapp2/res/layout-800x480/station_info.xml
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout android:id="@+id/station_info"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:orientation="vertical"
+ android:gravity="top">
+
+ <RelativeLayout android:id="@+id/top_most"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/signal_level"
+ android:src="@drawable/ic_signal_2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="5dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/record_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/recorder_start"
+ android:layout_marginLeft="20dp"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:clickable="true"
+ android:visibility="invisible"
+ android:text="@string/default_record"/>
+
+
+ <TextView android:id="@+id/stereo_text_tv"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="16dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@id/signal_level"
+ android:textSize="13sp"
+ android:text="@string/default_audio"/>
+
+ </RelativeLayout>
+
+ <LinearLayout android:id="@+id/station_frequency_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp"
+ android:layout_alignWithParentIfMissing="true"
+ android:gravity="center">
+
+ <TextView android:id="@+id/call_sign_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:paddingTop="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="2dp"
+ android:paddingLeft="2dp"
+ android:text="@string/station_id_string" />
+
+ <TextView android:id="@+id/pty_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:paddingTop="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="2dp"
+ android:paddingLeft="2dp"
+ android:text="@string/pty_string" />
+
+ </LinearLayout>
+
+ <RelativeLayout android:id="@+id/second_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="1dp"
+ android:layout_marginBottom="1dp"
+ android:layout_marginLeft="2dp"
+ android:layout_marginRight="2dp">
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:gravity="center"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_toLeftOf="@+id/btn_forward"
+ android:layout_toRightOf="@+id/btn_back"
+ android:text="@string/frequency_string"/>
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="15dp"
+ android:layout_alignParentLeft="true"
+ android:visibility="visible"
+ android:layout_alignWithParentIfMissing="true"
+ android:src="@drawable/btn_arrow_left"/>
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="15dp"
+ android:layout_alignParentRight="true"
+ android:visibility="visible"
+ android:layout_alignWithParentIfMissing="true"
+ android:src="@drawable/btn_arrow_right"/>
+
+ </RelativeLayout>
+
+
+ <!-- Station Radio Text information display -->
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="5dp"
+ android:paddingTop="20dp"
+ android:gravity="center_horizontal"
+ android:text="@string/radio_text_string"/>
+
+ <TextView android:id="@+id/eradio_text_tv"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="5dp"
+ android:gravity="center_horizontal"
+ android:text="@string/eradio_text_string"/>
+
+ </LinearLayout>
+
+</merge>
diff --git a/fmapp2/res/layout-land-finger-800x480/fmradio.xml b/fmapp2/res/layout-land-finger-800x480/fmradio.xml
new file mode 100644
index 0000000..4a4519a
--- /dev/null
+++ b/fmapp2/res/layout-land-finger-800x480/fmradio.xml
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fmradio"
+ android:background="@drawable/bg_landscape"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout android:id="@+id/layout_on_off"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true">
+
+ <ImageView android:id="@+id/btn_onoff"
+ android:src="@drawable/btn_onoff"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="15dp"
+ android:layout_width="wrap_content"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/record_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/recorder_start"
+ android:layout_marginLeft="25dp"
+ android:layout_toRightOf="@+id/btn_onoff"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:clickable="true"
+ android:focusable="true"
+ android:visibility="invisible"
+ android:text="@string/default_record"/>
+
+ <TextView android:id="@+id/prog_service_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_toRightOf="@+id/record_msg_tv"
+ android:layout_toLeftOf="@+id/btn_silent"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:text="@string/prog_service_string"/>
+
+ <ImageView android:id="@+id/btn_silent"
+ android:src="@drawable/ic_silent_mode_off"
+ android:layout_toLeftOf="@+id/btn_speaker_earphone"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="10dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_height="wrap_content" />
+
+ <ImageView android:id="@+id/btn_speaker_earphone"
+ android:src="@drawable/btn_earphone"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/layout_on_off">
+
+ <!-- This included layout contains Station Information to display -->
+ <include layout="@layout/station_info" />
+
+ </RelativeLayout>
+
+ <com.caf.fmradio.HorizontalNumberPicker
+ android:id="@+id/fm_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="175dp"
+ android:layout_marginBottom="50dp"
+ android:layout_below="@+id/stationinfo_layout"/>
+
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="50dp"
+ android:fillViewport="true"
+ android:layout_alignParentBottom="true"
+ android:scrollbars="none">
+
+ <LinearLayout android:id="@+id/favorite_list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ style="@style/ButtonStripLeft"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_2"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_3"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_4"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_5"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_6"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_7"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_weight="1"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+</RelativeLayout>
diff --git a/fmapp2/res/layout-land-finger-800x480/fmtransmitter.xml b/fmapp2/res/layout-land-finger-800x480/fmtransmitter.xml
new file mode 100644
index 0000000..d32ffbc
--- /dev/null
+++ b/fmapp2/res/layout-land-finger-800x480/fmtransmitter.xml
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fmtransmitter"
+ android:background="@drawable/bg_landscape"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <RelativeLayout android:id="@+id/on_off_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dp"
+ android:layout_marginBottom="2dp"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true" >
+
+ <ImageView android:id="@+id/btn_onoff"
+ android:src="@drawable/btn_onoff"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/transmit_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:layout_centerInParent="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:text="@string/transmit_msg_string"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_below="@id/on_off_layout"
+ android:layout_marginTop="20dp" >
+
+ <RelativeLayout android:id="@+id/frequency_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_alignParentLeft="true"
+ android:paddingLeft="15dp"
+ android:visibility="visible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/btn_arrow_left"/>
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textSize="25dp"
+ android:layout_centerInParent="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/frequency_string"/>
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:paddingRight="15dp"
+ android:layout_alignParentRight="true"
+ android:visibility="visible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/btn_arrow_right"/>
+
+ </RelativeLayout>
+
+ <!-- Station Radio Text information display -->
+ <RelativeLayout android:id="@+id/radio_text_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/frequency_layout">
+
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="20dp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="10dp"
+ android:layout_centerInParent="true"
+ android:text="@string/radio_text_string"/>
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+ <com.caf.fmradio.HorizontalNumberPicker
+ android:id="@+id/fm_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="167dp"
+ android:layout_marginBottom="55dp"
+ android:layout_below="@id/stationinfo_layout"/>
+
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="50dp"
+ android:layout_alignParentBottom="true"
+ android:scrollbars="none">
+
+ <LinearLayout android:id="@+id/favorite_list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ style="@style/ButtonStripLeft"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_2"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_3"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_4"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_5"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_6"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_7"
+ style="@style/ButtonStripRight"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+</RelativeLayout>
+
diff --git a/fmapp2/res/layout-land-finger-800x480/navigate.xml b/fmapp2/res/layout-land-finger-800x480/navigate.xml
new file mode 100644
index 0000000..269793b
--- /dev/null
+++ b/fmapp2/res/layout-land-finger-800x480/navigate.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="#ffffffff" />
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dip"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="5dip"
+ android:layout_marginBottom="2dip"
+ android:orientation="vertical"
+ android:gravity="bottom"
+ >
+ <RelativeLayout android:id="@+id/message_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ >
+
+ <TextView android:id="@+id/record_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_alignParentLeft="true"
+ android:textSize="14dip"
+ android:textStyle="bold"
+ android:text="@string/default_record"
+ />
+
+ <Button android:id="@+id/btn_presets_list"
+ android:text= "@string/default_preset_list_name"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:layout_gravity="center"
+ android:layout_centerHorizontal="true"
+ android:layout_marginLeft="10dip"
+ android:layout_marginRight="10dip"
+ android:layout_width="140dip"
+ android:layout_height="45dip" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@+id/btn_presets_list"
+ android:layout_gravity="center_vertical"
+ android:textSize="14dip"
+ android:textStyle="bold"
+ android:text="@string/default_sleep"
+ />
+ </RelativeLayout>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/presets_upper_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="center"
+ android:layout_marginBottom="5dip"
+ android:gravity="center_horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ android:text="@string/default_station"
+ android:layout_width="80dip"
+ android:layout_height="50dip" />
+
+ <Button android:id="@+id/presets_button_2"
+ android:text="@string/default_station"
+ android:layout_width="80dp"
+ android:layout_height="50dp" />
+
+ <Button android:id="@+id/presets_button_3"
+ android:text="@string/default_station"
+ android:layout_width="80dip"
+ android:layout_height="50dip"
+ android:visibility="visible" />
+
+ <Button android:id="@+id/presets_button_4"
+ android:text="@string/default_blank"
+ android:layout_width="80dip"
+ android:layout_height="50dip" />
+
+ <Button android:id="@+id/presets_button_5"
+ android:text="@string/default_blank"
+ android:layout_width="80dip"
+ android:layout_height="50dip" />
+
+ <ImageButton android:id="@+id/btn_preset_page"
+ android:src="@drawable/ic_btn_forward"
+ android:layout_marginRight="2dp"
+ android:layout_width="80dip"
+ android:layout_height="50dip"
+ android:visibility="visible" />
+ </LinearLayout>
+</merge>
diff --git a/fmapp2/res/layout-land-finger-800x480/station_info.xml b/fmapp2/res/layout-land-finger-800x480/station_info.xml
new file mode 100644
index 0000000..3f44573
--- /dev/null
+++ b/fmapp2/res/layout-land-finger-800x480/station_info.xml
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <RelativeLayout android:id="@+id/station_info"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <RelativeLayout android:id="@+id/top_most"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/signal_level"
+ android:src="@drawable/ic_signal_2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="5dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/count_down"
+ android:textSize="14sp"
+ android:layout_marginLeft="25dp"
+ android:textStyle="bold"
+ android:visibility="invisible"
+ android:text="@string/default_sleep" />
+
+ <TextView android:id="@+id/eradio_text_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="5dp"
+ android:paddingRight="5dp"
+ android:layout_toLeftOf="@+id/sleep_msg_tv"
+ android:layout_toRightOf="@+id/stereo_text_tv"
+ android:layout_centerInParent="true"
+ android:text="@string/eradio_text_string"/>
+
+ <TextView android:id="@+id/stereo_text_tv"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="16dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@id/signal_level"
+ android:textSize="13sp"
+ android:text="@string/default_audio"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/station_frequency_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="1dp"
+ android:paddingBottom="1dp"
+ android:layout_below="@+id/top_most"
+ android:layout_alignWithParentIfMissing="true">
+
+ <TextView android:id="@+id/call_sign_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:paddingLeft="16dp"
+ android:text="@string/station_id_string" />
+
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:layout_centerInParent="true"
+ android:text="@string/radio_text_string"/>
+
+ <TextView android:id="@+id/pty_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:paddingRight="16dp"
+ android:text="@string/pty_string" />
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/second_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="1dp"
+ android:layout_marginBottom="1dp"
+ android:layout_marginLeft="2dp"
+ android:layout_below="@+id/station_frequency_row"
+ android:layout_marginRight="2dp">
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:gravity="center"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_toLeftOf="@+id/btn_forward"
+ android:layout_toRightOf="@+id/btn_back"
+ android:text="@string/frequency_string"/>
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="15dp"
+ android:layout_alignParentLeft="true"
+ android:visibility="visible"
+ android:layout_alignWithParentIfMissing="true"
+ android:src="@drawable/btn_arrow_left"/>
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="15dp"
+ android:layout_alignParentRight="true"
+ android:visibility="visible"
+ android:layout_alignWithParentIfMissing="true"
+ android:src="@drawable/btn_arrow_right"/>
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+</merge>
diff --git a/fmapp2/res/layout-land-finger/fmradio.xml b/fmapp2/res/layout-land-finger/fmradio.xml
new file mode 100644
index 0000000..23d74fb
--- /dev/null
+++ b/fmapp2/res/layout-land-finger/fmradio.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fmradio"
+ android:background="@drawable/bg_landscape"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout android:id="@+id/layout_on_off"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true">
+
+ <ImageView android:id="@+id/btn_onoff"
+ android:src="@drawable/btn_onoff"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="15dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/record_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/recorder_start"
+ android:layout_marginLeft="2dp"
+ android:layout_toRightOf="@+id/btn_onoff"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:clickable="true"
+ android:focusable="true"
+ android:visibility="invisible"
+ android:text="@string/default_record"/>
+
+ <TextView android:id="@+id/prog_service_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_toRightOf="@+id/record_msg_tv"
+ android:layout_toLeftOf="@+id/btn_silent"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:text="@string/prog_service_string"/>
+
+ <ImageView android:id="@+id/btn_silent"
+ android:src="@drawable/ic_silent_mode_off"
+ android:layout_toLeftOf="@+id/btn_speaker_earphone"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="10dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_height="wrap_content" />
+
+ <ImageView android:id="@+id/btn_speaker_earphone"
+ android:src="@drawable/btn_earphone"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="1dp"
+ android:layout_below="@+id/layout_on_off">
+
+ <!-- This included layout contains Station Information to display -->
+ <include layout="@layout/station_info" />
+
+ </RelativeLayout>
+
+ <com.caf.fmradio.HorizontalNumberPicker
+ android:id="@+id/fm_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="175dp"
+ android:layout_marginBottom="50dp"
+ android:layout_below="@+id/stationinfo_layout"/>
+
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="50dp"
+ android:layout_alignParentBottom="true"
+ android:scrollbars="none">
+
+ <LinearLayout android:id="@+id/favorite_list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ style="@style/ButtonStripLeft"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_2"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_3"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_4"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_5"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_6"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_7"
+ style="@style/ButtonStripRight"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+</RelativeLayout>
diff --git a/fmapp2/res/layout-land-finger/fmtransmitter.xml b/fmapp2/res/layout-land-finger/fmtransmitter.xml
new file mode 100644
index 0000000..1e350b0
--- /dev/null
+++ b/fmapp2/res/layout-land-finger/fmtransmitter.xml
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fmtransmitter"
+ android:background="@drawable/bg_landscape"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <RelativeLayout android:id="@+id/on_off_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dp"
+ android:layout_marginBottom="2dp"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true" >
+
+ <ImageView android:id="@+id/btn_onoff"
+ android:src="@drawable/btn_onoff"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/transmit_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:layout_centerInParent="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:text="@string/transmit_msg_string"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_below="@id/on_off_layout"
+ android:layout_marginTop="20dp" >
+
+ <RelativeLayout android:id="@+id/frequency_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_alignParentLeft="true"
+ android:paddingLeft="15dp"
+ android:visibility="visible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/btn_arrow_left"/>
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textSize="25dp"
+ android:layout_centerInParent="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/frequency_string"/>
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:paddingRight="15dp"
+ android:layout_alignParentRight="true"
+ android:visibility="visible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/btn_arrow_right"/>
+
+ </RelativeLayout>
+
+ <!-- Station Radio Text information display -->
+ <RelativeLayout android:id="@+id/radio_text_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/frequency_layout">
+
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="20dp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="5dp"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="10dp"
+ android:layout_centerInParent="true"
+ android:text="@string/radio_text_string"/>
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+ <com.caf.fmradio.HorizontalNumberPicker
+ android:id="@+id/fm_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="175dp"
+ android:layout_marginBottom="50dp"
+ android:layout_below="@id/stationinfo_layout"/>
+
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:scrollbars="none">
+
+ <LinearLayout android:id="@+id/favorite_list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ style="@style/ButtonStripLeft"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_2"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_3"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_4"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_5"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_6"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_7"
+ style="@style/ButtonStripRight"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="12sp"
+ android:singleLine="true"
+ android:layout_width="85dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+</RelativeLayout>
+
diff --git a/fmapp2/res/layout-land-finger/navigate.xml b/fmapp2/res/layout-land-finger/navigate.xml
new file mode 100644
index 0000000..269793b
--- /dev/null
+++ b/fmapp2/res/layout-land-finger/navigate.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="#ffffffff" />
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dip"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="5dip"
+ android:layout_marginBottom="2dip"
+ android:orientation="vertical"
+ android:gravity="bottom"
+ >
+ <RelativeLayout android:id="@+id/message_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ >
+
+ <TextView android:id="@+id/record_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_alignParentLeft="true"
+ android:textSize="14dip"
+ android:textStyle="bold"
+ android:text="@string/default_record"
+ />
+
+ <Button android:id="@+id/btn_presets_list"
+ android:text= "@string/default_preset_list_name"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:layout_gravity="center"
+ android:layout_centerHorizontal="true"
+ android:layout_marginLeft="10dip"
+ android:layout_marginRight="10dip"
+ android:layout_width="140dip"
+ android:layout_height="45dip" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@+id/btn_presets_list"
+ android:layout_gravity="center_vertical"
+ android:textSize="14dip"
+ android:textStyle="bold"
+ android:text="@string/default_sleep"
+ />
+ </RelativeLayout>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/presets_upper_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="center"
+ android:layout_marginBottom="5dip"
+ android:gravity="center_horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ android:text="@string/default_station"
+ android:layout_width="80dip"
+ android:layout_height="50dip" />
+
+ <Button android:id="@+id/presets_button_2"
+ android:text="@string/default_station"
+ android:layout_width="80dp"
+ android:layout_height="50dp" />
+
+ <Button android:id="@+id/presets_button_3"
+ android:text="@string/default_station"
+ android:layout_width="80dip"
+ android:layout_height="50dip"
+ android:visibility="visible" />
+
+ <Button android:id="@+id/presets_button_4"
+ android:text="@string/default_blank"
+ android:layout_width="80dip"
+ android:layout_height="50dip" />
+
+ <Button android:id="@+id/presets_button_5"
+ android:text="@string/default_blank"
+ android:layout_width="80dip"
+ android:layout_height="50dip" />
+
+ <ImageButton android:id="@+id/btn_preset_page"
+ android:src="@drawable/ic_btn_forward"
+ android:layout_marginRight="2dp"
+ android:layout_width="80dip"
+ android:layout_height="50dip"
+ android:visibility="visible" />
+ </LinearLayout>
+</merge>
diff --git a/fmapp2/res/layout-land-finger/station_info.xml b/fmapp2/res/layout-land-finger/station_info.xml
new file mode 100644
index 0000000..9383894
--- /dev/null
+++ b/fmapp2/res/layout-land-finger/station_info.xml
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <RelativeLayout android:id="@+id/station_info"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <RelativeLayout android:id="@+id/top_most"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/signal_level"
+ android:src="@drawable/ic_signal_2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="5dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/count_down"
+ android:textSize="14sp"
+ android:layout_marginLeft="25dp"
+ android:textStyle="bold"
+ android:visibility="invisible"
+ android:text="@string/default_sleep" />
+
+ <TextView android:id="@+id/eradio_text_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="5dp"
+ android:paddingRight="5dp"
+ android:layout_toRightOf="@+id/sleep_msg_tv"
+ android:layout_toLeftOf="@+id/stereo_text_tv"
+ android:layout_centerInParent="true"
+ android:text="@string/eradio_text_string"/>
+
+ <TextView android:id="@+id/stereo_text_tv"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="16dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@id/signal_level"
+ android:textSize="13sp"
+ android:text="@string/default_audio"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/station_frequency_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="1dp"
+ android:paddingBottom="1dp"
+ android:layout_below="@+id/top_most"
+ android:layout_alignWithParentIfMissing="true">
+
+ <TextView android:id="@+id/call_sign_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:paddingLeft="16dp"
+ android:text="@string/station_id_string" />
+
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:layout_centerInParent="true"
+ android:text="@string/radio_text_string"/>
+
+ <TextView android:id="@+id/pty_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:paddingRight="16dp"
+ android:text="@string/pty_string" />
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/second_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="1dp"
+ android:layout_marginBottom="1dp"
+ android:layout_marginLeft="2dp"
+ android:layout_below="@+id/station_frequency_row"
+ android:layout_marginRight="2dp">
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:gravity="center"
+ android:layout_toLeftOf="@+id/btn_forward"
+ android:layout_toRightOf="@+id/btn_back"
+ android:text="@string/frequency_string"/>
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="15dp"
+ android:layout_alignParentLeft="true"
+ android:visibility="visible"
+ android:layout_alignWithParentIfMissing="true"
+ android:src="@drawable/btn_arrow_left"/>
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="15dp"
+ android:layout_alignParentRight="true"
+ android:visibility="visible"
+ android:layout_alignWithParentIfMissing="true"
+ android:src="@drawable/btn_arrow_right"/>
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+</merge>
diff --git a/fmapp2/res/layout/alert_dialog_list.xml b/fmapp2/res/layout/alert_dialog_list.xml
new file mode 100644
index 0000000..8b9ae52
--- /dev/null
+++ b/fmapp2/res/layout/alert_dialog_list.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ListView android:id="@+id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ </ListView>
+
+</LinearLayout>
diff --git a/fmapp2/res/layout/alert_dialog_text_entry.xml b/fmapp2/res/layout/alert_dialog_text_entry.xml
new file mode 100644
index 0000000..a54fc00
--- /dev/null
+++ b/fmapp2/res/layout/alert_dialog_text_entry.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/list_edit"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_marginLeft="20dp"
+ android:layout_marginRight="20dp"
+ android:scrollHorizontally="true"
+ android:hint="Enter a name"
+ android:singleLine="true"
+ android:inputType="text"
+ android:autoText="false"
+ android:capitalize="none"
+ android:gravity="fill_horizontal"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:saveEnabled="false"/>
+
+</LinearLayout>
diff --git a/fmapp2/res/layout/delete_dialog.xml b/fmapp2/res/layout/delete_dialog.xml
new file mode 100644
index 0000000..44df762
--- /dev/null
+++ b/fmapp2/res/layout/delete_dialog.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012, 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, Inc. 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:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/prompt"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:drawableLeft="@android:drawable/ic_dialog_alert"
+ android:drawablePadding="8dp">
+ </TextView>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="6dp">
+
+ <Button android:id="@+id/delete"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ android:text="@string/button_delete"
+ android:layout_gravity="center_horizontal"
+ android:layout_alignParentLeft="true" />
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ android:text="@string/button_cancel"
+ android:layout_alignParentRight="true" />
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/fmapp2/res/layout/fmradio.xml b/fmapp2/res/layout/fmradio.xml
new file mode 100644
index 0000000..4aef544
--- /dev/null
+++ b/fmapp2/res/layout/fmradio.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fmradio"
+ android:background="@drawable/bg"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout android:id="@+id/layout_on_off"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="3dp"
+ android:layout_alignParentTop="true">
+
+ <ImageView android:id="@+id/btn_onoff"
+ android:src="@drawable/btn_onoff"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/count_down"
+ android:layout_toRightOf="@+id/btn_onoff"
+ android:textSize="14sp"
+ android:layout_marginLeft="5dp"
+ android:textStyle="bold"
+ android:visibility="invisible"
+ android:text="@string/default_sleep" />
+
+ <TextView android:id="@+id/prog_service_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_marginLeft="2dp"
+ android:layout_marginRight="2dp"
+ android:layout_toLeftOf="@+id/btn_silent"
+ android:layout_toRightOf="@+id/sleep_msg_tv"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:text="@string/prog_service_string"/>
+
+ <ImageView android:id="@+id/btn_silent"
+ android:src="@drawable/ic_silent_mode_off"
+ android:layout_toLeftOf="@+id/btn_speaker_earphone"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="10dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_height="wrap_content" />
+
+ <ImageView android:id="@+id/btn_speaker_earphone"
+ android:src="@drawable/btn_earphone"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </RelativeLayout>
+
+ <LinearLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="5dp"
+ android:layout_below="@+id/layout_on_off">
+
+ <!-- This included layout contains Station Information to display -->
+ <include layout="@layout/station_info" />
+
+ </LinearLayout>
+
+ <com.caf.fmradio.HorizontalNumberPicker
+ android:id="@+id/fm_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="200dp"
+ android:layout_marginBottom="50dp"
+ android:layout_below="@+id/stationinfo_layout"/>
+
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:layout_alignParentBottom="true"
+ android:scrollbars="none">
+
+ <LinearLayout android:id="@+id/favorite_list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ style="@style/ButtonStripLeft"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_2"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_3"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_4"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_5"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_6"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_7"
+ style="@style/ButtonStripRight"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+</RelativeLayout>
diff --git a/fmapp2/res/layout/fmstats.xml b/fmapp2/res/layout/fmstats.xml
new file mode 100644
index 0000000..d9306d5
--- /dev/null
+++ b/fmapp2/res/layout/fmstats.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2011, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:scrollbars="vertical"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="75dp"
+ android:prompt="@string/test_prompt"
+ android:textSize="25sp"/>
+
+ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp">
+
+ <Button
+ android:id= "@+id/Runbutton"
+ android:text="@string/test_run"
+ android:layout_width="wrap_content"
+ android:layout_height="100dp"
+ android:textSize="25sp"
+ android:visibility="gone"/>
+
+ <ProgressBar android:id="@+id/progressbar"
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_toRightOf="@id/Runbutton"
+ android:visibility="gone"/>
+
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textSize="15sp"
+ android:text="@string/enter_rssi"
+ android:visibility="gone"/>
+
+ <EditText
+ android:id="@+id/txtbox1"
+ android:layout_width="100dp"
+ android:layout_height="50dp"
+ android:text="@string/type_rd"
+ android:textSize="15sp"
+ android:layout_alignParentRight="true"
+ android:visibility="gone"/>
+
+ <Button
+ android:id= "@+id/Setbutton"
+ android:text="@string/set_rmmsi_delta"
+ android:layout_width="wrap_content"
+ android:layout_height="100dp"
+ android:layout_below="@id/txtbox1"
+ android:textSize="15sp"
+ android:visibility="gone"/>
+
+ </RelativeLayout>
+
+ <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/maintable"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:scrollbars="vertical">
+
+ <TableRow
+ android:id="@+id/mainrow">
+ </TableRow>
+
+ </TableLayout>
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/fmapp2/res/layout/fmtags.xml b/fmapp2/res/layout/fmtags.xml
new file mode 100644
index 0000000..352320f
--- /dev/null
+++ b/fmapp2/res/layout/fmtags.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent">
+
+ <ListView
+ android:id="@+id/TagListView"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textSize="30sp"/>
+
+</RelativeLayout>
diff --git a/fmapp2/res/layout/fmtransmitter.xml b/fmapp2/res/layout/fmtransmitter.xml
new file mode 100644
index 0000000..b2ba8e5
--- /dev/null
+++ b/fmapp2/res/layout/fmtransmitter.xml
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fmtransmitter"
+ android:background="@drawable/bg"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <RelativeLayout android:id="@+id/on_off_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dp"
+ android:layout_marginBottom="2dp"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true" >
+
+ <ImageView android:id="@+id/btn_onoff"
+ android:src="@drawable/btn_onoff"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="15dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/transmit_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:layout_centerInParent="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:text="@string/transmit_msg_string"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout android:id="@+id/stationinfo_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_below="@id/on_off_layout"
+ android:layout_marginTop="150dp" >
+
+ <RelativeLayout android:id="@+id/frequency_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_alignParentLeft="true"
+ android:paddingLeft="15dp"
+ android:visibility="visible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/btn_arrow_left"/>
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textSize="25dp"
+ android:layout_centerInParent="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/frequency_string"/>
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:paddingRight="15dp"
+ android:layout_alignParentRight="true"
+ android:visibility="visible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/btn_arrow_right"/>
+
+ </RelativeLayout>
+
+ <!-- Station Radio Text information display -->
+ <RelativeLayout android:id="@+id/radio_text_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/frequency_layout">
+
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="20dp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="5dp"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="10dp"
+ android:layout_centerInParent="true"
+ android:text="@string/radio_text_string"/>
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+ <com.caf.fmradio.HorizontalNumberPicker
+ android:id="@+id/fm_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="200dp"
+ android:layout_marginBottom="50dp"
+ android:layout_below="@id/stationinfo_layout"/>
+
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="60dp"
+ android:layout_alignParentBottom="true"
+ android:scrollbars="none">
+
+ <LinearLayout android:id="@+id/favorite_list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ style="@style/ButtonStripLeft"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_2"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_3"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_4"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_5"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_6"
+ style="@style/ButtonStripMiddle"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/presets_button_7"
+ style="@style/ButtonStripRight"
+ android:text="@string/add_station"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:layout_width="80dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+</RelativeLayout>
+
diff --git a/fmapp2/res/layout/frequency_picker.xml b/fmapp2/res/layout/frequency_picker.xml
new file mode 100644
index 0000000..cef47f9
--- /dev/null
+++ b/fmapp2/res/layout/frequency_picker.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright (c) 2013, The Linux Foundation. All rights reserved.
+** Not a Contribution.
+**
+** Copyright 2007, 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <!-- MHz -->
+ <android.widget.NumberPicker
+ android:id="@+id/mhz"
+ android:layout_width="80dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="1dp"
+ android:layout_marginRight="1dp"
+ android:focusable="true"
+ android:focusableInTouchMode="true" />
+
+ <!-- KHz -->
+ <android.widget.NumberPicker
+ android:id="@+id/khz"
+ android:layout_width="80dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="1dp"
+ android:layout_marginRight="1dp"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ />
+</LinearLayout>
diff --git a/fmapp2/res/layout/frequency_picker_dialog.xml b/fmapp2/res/layout/frequency_picker_dialog.xml
new file mode 100644
index 0000000..3d95123
--- /dev/null
+++ b/fmapp2/res/layout/frequency_picker_dialog.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright (c) 2013, The Linux Foundation. All rights reserved.
+** Not a Contribution.
+**
+** Copyright 2007, 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.caf.utils.FrequencyPicker xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/frequencyPicker"
+ android:padding="5dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
diff --git a/fmapp2/res/layout/navigate.xml b/fmapp2/res/layout/navigate.xml
new file mode 100644
index 0000000..5e6b665
--- /dev/null
+++ b/fmapp2/res/layout/navigate.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <RelativeLayout android:id="@+id/message_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dip"
+ android:orientation="horizontal">
+
+ <TextView android:id="@+id/record_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:textSize="14dip"
+ android:textStyle="bold"
+ android:text="@string/default_record"/>
+
+ <Button android:id="@+id/btn_presets_list"
+ android:text="@string/default_preset_list_name"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentTop="true"
+ android:layout_gravity="center"
+ android:layout_marginLeft="10dip"
+ android:layout_marginRight="10dip"
+ android:layout_width="140dip"
+ android:layout_height="45dip" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@+id/btn_presets_list"
+ android:textSize="14dip"
+ android:textStyle="bold"
+ android:text="@string/default_sleep" />
+
+ </RelativeLayout>
+
+ <LinearLayout android:id="@+id/presets_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:orientation="vertical">
+
+ <!-- Upper Presets -->
+ <LinearLayout android:id="@+id/presets_upper_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="center"
+ android:layout_marginBottom="5dip"
+ android:gravity="center_horizontal">
+
+ <Button android:id="@+id/presets_button_1"
+ android:text="@string/default_station"
+ android:layout_marginLeft="10dip"
+ android:layout_width="90dip"
+ android:layout_height="55dip" />
+
+ <Button android:id="@+id/presets_button_2"
+ android:text="@string/default_station"
+ android:layout_marginLeft="10dip"
+ android:layout_marginRight="10dip"
+ android:layout_width="90dip"
+ android:layout_height="55dip" />
+
+ <Button android:id="@+id/presets_button_3"
+ android:text="@string/default_station"
+ android:layout_marginRight="10dip"
+ android:layout_width="90dip"
+ android:layout_height="55dip"
+ android:visibility="visible" />
+
+ </LinearLayout>
+
+ <!-- Lower Presets -->
+ <LinearLayout android:id="@+id/presets_lower_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="center"
+ android:layout_marginBottom="5dip"
+ android:gravity="center_horizontal">
+
+ <Button android:id="@+id/presets_button_4"
+ android:text="@string/default_blank"
+ android:layout_marginLeft="10dip"
+ android:layout_width="90dip"
+ android:layout_height="55dip" />
+
+ <Button android:id="@+id/presets_button_5"
+ android:text="@string/default_blank"
+ android:layout_marginLeft="10dip"
+ android:layout_marginRight="10dip"
+ android:layout_width="90dip"
+ android:layout_height="55dip" />
+
+ <ImageButton android:id="@+id/btn_preset_page"
+ android:src="@drawable/preset_page_button"
+ android:layout_marginRight="10dip"
+ android:layout_width="90dip"
+ android:layout_height="55dip"
+ android:visibility="visible" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/scanning_stoplayout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:gravity="center">
+
+ <Button android:id="@+id/btn_scanning_stop"
+ android:clickable="true"
+ android:text="@string/stop_scanning"
+ android:singleLine="true"
+ android:padding="20dip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</merge>
diff --git a/fmapp2/res/layout/rename_dialog.xml b/fmapp2/res/layout/rename_dialog.xml
new file mode 100644
index 0000000..2374c9c
--- /dev/null
+++ b/fmapp2/res/layout/rename_dialog.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012, 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, Inc. 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/rename"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="10dp">
+
+ <EditText android:id="@+id/name"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:inputType="text"
+ android:textColor="#FFF"
+ android:singleLine="true"
+ android:maxLength="16">
+ <requestFocus />
+ </EditText>
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="6dp" >
+
+ <Button android:id="@+id/save"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ android:text="@string/button_ok"
+ android:layout_alignParentLeft="true" />
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="120dp"
+ android:layout_height="wrap_content"
+ android:text="@string/button_cancel"
+ android:layout_alignParentRight="true" />
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/fmapp2/res/layout/station_info.xml b/fmapp2/res/layout/station_info.xml
new file mode 100644
index 0000000..fc7d62e
--- /dev/null
+++ b/fmapp2/res/layout/station_info.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout android:id="@+id/station_info"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:orientation="vertical"
+ android:gravity="top">
+
+ <RelativeLayout android:id="@+id/top_most"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/signal_level"
+ android:src="@drawable/ic_signal_2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="5dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/sleep_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/count_down"
+ android:textSize="14sp"
+ android:layout_marginLeft="25dp"
+ android:textStyle="bold"
+ android:visibility="invisible"
+ android:text="@string/default_sleep" />
+
+ <TextView android:id="@+id/record_msg_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/recorder_start"
+ android:layout_marginLeft="20dp"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:clickable="true"
+ android:visibility="invisible"
+ android:text="@string/default_record"/>
+
+ <TextView android:id="@+id/stereo_text_tv"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="16dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignBaseline="@id/signal_level"
+ android:textSize="13sp"
+ android:text="@string/default_audio"/>
+
+ </RelativeLayout>
+
+ <LinearLayout android:id="@+id/station_frequency_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp"
+ android:layout_alignWithParentIfMissing="true"
+ android:gravity="center">
+
+ <TextView android:id="@+id/call_sign_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:paddingTop="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="2dp"
+ android:paddingLeft="2dp"
+ android:text="@string/station_id_string" />
+
+ <TextView android:id="@+id/pty_tv"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:paddingTop="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="2dp"
+ android:paddingLeft="2dp"
+ android:text="@string/pty_string" />
+
+ </LinearLayout>
+
+ <RelativeLayout android:id="@+id/second_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dp"
+ android:layout_marginBottom="2dp"
+ android:layout_marginLeft="2dp"
+ android:layout_marginRight="2dp">
+
+ <TextView android:id="@+id/prog_frequency_tv"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:gravity="center"
+ android:layout_toLeftOf="@+id/btn_forward"
+ android:layout_toRightOf="@+id/btn_back"
+ android:text="@string/frequency_string"/>
+
+ <ImageView android:id="@+id/btn_back"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="15dp"
+ android:layout_alignParentLeft="true"
+ android:visibility="visible"
+ android:layout_alignWithParentIfMissing="true"
+ android:src="@drawable/btn_arrow_left"/>
+
+ <ImageView android:id="@+id/btn_forward"
+ android:clickable="true"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="15dp"
+ android:layout_alignParentRight="true"
+ android:visibility="visible"
+ android:layout_alignWithParentIfMissing="true"
+ android:src="@drawable/btn_arrow_right"/>
+
+ </RelativeLayout>
+
+ <!-- Station Radio Text information display -->
+ <TextView android:id="@+id/radio_text_tv"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="5dp"
+ android:paddingTop="20dp"
+ android:gravity="center_horizontal"
+ android:text="@string/radio_text_string"/>
+
+ <TextView android:id="@+id/eradio_text_tv"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="15sp"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:paddingBottom="5dp"
+ android:gravity="center_horizontal"
+ android:text="@string/eradio_text_string"/>
+
+ </LinearLayout>
+
+</merge>
diff --git a/fmapp2/res/layout/station_list.xml b/fmapp2/res/layout/station_list.xml
new file mode 100644
index 0000000..18d31f0
--- /dev/null
+++ b/fmapp2/res/layout/station_list.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012, 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, Inc. 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="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <!--Change layout_height value "wrap_content" to "fill_parent" can dispaly last item dividing line -->
+ <ListView android:id="@+id/station_list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+ </ListView>
+
+</LinearLayout>
diff --git a/fmapp2/res/layout/station_list_item.xml b/fmapp2/res/layout/station_list_item.xml
new file mode 100644
index 0000000..40c05d5
--- /dev/null
+++ b/fmapp2/res/layout/station_list_item.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012, 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, Inc. 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/item"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <TableLayout
+ android:id="@+id/TableLayout1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:stretchColumns="1">
+
+ <TableRow>
+ <TextView
+ android:id="@+id/fixName"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="30dp"
+ android:paddingTop="15dp"
+ android:text="@string/station_name" />
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever" />
+
+ </TableRow>
+ <TableRow >
+
+ <TextView
+ android:id="@+id/freq"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="30dp"
+ android:paddingBottom="15dp"
+ android:textSize="20sp"
+ android:layout_span="2" />
+
+ </TableRow>
+
+ </TableLayout>
+
+</LinearLayout>
diff --git a/fmapp2/res/layout/statusbar.xml b/fmapp2/res/layout/statusbar.xml
new file mode 100644
index 0000000..677ed74
--- /dev/null
+++ b/fmapp2/res/layout/statusbar.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012, 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, Inc. 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="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <ImageView android:id="@+id/icon"
+ android:layout_gravity="center_vertical"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </ImageView>
+
+ <TextView android:id="@+id/frequency"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse"
+ android:textColor="#FFFFFFFF"
+ android:focusable="true"
+ android:ellipsize="marquee"
+ android:singleLine="true"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/fmapp2/res/values-zh-rCN/arrays.xml b/fmapp2/res/values-zh-rCN/arrays.xml
new file mode 100644
index 0000000..d0a95a0
--- /dev/null
+++ b/fmapp2/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+-->
+<resources>
+
+ <string-array name="ster_mon_entries">
+ <item>立体声</item>
+ <item>单声道</item>
+ </string-array>
+
+ <string-array name="record_durations_entries">
+ <item>5 分钟</item>
+ <item>15 分钟</item>
+ <item>30 分钟</item>
+ <item>直至停止</item>
+ </string-array>
+
+ <string-array name="sleep_duration_values">
+ <item>15 分钟之后</item>
+ <item>30 分钟之后</item>
+ <item>45 分钟之后</item>
+ <item>1 小时之后</item>
+ </string-array>
+
+ <string-array name="search_category_rbds_entries">
+ <item>全部电台</item>
+ <item>成人点击</item>
+ <item>古典</item>
+ <item>经典摇滚</item>
+ <item>校园</item>
+ <item>乡村</item>
+ <item>紧急</item>
+ <item>紧急 测试</item>
+ <item>外语频道</item>
+ <item>信息频道</item>
+ <item>爵士</item>
+ <item>新闻</item>
+ <item>怀旧</item>
+ <item>老歌</item>
+ <item>个性</item>
+ <item>公共频道</item>
+ <item>宗教音乐</item>
+ <item>宗教交流</item>
+ <item>节奏布鲁斯</item>
+ <item>摇滚</item>
+ <item>轻音乐</item>
+ <item>轻摇滚</item>
+ <item>轻节奏布鲁斯</item>
+ <item>体育</item>
+ <item>交流</item>
+ <item>前40</item>
+ <item>天气</item>
+ </string-array>
+
+ <string-array name="search_category_rds_entries">
+ <item>全部电台</item>
+ <item>文化</item>
+ <item>新闻时事</item>
+ <item>儿童节目</item>
+ <item>乡村音乐</item>
+ <item>记录</item>
+ <item>戏剧</item>
+ <item>轻音乐</item>
+ <item>教育</item>
+ <item>紧急</item>
+ <item>紧急测试</item>
+ <item>经济</item>
+ <item>民族音乐</item>
+ <item>信息</item>
+ <item>爵士音乐</item>
+ <item>轻古典音乐</item>
+ <item>休闲</item>
+ <item>新闻</item>
+ <item>民族音乐</item>
+ <item>其他音乐</item>
+ <item>怀旧</item>
+ <item>听众来电</item>
+ <item>流行音乐</item>
+ <item>宗教</item>
+ <item>摇滚音乐</item>
+ <item>科学</item>
+ <item>古典</item>
+ <item>社会事务</item>
+ <item>体育</item>
+ <item>旅行</item>
+ <item>变奏</item>
+ <item>天气</item>
+ </string-array>
+
+
+ <string-array name="presetlist_edit_category">
+ <item>重命名</item>
+ <item>自动选择</item>
+ <item>删除</item>
+ </string-array>
+
+
+ <string-array name="regional_band_entries">
+ <item>北美</item>
+ <item>欧洲</item>
+ <item>日本</item>
+ <item>日本 (宽频)</item>
+ <item>澳大利亚</item>
+ <item>奥地利</item>
+ <item>比利时</item>
+ <item>巴西</item>
+ <item>中国</item>
+ <item>捷克</item>
+ <item>丹麦</item>
+ <item>芬兰</item>
+ <item>法国</item>
+ <item>德国</item>
+ <item>希腊</item>
+ <item>香港</item>
+ <item>印度</item>
+ <item>爱尔兰</item>
+ <item>意大利</item>
+ <item>韩国</item>
+ <item>墨西哥</item>
+ <item>荷兰</item>
+ <item>新西兰</item>
+ <item>挪威</item>
+ <item>波兰</item>
+ <item>葡萄牙</item>
+ <item>俄罗斯</item>
+ <item>新加坡</item>
+ <item>斯洛伐克</item>
+ <item>西班牙</item>
+ <item>瑞士</item>
+ <item>瑞典</item>
+ <item>台湾</item>
+ <item>土耳其</item>
+ <item>英国</item>
+ <item>美国</item>
+ </string-array>
+
+ <string-array name="regional_band_summary">
+ <item>北美 (87.5MHz - 108.0MHz ,步长 200 Khz)</item>
+ <item>欧洲 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>日本 (76.0MHz - 90.0MHz ,步长 100 Khz)</item>
+ <item>日本 (宽频) (90.0MHz - 108.0MHz ,步长 50 Khz)</item>
+ <item>澳大利亚 (87.7MHz - 108.0MHz , 步长 100 Khz)</item>
+ <item>奥地利 (87.5MHz - 108.0MHz , 步长 50 Khz)</item>
+ <item>比利时 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>巴西 (87.8MHz - 108.0MHz ,步长 200 Khz)</item>
+ <item>中国 (87.0MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>捷克 (87.5MHz - 108.0MHz , 步长 100 Khz)</item>
+ <item>丹麦 (87.5MHz - 108.0MHz ,步长 50 Khz)</item>
+ <item>芬兰 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>法国 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>德国 (87.5MHz - 108.0MHz ,步长 50 Khz)</item>
+ <item>希腊 (87.5MHz - 108.0MHz ,步长 50 Khz)</item>
+ <item>香港 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>印度 (91.0MHz - 106.4MHz ,步长 100 Khz)</item>
+ <item>爱尔兰 (87.5MHz - 108.0MHz ,步长 50 Khz)</item>
+ <item>意大利 (87.5MHz - 108.0MHz , 步长 50 Khz)</item>
+ <item>韩国 (87.5MHz - 108.0MHz ,步长 200 Khz)</item>
+ <item>墨西哥 (88.1MHz - 107.9MHz ,步长 200 Khz)</item>
+ <item>荷兰 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>新西兰 (88.0MHz - 107.0MHz ,步长 100 Khz)</item>
+ <item>挪威 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>波兰 (88.0MHz - 108.0MHz ,步长 50 Khz)</item>
+ <item>葡萄牙 (87.5MHz - 108.0MHz ,步长 50 Khz)</item>
+ <item>俄国 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>新加坡 (88.0MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>斯洛伐克 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>西班牙 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>瑞士 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>瑞典 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>台湾 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>土耳其 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>英国 (87.5MHz - 108.0MHz ,步长 100 Khz)</item>
+ <item>美国 (88.1MHz - 107.9MHz ,步长 200 Khz)</item>
+ </string-array>
+</resources>
diff --git a/fmapp2/res/values-zh-rCN/strings.xml b/fmapp2/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..cbea52b
--- /dev/null
+++ b/fmapp2/res/values-zh-rCN/strings.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2012-2013, 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.
+ */
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">FM 收音机</string>
+ <string name="menu_settings">设置</string>
+ <string name="menu_record_start">开始录音</string>
+ <string name="menu_record_stop">停止录音</string>
+ <string name="menu_all_channels">全部频道</string>
+ <string name="app_label_all_channels">全部频道</string>
+ <string name="button_ok">确定</string>
+ <string name="button_cancel">取消</string>
+ <string name="button_delete">删除</string>
+ <string name="error_sdcard_access">不能访问SD卡</string>
+ <string name="fm_service_started">FM 服务</string>
+ <string name="menu_sleep">自动关闭</string>
+ <string name="menu_sleep_cancel">取消自动关闭</string>
+ <string name="menu_scan_start">搜索</string>
+ <string name="button_text_stop">停止</string>
+ <string name="menu_scan_stop">停止搜索</string>
+ <string name="msg_seeking">搜索 FM 电台</string>
+ <string name="msg_scanning">搜索所有 FM 电台</string>
+ <string name="msg_scanning_pty">搜索 "<xliff:g id="pty_name">%1$s</xliff:g>"</string>
+ <string name="msg_noantenna">请插入耳机</string>
+ <!-- The messsage shown when FM radio was turned on, headset was plugged out -->
+ <string name="msg_headset_plug_out">收音机播放中拔出了耳机,收音机已经自动关闭。</string>
+ <string name="dialog_sleep_title">收音机自动关闭</string>
+ <string name="preset_name_dialog_title">输入名称 </string>
+ <string name="preset_name_title">重命名</string>
+ <string name="preset_search_title">搜索</string>
+ <string name="preset_search_name">搜索 "<xliff:g id="preset_name">%1$s</xliff:g>"</string>
+ <string name="preset_add">添加</string>
+ <string name="preset_replace">替换</string>
+ <string name="preset_tune">设定频率</string>
+ <string name="preset_rename">重命名</string>
+ <string name="preset_delete">删除</string>
+ <string name="sel_band_menu">选择地区</string>
+ <string name="settings_menu">设置</string>
+ <string name="regional_band">地区</string>
+ <string name="aud_output_mode">音频输出模式</string>
+ <string name="sel_audio_output">选择音频输出</string>
+ <string name="record_dur">录音时长</string>
+ <string name="sel_rec_dur">选择录音时长</string>
+ <string name="auto_select_af">备用频率</string>
+ <string name="auto_select_af_enabled">自动选择开启</string>
+ <string name="auto_select_af_disabled">自动选择关闭</string>
+ <string name="settings_revert_confirm_title">确定清除设置</string>
+ <string name="settings_revert_confirm_msg">将删除所有设置</string>
+ <string name="settings_back_summary">Return to previous view</string>
+ <string name="settings_revert_defaults_title">恢复出厂设置</string>
+ <string name="settings_revert_defaults_summary">清除所有设置</string>
+ <string name="audio_type_stereo">立体声</string>
+ <string name="audio_type_mono">单声道</string>
+ <string name="spaceIsLow_content">"USB 存储设备空间不足,请更改录音时长设置,或删除某些录音或者其他文件。</string>
+ <string name="alert_dialog_ok">确定</string>
+ <string name="alert_dialog_cancel">取消</string>
+ <string name="search_dialog_title">搜索电台</string>
+
+ <string name="fm_command_timeout_title">超时</string>
+ <string name="fm_tune_timeout_msg">打开FM电台无响应。
+ 如果问题持续, 请重新启动FM收音机。</string>
+ <string name="fm_command_failed_title">FM 操作失败</string>
+ <string name="fm_cmd_failed_msg">FM 操作失败。如果问题持续, 请重新启动FM收音机。
+ </string>
+ <string name="fm_cmd_failed_msg_hdmi">FM 操作失败。 不支持 FM 和 HDMI 并发 。
+ </string>
+ <string name="fm_cmd_failed_call_on">FM 操作失败。通话期间禁止FM音频 </string>
+
+ <string name="station_name">电台:</string>
+ <string name="station_exists">此频率已存在!</string>
+ <string name="stat_notif_frequency">FM 收音机 "<xliff:g id="frequency">%1$s</xliff:g>" MHz</string>
+ <string name="cannot_startup_during_call">通话中不能启动FM!</string>
+ <string name="cannot_startup_during_airplane">"飞行模式中不能启动FM!"</string>
+ <string name="station_list_delete_station">删除:<xliff:g id="name">%s</xliff:g></string>
+ <string name="station_list_delete_station_prompt">要删除 <xliff:g id="name">%s</xliff:g> 吗?</string>
+ <string name="sdcard_no_space_cannot_recording">SD卡空间不足,不能录音</string>
+ <string name="recording_stop_no_space">SD卡空间不足,录音停止</string>
+ <string name="station_name_empty">电台名字不能为空,请新重输入</string>
+ <string name="station_name_exist">已存在名为\"<xliff:g id="name">%s</xliff:g>\"的电台,请输入其他名称。</string>
+ <!-- the title of rename dialog lack of translation -->
+ <string name="dialog_presetlist_rename_title">"请输入名字"</string>
+
+ <!-- prompt message for stopping record -->
+ <string name="save_record_file">"录音已保存至 "<xliff:g id="record_file">%1$s</xliff:g>"</string>
+ <!-- all recordings will show up in the media database with this 'artist' name -->
+ <string name="audio_db_artist_name">我的 FM 录音</string>
+ <!-- all recordings will show up in the media database with this 'album' name -->
+ <string name="audio_db_album_name">FM 录音</string>
+ <!-- all recordings will show up in the media database in a playlist with this name -->
+ <string name="audio_db_playlist_name">FM 录音</string>
+</resources>
diff --git a/fmapp2/res/values-zh-rTW/strings.xml b/fmapp2/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..cf3d605
--- /dev/null
+++ b/fmapp2/res/values-zh-rTW/strings.xml
@@ -0,0 +1,101 @@
+<!--
+Copyright (c) 2012, 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">FM 收音機</string>
+ <string name="menu_settings">設定</string>
+ <string name="menu_record_start">開始錄音</string>
+ <string name="menu_record_stop">停止錄音</string>
+ <string name="menu_all_channels">全部頻道</string>
+ <string name="app_label_all_channels">全部頻道</string>
+ <string name="button_ok">確定</string>
+ <string name="button_cancel">取消</string>
+ <string name="button_delete">取消</string>
+ <string name="error_sdcard_access">不能訪問SD卡</string>
+ <string name="fm_service_started">FM 服務</string>
+ <string name="menu_sleep">自動關閉</string>
+ <string name="menu_sleep_cancel">取消自動關閉</string>
+ <string name="menu_scan_start">搜索</string>
+ <string name="button_text_stop">停止</string>
+ <string name="menu_scan_stop">停止搜索</string>
+ <string name="msg_seeking">搜索 FM 電台</string>
+ <string name="msg_scanning">搜索所有 FM 電台</string>
+ <string name="msg_scanning_pty">搜索 "<xliff:g id="pty_name">%1$s</xliff:g>"</string>
+ <string name="msg_noantenna">請插入耳機</string>
+ <string name="dialog_sleep_title">收音機自動關閉</string>
+ <string name="preset_name_dialog_title">輸入名稱 </string>
+ <string name="preset_name_title">重命名</string>
+ <string name="preset_search_title">搜索</string>
+ <string name="preset_search_name">搜索 "<xliff:g id="preset_name">%1$s</xliff:g>"</string>
+ <string name="preset_add">添加</string>
+ <string name="preset_replace">替換</string>
+ <string name="preset_tune">設定頻率</string>
+ <string name="preset_rename">重命名</string>
+ <string name="preset_delete">刪除</string>
+ <string name="sel_band_menu">選擇地區</string>
+ <string name="settings_menu">設定</string>
+ <string name="regional_band">地區</string>
+ <string name="aud_output_mode">音頻輸出模式</string>
+ <string name="sel_audio_output">選擇音頻輸出</string>
+ <string name="record_dur">錄音時長</string>
+ <string name="sel_rec_dur">選擇錄音時長</string>
+ <string name="auto_select_af">備用頻率</string>
+ <string name="auto_select_af_enabled">自動選擇開啟</string>
+ <string name="auto_select_af_disabled">自動選擇關閉</string>
+ <string name="settings_revert_confirm_title">確定清除設定</string>
+ <string name="settings_revert_confirm_msg">將刪除所有設定</string>
+ <string name="settings_revert_defaults_title">恢復出廠設定</string>
+ <string name="settings_revert_defaults_summary">清除所有設定</string>
+ <string name="audio_type_stereo">立體聲</string>
+ <string name="audio_type_mono">單聲道</string>
+ <string name="spaceIsLow_content">USB 存儲設備空間不足,請更改錄音時長設置,或刪除某些錄音或者其他檔案。</string>
+ <string name="alert_dialog_ok">確定</string>
+ <string name="alert_dialog_cancel">取消</string>
+ <string name="search_dialog_title">搜索電台</string>
+ <string name="fm_command_timeout_title">超時</string>
+ <string name="fm_tune_timeout_msg">打開FM 電台無響應。如果問題持續,請重新啟動FM 收音機。</string>
+ <string name="fm_command_failed_title">FM 操作失敗</string>
+ <string name="fm_cmd_failed_msg">FM 操作失敗。如果問題持續,請重新啟動FM 收音機。</string>
+ <string name="fm_cmd_failed_msg_hdmi">FM 操作失敗。不支持FM和 HDMI 並發。 </string>
+ <string name="fm_cmd_failed_call_on">FM 操作失敗。通話期間禁止FM音頻 </string>
+ <string name="station_name">電台:</string>
+ <string name="station_exists">此頻率已存在!</string>
+ <string name="stat_notif_frequency">FM 收音機 "<xliff:g id="frequency">%1$s</xliff:g>" MHz</string>
+ <string name="cannot_startup_during_call">通话中不能启动FM!</string>
+ <string name="cannot_startup_during_airplane">"通話中不能啟動FM!"</string>
+ <string name="station_list_delete_station">删除:<xliff:g id="name">%s</xliff:g></string>
+ <string name="station_list_delete_station_prompt">要删除 <xliff:g id="name">%s</xliff:g> 吗?</string>
+ <string name="sdcard_no_space_cannot_recording">SD卡空間不足,不能錄音</string>
+ <string name="recording_stop_no_space">SD卡空間不足,錄音停止</string>
+ <string name="station_name_empty">電台名字不能為空,請新重輸入</string>
+ <string name="station_name_exist">已存在名為\"<xliff:g id="name">%s</xliff:g>\"的電台,請輸入其他名稱。</string>
+ <string name="dialog_presetlist_rename_title">請輸入名字</string>
+
+ <!-- prompt message for stopping record -->
+ <string name="save_record_file">錄音已保存至 "<xliff:g id="record_file">%1$s</xliff:g>"</string>
+</resources>
diff --git a/fmapp2/res/values/arrays.xml b/fmapp2/res/values/arrays.xml
new file mode 100644
index 0000000..cb673c8
--- /dev/null
+++ b/fmapp2/res/values/arrays.xml
@@ -0,0 +1,354 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2011, 2012 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<resources>
+
+ <string-array name="ster_mon_entries">
+ <item>Stereo</item>
+ <item>Mono</item>
+ </string-array>
+
+ <string-array name="ster_mon_values">
+ <item>0</item>
+ <item>1</item>
+ </string-array>
+
+ <string-array name="record_durations_entries">
+ <item>5 minutes</item>
+ <item>15 minutes</item>
+ <item>30 minutes</item>
+ <item>Until stopped</item>
+ </string-array>
+
+
+ <string-array name="record_duration_values">
+ <item>5</item>
+ <item>15</item>
+ <item>30</item>
+ <item>-1</item>
+ </string-array>
+
+ <string-array name="sleep_duration_values">
+ <item>15 minutes</item>
+ <item>30 minutes</item>
+ <item>45 minutes</item>
+ <item>1 Hour</item>
+ </string-array>
+
+ <!-- This array should be in seconds and should match the values above -->
+ <string-array name="sleep_duration_values_seconds">
+ <item>900</item>
+ <item>1800</item>
+ <item>2700</item>
+ <item>3600</item>
+ </string-array>
+
+ <string-array name="search_category_rbds_entries">
+ <item>All Stations</item>
+ <item>Adult Hits</item>
+ <item>Classical</item>
+ <item>Classic Rock</item>
+ <item>College</item>
+ <item>Country</item>
+ <item>Emergency</item>
+ <item>Emergency Test</item>
+ <item>Foreign Language</item>
+ <item>Information</item>
+ <item>Jazz</item>
+ <item>News</item>
+ <item>Nostalgia</item>
+ <item>Oldies</item>
+ <item>Personality</item>
+ <item>Public</item>
+ <item>Religious Music</item>
+ <item>Religious Talk</item>
+ <item>Rhythm and Blues</item>
+ <item>Rock</item>
+ <item>Soft</item>
+ <item>Soft Rock</item>
+ <item>Soft Rhythm and Blues</item>
+ <item>Sports</item>
+ <item>Talk</item>
+ <item>Top 40</item>
+ <item>Weather</item>
+ </string-array>
+
+ <string-array name="search_category_rbds_values">
+ <item>0</item>
+ <item>7</item>
+ <item>15</item>
+ <item>6</item>
+ <item>23</item>
+ <item>10</item>
+ <item>31</item>
+ <item>30</item>
+ <item>18</item>
+ <item>2</item>
+ <item>14</item>
+ <item>1</item>
+ <item>13</item>
+ <item>11</item>
+ <item>21</item>
+ <item>22</item>
+ <item>19</item>
+ <item>20</item>
+ <item>16</item>
+ <item>5</item>
+ <item>12</item>
+ <item>8</item>
+ <item>17</item>
+ <item>3</item>
+ <item>4</item>
+ <item>9</item>
+ <item>29</item>
+ </string-array>
+
+
+ <string-array name="search_category_rds_entries">
+ <item>All Stations</item>
+ <item>Culture</item>
+ <item>Current Affairs</item>
+ <item>Children programs</item>
+ <item>Country Music</item>
+ <item>Documentary</item>
+ <item>Drama</item>
+ <item>Easy Listening Music</item>
+ <item>Education</item>
+ <item>Emergency</item>
+ <item>Emergency Test</item>
+ <item>Finance</item>
+ <item>Folk Music</item>
+ <item>Information</item>
+ <item>Jazz Music</item>
+ <item>Light classical</item>
+ <item>Leisure</item>
+ <item>News</item>
+ <item>National Music</item>
+ <item>Other Music</item>
+ <item>Oldies Music</item>
+ <item>Phone In</item>
+ <item>Pop Music</item>
+ <item>Religion</item>
+ <item>Rock Music</item>
+ <item>Science</item>
+ <item>Serious classical</item>
+ <item>Social Affairs</item>
+ <item>Sport</item>
+ <item>Travel</item>
+ <item>Varied</item>
+ <item>Weather</item>
+ </string-array>
+
+ <string-array name="search_category_rds_values">
+ <item>0</item>
+ <item>7</item>
+ <item>2</item>
+ <item>18</item>
+ <item>25</item>
+ <item>29</item>
+ <item>6</item>
+ <item>12</item>
+ <item>5</item>
+ <item>31</item>
+ <item>30</item>
+ <item>17</item>
+ <item>28</item>
+ <item>3</item>
+ <item>24</item>
+ <item>13</item>
+ <item>23</item>
+ <item>1</item>
+ <item>26</item>
+ <item>15</item>
+ <item>27</item>
+ <item>21</item>
+ <item>10</item>
+ <item>20</item>
+ <item>11</item>
+ <item>8</item>
+ <item>14</item>
+ <item>19</item>
+ <item>4</item>
+ <item>22</item>
+ <item>9</item>
+ <item>16</item>
+ </string-array>
+
+ <string-array name="presetlist_edit_category">
+ <item>Rename</item>
+ <item>Auto-Select</item>
+ <item>Delete</item>
+ </string-array>
+
+
+ <string-array name="regional_band_entries">
+ <item>North America</item>
+ <item>Europe</item>
+ <item>Japan</item>
+ <item>Japan (Wide)</item>
+ <item>Australia</item>
+ <item>Austria</item>
+ <item>Belgium</item>
+ <item>Brazil</item>
+ <item>China</item>
+ <item>Czech</item>
+ <item>Denmark</item>
+ <item>Finland</item>
+ <item>France</item>
+ <item>Germany</item>
+ <item>Greece</item>
+ <item>Hong Kong</item>
+ <item>India</item>
+ <item>Ireland</item>
+ <item>Italy</item>
+ <item>Korea</item>
+ <item>Mexico</item>
+ <item>Netherlands</item>
+ <item>New Zealand</item>
+ <item>Norway</item>
+ <item>Poland</item>
+ <item>Portugal</item>
+ <item>Russia</item>
+ <item>Singapore</item>
+ <item>Slovakia</item>
+ <item>Spain</item>
+ <item>Switzerland</item>
+ <item>Sweden</item>
+ <item>Taiwan</item>
+ <item>Turkey</item>
+ <item>United Kingdom </item>
+ <item>United States</item>
+ </string-array>
+
+ <string-array name="regional_band_values">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ <item>6</item>
+ <item>7</item>
+ <item>8</item>
+ <item>9</item>
+ <item>10</item>
+ <item>11</item>
+ <item>12</item>
+ <item>13</item>
+ <item>14</item>
+ <item>15</item>
+ <item>16</item>
+ <item>17</item>
+ <item>18</item>
+ <item>19</item>
+ <item>20</item>
+ <item>21</item>
+ <item>22</item>
+ <item>23</item>
+ <item>24</item>
+ <item>25</item>
+ <item>26</item>
+ <item>27</item>
+ <item>28</item>
+ <item>29</item>
+ <item>30</item>
+ <item>31</item>
+ <item>32</item>
+ <item>33</item>
+ <item>34</item>
+ <item>35</item>
+ </string-array>
+
+ <string-array name="regional_band_summary">
+ <item>North America (87.5MHz To 108.0MHz In 200 Khz Steps)</item>
+ <item>Europe (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Japan (76.0MHz To 90.0MHz In 100 Khz Steps)</item>
+ <item>Japan (Wide) (90.0MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Australia (87.7MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Austria (87.5MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Belgium (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Brazil (87.8MHz To 108.0MHz In 200 Khz Step)</item>
+ <item>China (87.0MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Czech (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Denmark (87.5MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Finland (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>France (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Germany (87.5MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Greece (87.5MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Hong Kong (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>India (91.0MHz To 106.4MHz In 100 Khz Steps)</item>
+ <item>Ireland (87.5MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Italy (87.5MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Korea (87.5MHz To 108.0MHz In 200 Khz Steps)</item>
+ <item>Mexico (88.1MHz To 107.9MHz In 200 Khz Steps)</item>
+ <item>Netherlands (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>New Zealand (88.0MHz To 107.0MHz In 100 Khz Steps)</item>
+ <item>Norway (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Poland (88.0MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Portugal (87.5MHz To 108.0MHz In 50 Khz Steps)</item>
+ <item>Russia (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Singapore (88.0MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Slovakia (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Spain (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Switzerland (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Sweden (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Taiwan (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>Turkey (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>United Kingdom (87.5MHz To 108.0MHz In 100 Khz Steps)</item>
+ <item>United States (88.1MHz To 107.9MHz In 200 Khz Steps)</item>
+ </string-array>
+
+ <string-array name="rf_cfg">
+ <item> Get Current Station Parameters</item>
+ <item> Get Station Parameters Repeat 20 times</item>
+ <item> Get Search station parameters</item>
+ <item> Get FM Band sweep parameters</item>
+ <item> Configure FM Search Parameters</item>
+ </string-array>
+
+ <string-array name="cfg_rf1">
+ <item> Set FM RSSI Delta</item>
+ <item> Set Signal Threshold</item>
+ <item> RF Statistics</item>
+ </string-array>
+
+ <string-array name="cfg_rf2">
+ <item> Set SINR Samples Count</item>
+ <item> Set SINR Threshold</item>
+ <item> Set IntfDetoutLow Threshold</item>
+ <item> Set IntfDetoutHigh Threshold</item>
+ <item> RF Statistics</item>
+ </string-array>
+
+ <string-array name="stats_options">
+ <item> Select the option</item>
+ <item> Configure FM Search parameters</item>
+ <item> RF Statistics</item>
+ </string-array>
+
+</resources>
diff --git a/fmapp2/res/values/strings.xml b/fmapp2/res/values/strings.xml
new file mode 100644
index 0000000..0c46991
--- /dev/null
+++ b/fmapp2/res/values/strings.xml
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">FM Radio New</string>
+ <string name="tx_app_name">FM Tx New</string>
+
+ <string name="default_sleep"></string>
+ <string name="menu_add_to_preset">Add to Presets</string>
+ <string name="menu_settings">Settings</string>
+ <string name="menu_record_start">Start Recording</string>
+ <string name="menu_record_stop">Stop Recording</string>
+ <string name="menu_all_channels">All Channels</string>
+ <string name="app_label_all_channels">All Channels</string>
+ <string name="button_ok">OK</string>
+ <string name="button_cancel">Cancel</string>
+ <string name="button_delete">Delete</string>
+ <string name="error_sdcard_access">Unable to access SD card</string>
+ <string name="fm_service_started">FM Service</string>
+ <string name="menu_sleep">Sleep</string>
+ <string name="menu_sleep_cancel">Cancel Sleep</string>
+ <string name="menu_wired_headset">Wired Headset</string>
+ <string name="menu_speaker_on">Enable Speaker</string>
+ <string name="menu_display_tags">Show Tags</string>
+ <string name="menu_speaker_off">Disable Speaker</string>
+ <string name="menu_scan_start">Scan</string>
+ <string name="menu_scan_stop">Stop Searching</string>
+ <string name="menu_stats">FM RF Stats</string>
+ <string name="button_text_stop">Stop</string>
+ <string name="msg_seeking">Searching FM Station</string>
+ <string name="msg_scanning">Scanning for all FM Stations</string>
+ <string name="msg_scanning_pty">Scanning for "<xliff:g id="pty_name">%1$s</xliff:g>"</string>
+ <string name="msg_search_title">FM - <xliff:g id="frequency_string">%1$s</xliff:g></string>
+ <string name="msg_searching">Searching for Strong Stations to create a Preset List </string>
+ <string name="msg_searching_title">Auto-Select Presets</string>
+ <string name="msg_weak_searching">Searching for frequencies to transmit</string>
+
+ <string name="msg_noantenna">Please plug in a Headset to use FM
+ Radio </string>
+ <string name="msg_headsetpluggedin">Disconnect all Headsets for using FM Tx</string>
+ <string name="msg_callactive">FM Tx is not supported when there is active voice call</string>
+ <string name="dialog_sleep_title">Select Auto-Off Sleep Time</string>
+ <string name="preset_select_name">Select "<xliff:g id="preset_name">%1$s</xliff:g>"</string>
+ <string name="presetlist_select_name">Select "<xliff:g id="presetlist_name">%1$s</xliff:g>"</string>
+ <string name="presetlist_add_new">Add New List</string>
+ <string name="dialog_presetlist_title">Preset Lists</string>
+ <string name="presetlist_prefix_name">FM - </string>
+ <string name="dialog_presetlist_rename_title">Enter a name </string>
+ <string name="dialog_preset_rename_title">Enter a name </string>
+ <string name="presetlist_delete_name">Delete "<xliff:g id="presetlist_name">%1$s</xliff:g>" and its Stations?</string>
+ <string name="presetlist_autoselect_title">Confirm Auto-Select</string>
+ <string name="presetlist_autoselect_name">Auto-Select will delete all the Presets in the list "<xliff:g id="presetlist_name">%1$s</xliff:g>", Continue?</string>
+ <string name="fmtx_autoselect_name">Auto-Select will delete all the Presets, Continue?</string>
+
+ <string name="enter_frequency">Tune to Station</string>
+ <string name="enter_new_frequency_title">Enter Frequency </string>
+
+ <string name="preset_now_playing">Now Playing </string>
+ <string name="preset_pref_title">"<xliff:g id="preset_name">%1$s</xliff:g>"
+ (<xliff:g id="preset_freq">%2$s</xliff:g>)</string>
+ <string name="preset_delete_title">Delete Preset</string>
+
+ <string name="preset_delete_name">Delete "<xliff:g id="preset_name">%1$s</xliff:g>"
+ from "<xliff:g id="presetlist_name">%2$s</xliff:g>"</string>
+
+ <string name="preset_confirm_delete_name">Delete "<xliff:g id="preset_name">%1$s</xliff:g>"
+ from "<xliff:g id="presetlist_name">%2$s</xliff:g>" ?</string>
+
+ <string name="preset_name_dialog_title">Enter a name </string>
+ <string name="preset_name_summary">"<xliff:g id="preset_name">%1$s</xliff:g>" </string>
+ <string name="preset_name_title">Rename</string>
+
+ <string name="preset_search_title">Search</string>
+ <string name="preset_search_name">Search for "<xliff:g id="preset_name">%1$s</xliff:g>"</string>
+
+ <string name="preset_preference_title">FM Stations in "<xliff:g id="preset_name">%1$s</xliff:g>" </string>
+
+ <string name="preset_empty_title">No Stations in the List</string>
+ <string name="preset_empty_add_summary">Add Stations</string>
+
+ <string name="preset_add">Add</string>
+ <string name="preset_replace">Replace</string>
+ <string name="preset_tune">Tune</string>
+ <string name="preset_rename">Rename</string>
+ <string name="preset_delete">Delete</string>
+ <string name="preset_search">Search for "<xliff:g id="preset_pi">%1$s</xliff:g>"
+ </string>
+
+ <string name="stat_notif_frequency">FM Radio Station "<xliff:g id="frequency">%1$s</xliff:g>" MHz</string>
+ <string name="stat_notif_tx_frequency">FM Transmitting at: "<xliff:g id="frequency">%1$s</xliff:g>"</string>
+
+
+ <string name="sel_band_menu">Select Regional Band</string>
+ <string name="test_menu">Select Test</string>
+
+ <string name="settings_menu">Settings</string>
+ <string name="regional_band">Regional Band</string>
+
+ <string name="aud_output_mode">Audio Output Mode</string>
+ <string name="sel_audio_output">Select Audio Output</string>
+
+ <string name="record_dur">Record Duration</string>
+ <string name="sel_rec_dur">Select Record Duration</string>
+
+ <string name="auto_select_af">Alternate Frequency</string>
+ <string name="auto_select_af_enabled">Auto Select Enabled</string>
+ <string name="auto_select_af_disabled">Auto Select Disabled</string>
+
+ <string name="fac_defaults">Revert to Factory Defaults</string>
+ <string name="revert_to_fac">Revert to Factory Defaults</string>
+ <string name="settings_back">Back</string>
+
+ <string name="settings_revert_confirm_title">Confirm Reset Settings</string>
+ <string name="settings_revert_confirm_msg">This will delete all settings including Presets</string>
+
+ <string name="settings_back_summary">Return to previous view</string>
+ <string name="settings_revert_defaults_title">Revert to Factory Defaults</string>
+ <string name="settings_revert_defaults_summary">Reset all settings</string>
+
+ <string name="audio_type_stereo">Stereo</string>
+ <string name="audio_type_mono">Mono</string>
+
+ <string name="default_band">United States</string>
+ <string name="default_audio">Stereo</string>
+ <string name="default_record_duration">5 minutes</string>
+ <string name="factory_reset_dialog_title">Confirm Reset</string>
+ <string name="factory_reset_dialog_message">This will delete all settings including Presets</string>
+ <string name="station_move">Move</string>
+ <string name="station_edit">Edit</string>
+ <!-- alert to the user that USB storage must be available before using FM recording [CHAR LIMIT=NONE] -->
+ <string name="no_storage">Mount SD card before start recording.</string>
+ <!-- alert to the user that the USB storage is being disk-checked [CHAR LIMIT=30] -->
+ <string name="preparing_sd">Preparing SD card</string>
+ <!-- alert to the user that the FM fails to read or write the USB storage. [CHAR LIMIT=NONE] -->
+ <string name="access_sd_fail">Couldn\'t access SD card.</string>
+ <!-- Low-memory dialog message [CHAR LIMT=NONE] -->
+ <string name="spaceIsLow_content">Your SD card storage is running out of space. Change the quality setting or delete some images or other files.</string>
+ <!-- The messsage shown when FM record reaches size limit. -->
+ <string name="FMRecording_reach_size_limit">Size limit reached.</string>
+
+ <string name="delete_station_title">Delete</string>
+ <string name="find_af_station">Find</string>
+ <string name="menu_add_list">Add List</string>
+ <string name="menu_rename_list">Rename List</string>
+ <string name="menu_reorder_list">Reorder List</string>
+ <string name="menu_scan_for_preset">Scan for Presets</string>
+ <string name="menu_delete_list">Delete List</string>
+ <string name="alert_dialog_ok">Ok</string>
+ <string name="alert_dialog_cancel">Cancel</string>
+
+ <string name="search_dialog_title">Scan Stations</string>
+
+ <string name="search_category_key">search_category_type</string>
+ <string name="search_category">Select Search Type</string>
+ <string name="search_category_title">Search Type</string>
+ <string name="default_search_category">Seek</string>
+ <string name="search_category_tune">Tune</string>
+
+ <string name="search_category_type_key">search_category_value_type</string>
+ <string name="search_category_type">Select Search by Program Type</string>
+ <string name="search_category_type_title">Search by Program Type</string>
+ <string name="default_search_category_type">All Stations</string>
+
+ <string name="presets_back">Back</string>
+ <string name="presets_back_summary">Return to previous view</string>
+
+ <string name="nav_mode_presets">Presets</string>
+ <string name="nav_mode_stations">Stations</string>
+
+ <string name="prog_service_string"></string>
+ <string name="radio_text_string">This is the long Radio Text String Message that needs to scroll</string>
+ <string name="eradio_text_string">Extended Radio Text Message</string>
+ <string name="pty_string"></string>
+ <string name="frequency_string"></string>
+ <string name="station_id_string"></string>
+
+
+ <string name="fm_command_timeout_title">Timeout</string>
+ <string name="fm_tune_timeout_msg">No response received to Tune FM
+ Station. If problem persists, please turn off FM and turn it back
+ on.</string>
+
+ <string name="fm_command_failed_title">FM Operation failed</string>
+ <string name="fm_cmd_failed_msg">FM operation failed. If problem
+ persists, please turn off FM and turn it back on.
+ </string>
+ <string name="fm_cmd_failed_msg_hdmi">FM operation failed. FM and HDMI concurrecny is not supported.
+ </string>
+ <string name="fm_cmd_failed_call_on">FM operation failed. FM audio is not allowed during call. </string>
+
+ <skip/>
+ <!-- Do not translate. Duration format. -->
+ <string name="durationformat"><xliff:g id="format">%2$d:%5$02d</xliff:g></string>
+
+ <string name="default_record"></string>
+ <string name="default_preset_list_name"> FM List </string>
+ <string name="default_station"> FM </string>
+ <string name="default_blank"> </string>
+ <string name="add_station">+</string>
+ <string name="stop_scanning">Stop Scanning</string>
+ <string name="transmit_msg_string">Transmitting at:</string>
+ <!-- the name under which recordings will be visible in the media database is formatted like this -->
+ <string name="audio_db_title_format"><xliff:g id="format">yyyy-MM-dd HH:mm:ss</xliff:g></string>
+ <!-- all recordings will show up in the media database with this 'artist' name -->
+ <string name="audio_db_artist_name">My FM recordings</string>
+ <!-- all recordings will show up in the media database with this 'album' name -->
+ <string name="audio_db_album_name">FM recordings</string>
+ <!-- all recordings will show up in the media database in a playlist with this name -->
+ <string name="audio_db_playlist_name">FM recordings</string>
+ <string name="test_prompt">Choose a test</string>
+ <string name="test_run">Run Test</string>
+ <string name="set_rmmsi_delta">Configure RMSSI Delta</string>
+ <string name="set_sigth">Configure Signal Th</string>
+ <string name="type_rd"></string>
+ <string name="srch_params">Configure RMSSI Delta</string>
+ <string name="srch_params_1">Configure Signal Th </string>
+ <string name="enter_rssi">Enter RMSSI Delta</string>
+ <string name="enter_sigth">Enter Signal Th</string>
+ <string name="enter_SinrSmplsCnt">Enter SINR Samples Count</string>
+ <string name="set_SinrSmplsCnt">Configure SINR Samples</string>
+ <string name="enter_SinrTh">Enter SINR Threshold</string>
+ <string name="set_SinrTh">Configure SINR Threshold</string>
+ <string name="enter_IntfLowTh">Enter Intf Det Low Threshold</string>
+ <string name="set_IntfLowTh">Config Intf Det Low Threshold</string>
+ <string name="enter_IntfHighTh">Enter Intf Det High Threshold</string>
+ <string name="set_IntfHighTh">Config Intf Det High Threshold</string>
+
+ <string name="station_name">Station:</string>
+ <string name="station_exists">This frequency already exists!</string>
+ <string name="station_list_delete_station">Delete:<xliff:g id="name">%s</xliff:g></string>
+ <string name="station_list_delete_station_prompt">Are you sure to delete <xliff:g id="name">%s</xliff:g> .</string>
+ <string name="station_name_empty">Station name is empty, please input again.</string>
+ <string name="station_name_exist">There is already a station named \"<xliff:g id="name">%s</xliff:g>\". Please input a different name.</string>
+
+ <string name="rt_plus_tags">Tags</string>
+
+</resources>
diff --git a/fmapp2/res/values/styles.xml b/fmapp2/res/values/styles.xml
new file mode 100644
index 0000000..e9c9bcf
--- /dev/null
+++ b/fmapp2/res/values/styles.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009, 2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.
+-->
+<resources>
+ <style name="FMStationSeekBar">
+ <item name="android:indeterminateOnly">false</item>
+ <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
+ <item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
+ <item name="android:minHeight">20dp</item>
+ <item name="android:maxHeight">20dp</item>
+ <item name="android:thumbOffset">8dp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+ <style name="ButtonStripLeft">
+ <item name="android:gravity">center</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:background">@drawable/btn_strip_trans_left</item>
+ </style>
+
+ <style name="ButtonStripMiddle">
+ <item name="android:gravity">center</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:background">@drawable/btn_strip_trans_middle</item>
+ </style>
+
+ <style name="ButtonStripRight">
+ <item name="android:gravity">center</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:background">@drawable/btn_strip_trans_right</item>
+ </style>
+
+</resources>
diff --git a/fmapp2/src/com/caf/fmradio/FMAdapterApp.java b/fmapp2/src/com/caf/fmradio/FMAdapterApp.java
new file mode 100644
index 0000000..dbc17c5
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FMAdapterApp.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2013, 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");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @hide
+ */
+
+package com.caf.fmradio;
+
+import android.app.Application;
+import android.util.Log;
+
+public class FMAdapterApp extends Application {
+ private static final String TAG = "FMAdapterApp";
+ private static final boolean DBG = true;
+ //For Debugging only
+ private static int sRefCount=0;
+
+ static {
+ if (DBG) Log.d(TAG,"Loading FM-JNI Library");
+ System.loadLibrary("qcomfm_jni");
+ }
+
+ public FMAdapterApp() {
+ super();
+ if (DBG) {
+ synchronized (FMAdapterApp.class) {
+ sRefCount++;
+ Log.d(TAG, "REFCOUNT: Constructed "+ this + " Instance Count = " + sRefCount);
+ }
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ @Override
+ protected void finalize() {
+ if (DBG) {
+ synchronized (FMAdapterApp.class) {
+ sRefCount--;
+ Log.d(TAG, "REFCOUNT: Finalized: " + this +", Instance Count = " + sRefCount);
+ }
+ }
+ }
+}
diff --git a/fmapp2/src/com/caf/fmradio/FMMediaButtonIntentReceiver.java b/fmapp2/src/com/caf/fmradio/FMMediaButtonIntentReceiver.java
new file mode 100644
index 0000000..9bc4d77
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FMMediaButtonIntentReceiver.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2012-2013, 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.caf.fmradio;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.app.IntentService;
+import android.content.BroadcastReceiver;
+import android.content.pm.PackageManager;
+import android.content.Context;
+import android.content.ComponentName;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.os.Bundle;
+import java.lang.Object;
+
+public class FMMediaButtonIntentReceiver extends BroadcastReceiver {
+
+private static final String TAG = "FMMediaButtonIntentReceiver";
+public static final String FM_MEDIA_BUTTON = "com.caf.fmradio.action.MEDIA_BUTTON";
+public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if ((action != null) && action.equals("android.intent.action.MEDIA_BUTTON")) {
+ KeyEvent event = (KeyEvent)
+ intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+
+ if (event == null) {
+ return;
+ }
+ int keycode = event.getKeyCode();
+ int key_action = event.getAction();
+ if (((KeyEvent.KEYCODE_HEADSETHOOK == keycode) &&
+ (key_action == KeyEvent.ACTION_DOWN)) ||
+ (KeyEvent.KEYCODE_MEDIA_PAUSE == keycode) ||
+ (KeyEvent.KEYCODE_MEDIA_PLAY == keycode)) {
+
+ Log.d(TAG, "ACTION_MEDIA_BUTTON intent received for ACTION_DOWN");
+ Intent i = new Intent(FM_MEDIA_BUTTON);
+ i.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ context.sendBroadcast(i);
+ }
+ }
+ }
+}
diff --git a/fmapp2/src/com/caf/fmradio/FMRadio.java b/fmapp2/src/com/caf/fmradio/FMRadio.java
new file mode 100644
index 0000000..caa9bb2
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FMRadio.java
@@ -0,0 +1,3037 @@
+/*
+ * Copyright (c) 2009-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnKeyListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.media.AudioSystem;
+import android.media.AudioManager;
+import android.media.MediaRecorder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.view.KeyEvent;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.text.TextUtils;
+
+import java.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.ArrayList;
+
+import com.caf.utils.FrequencyPicker;
+import com.caf.utils.FrequencyPickerDialog;
+import android.content.ServiceConnection;
+import android.media.MediaRecorder;
+
+import qcom.fmradio.FmConfig;
+import android.os.ServiceManager;
+
+import com.caf.fmradio.HorizontalNumberPicker.OnScrollFinishListener;
+import com.caf.fmradio.HorizontalNumberPicker.OnValueChangeListener;
+import com.caf.fmradio.HorizontalNumberPicker.Scale;
+
+import android.content.SharedPreferences;
+
+public class FMRadio extends Activity
+{
+ public static final String LOGTAG = "FMRadio";
+
+ public static final boolean RECORDING_ENABLE = true;
+ MediaRecorder mRecorder = null;
+
+ /* menu Identifiers */
+ private static final int MENU_SCAN_START = Menu.FIRST + 2;
+ private static final int MENU_SCAN_STOP = Menu.FIRST + 3;
+ private static final int MENU_RECORD_START = Menu.FIRST + 4;
+ private static final int MENU_RECORD_STOP = Menu.FIRST + 5;
+ private static final int MENU_SLEEP = Menu.FIRST + 6;
+ private static final int MENU_SLEEP_CANCEL = Menu.FIRST + 7;
+ private static final int MENU_SETTINGS = Menu.FIRST + 8;
+ private static final int MENU_SPEAKER = Menu.FIRST + 9;
+ private static final int MENU_TAGS = Menu.FIRST + 10;
+ private static final int MENU_STAT_TEST = Menu.FIRST + 11;
+ private static final int MENU_STATION_LIST = Menu.FIRST + 12;
+ /* Dialog Identifiers */
+ private static final int DIALOG_SEARCH = 1;
+ private static final int DIALOG_SLEEP = 2;
+ private static final int DIALOG_SELECT_PRESET_LIST = 3;
+ private static final int DIALOG_PRESETS_LIST = 4;
+ private static final int DIALOG_PRESET_LIST_RENAME = 5;
+ private static final int DIALOG_PRESET_LIST_DELETE = 6;
+ private static final int DIALOG_PRESET_LIST_AUTO_SET = 7;
+ private static final int DIALOG_PICK_FREQUENCY = 8;
+ private static final int DIALOG_PROGRESS_PROGRESS = 9;
+ private static final int DIALOG_PRESET_OPTIONS = 10;
+ private static final int DIALOG_PRESET_RENAME = 11;
+ private static final int DIALOG_CMD_TIMEOUT = 12;
+ private static final int DIALOG_CMD_FAILED = 13;
+ private static final int DIALOG_CMD_FAILED_HDMI_ON = 14;
+ private static final int DIALOG_CMD_FAILED_CALL_ON = 15;
+ private static final int DIALOG_TAGS = 16;
+
+ /* Activity Return ResultIdentifiers */
+ private static final int ACTIVITY_RESULT_SETTINGS = 1;
+
+ /* Activity Return ResultIdentifiers */
+ private static final int MAX_PRESETS_PER_PAGE = 7;
+
+
+ /* Station's Audio is Stereo */
+ private static final int FMRADIO_UI_STATION_AUDIO_STEREO = 1;
+ /* Station's Audio is Mono */
+ private static final int FMRADIO_UI_STATION_AUDIO_MONO = 2;
+
+ /* The duration during which the "Sleep: xx:xx" string will be toggling
+ */
+ private static final int SLEEP_TOGGLE_SECONDS = 60;
+
+ /* The number of Preset Stations to create.
+ * The hardware supports a maximum of 12.
+ */
+ private static final int NUM_AUTO_PRESETS_SEARCH= 12;
+ /*
+ * Command time out: For asynchonous operations, if no response
+ * is received with int this duration, a timeout msg will be displayed.
+ */
+ private static final int CMD_TIMEOUT_DELAY_MS = 5000;
+ private static final int MSG_CMD_TIMEOUT = 101;
+
+ private static final int CMD_NONE = 0;
+ private static final int CMD_TUNE = 1;
+ private static final int CMD_FMON = 2;
+ private static final int CMD_FMOFF = 3;
+ private static final int CMD_FMCONFIGURE = 4;
+ private static final int CMD_MUTE = 5;
+ private static final int CMD_SEEK = 6;
+ private static final int CMD_SCAN = 7;
+ private static final int CMD_SEEKPI = 8;
+ private static final int CMD_SEARCHLIST = 9;
+ private static final int CMD_CANCELSEARCH = 10;
+ private static final int CMD_SET_POWER_MODE = 11;
+ private static final int CMD_SET_AUDIO_MODE = 12;
+ private static final int CMD_SET_AUTOAF = 13;
+ private static final int CMD_GET_INTERNALANTENNA_MODE = 14;
+
+ private static final int PRESETS_OPTIONS_TUNE = 0;
+ private static final int PRESETS_OPTIONS_REPLACE = 1;
+ private static final int PRESETS_OPTIONS_RENAME = 2;
+ private static final int PRESETS_OPTIONS_DELETE = 3;
+ private static final int PRESETS_OPTIONS_SEARCHPI = 4;
+
+ public static final String SCAN_STATION_PREFS_NAME = "scan_station_list";
+ public static final String NUM_OF_STATIONS= "number_of_stations";
+ public static final String STATION_NAME = "name_of_station";
+ public static final String STATION_FREQUENCY = "frequency_of_station";
+
+ private static IFMRadioService mService = null;
+ private static FmSharedPreferences mPrefs;
+
+ /* Button Resources */
+ private ImageView mOnOffButton;
+ private ImageView mMuteButton;
+ private ImageView mSpeakerButton;
+ /* Button to navigate Preset pages */
+ private ImageButton mPresetPageButton;
+ /* 6 Preset Buttons */
+ private Button[] mPresetButtons = {null, null, null, null, null, null, null};
+ private Button mPresetListButton;
+ // private ImageButton mSearchButton;
+ private ImageView mForwardButton;
+ private ImageView mBackButton;
+
+ /* Top row in the station info layout */
+ private ImageView mRSSI;
+ private TextView mProgramServiceTV;
+ private TextView mStereoTV;
+
+ /* Middle row in the station info layout */
+ private TextView mTuneStationFrequencyTV;
+ private TextView mStationCallSignTV;
+ private TextView mProgramTypeTV;
+
+ /* Bottom row in the station info layout */
+ private TextView mRadioTextTV;
+ private TextView mERadioTextTV;
+
+ /* Sleep and Recording Messages */
+ private TextView mSleepMsgTV;
+ private TextView mRecordingMsgTV;
+
+ private double mOutputFreq;
+ private int mPresetPageNumber = 0;
+ private int mStereo = -1;
+
+ // default audio device - speaker
+ private static int mAudioRoute = FMRadioService.RADIO_AUDIO_DEVICE_WIRED_HEADSET;
+ private static boolean mFMStats = false;
+
+
+ /* Current Status Indicators */
+ private static boolean mRecording = false;
+ private static boolean mIsScaning = false;
+ private static boolean mIsSeeking = false;
+ private static boolean mIsSearching = false;
+ private static int mScanPty = 0;
+
+ private Animation mAnimation = null;
+ private ScrollerText mRadioTextScroller = null;
+ private ScrollerText mERadioTextScroller = null;
+
+ private static PresetStation mTunedStation = new PresetStation("", 102100);
+ private PresetStation mPresetButtonStation = null;
+
+ /* Radio Vars */
+ final Handler mHandler = new Handler();
+ /* Search Progress Dialog */
+ private ProgressDialog mProgressDialog = null;
+
+ /* Asynchronous command active */
+ private static int mCommandActive = 0;
+
+ /* Command that failed (Sycnhronous or Asynchronous) */
+ private static int mCommandFailed = 0;
+
+ private HorizontalNumberPicker mPicker;
+ private int mFrequency;
+
+ /** Index of arrays.xml key word "search_category_rbds_entries or search_category_rds_entries resources*/
+ private int mItemsIndex = -1;
+ private static int mDisplayWidth;
+ private static final int TEXTSIZE_PARAMETER_FOR_NUMBER_PICKER = 20;
+ private static final int FREQUENCY_STEP_SMALL = 50;
+ private static final int FREQUENCY_STEP_MEDIUM = 100;
+ private static final int FREQUENCY_STEP_LARGE = 200;
+ public static boolean mUpdatePickerValue = false;
+
+ private LoadedDataAndState SavedDataAndState = null;
+
+ /** fm stats property string */
+ public static final String FM_STATS_PROP = "persist.fm.stats";
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ mPrefs = new FmSharedPreferences(this);
+ mCommandActive = CMD_NONE;
+ mCommandFailed = CMD_NONE;
+
+ Log.d(LOGTAG, "onCreate - Height : "+ getWindowManager().getDefaultDisplay().getHeight()
+ + " - Width : "+ getWindowManager().getDefaultDisplay().getWidth());
+
+ mDisplayWidth = getWindowManager().getDefaultDisplay().getWidth();
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(outMetrics );
+
+ setContentView(R.layout.fmradio);
+ SavedDataAndState = (LoadedDataAndState)getLastNonConfigurationInstance();
+
+ mPicker = (HorizontalNumberPicker)findViewById(R.id.fm_picker);
+ if (mPicker != null) {
+ mPicker.setTextSize(mDisplayWidth / TEXTSIZE_PARAMETER_FOR_NUMBER_PICKER);
+ mPicker.setDensity(outMetrics.densityDpi);
+ mPicker.setOnValueChangedListener(new OnValueChangeListener(){
+ @Override
+ public void onValueChange(HorizontalNumberPicker picker,
+ int oldVal, int newVal) {
+ // TODO Auto-generated method stub
+ valueToFrequency(newVal);
+ mHandler.post(mRadioChangeFrequency);
+ }
+ });
+ }
+
+ mAnimation = AnimationUtils.loadAnimation(this,
+ R.anim.preset_select);
+
+ mMuteButton = (ImageView)findViewById(R.id.btn_silent);
+ if (mMuteButton != null) {
+ mMuteButton.setOnClickListener(mMuteModeClickListener);
+ }
+
+ mSpeakerButton = (ImageView)findViewById(R.id.btn_speaker_earphone);
+ if (mSpeakerButton != null) {
+ mSpeakerButton.setOnClickListener(mSpeakerClickListener);
+ }
+
+ mOnOffButton = (ImageView)findViewById(R.id.btn_onoff);
+ if (mOnOffButton != null) {
+ mOnOffButton.setOnClickListener(mTurnOnOffClickListener);
+ }
+
+ mForwardButton = (ImageView)findViewById(R.id.btn_forward);
+ if (mForwardButton != null) {
+ mForwardButton.setOnClickListener(mForwardClickListener);
+ mForwardButton.setOnLongClickListener(mForwardLongClickListener);
+ }
+
+ mBackButton = (ImageView)findViewById(R.id.btn_back);
+ if (mBackButton != null) {
+ mBackButton.setOnClickListener(mBackClickListener);
+ mBackButton.setOnLongClickListener(mBackLongClickListener);
+ }
+
+ /* 6 Preset Buttons */
+ mPresetButtons[0] = (Button)findViewById(R.id.presets_button_1);
+ mPresetButtons[1] = (Button)findViewById(R.id.presets_button_2);
+ mPresetButtons[2] = (Button)findViewById(R.id.presets_button_3);
+ mPresetButtons[3] = (Button)findViewById(R.id.presets_button_4);
+ mPresetButtons[4] = (Button)findViewById(R.id.presets_button_5);
+ mPresetButtons[5] = (Button)findViewById(R.id.presets_button_6);
+ mPresetButtons[6] = (Button)findViewById(R.id.presets_button_7);
+
+ for (int nButton = 0; nButton < MAX_PRESETS_PER_PAGE; nButton++) {
+ if (mPresetButtons[nButton] != null) {
+ mPresetButtons[nButton]
+ .setOnClickListener(mPresetButtonClickListener);
+ mPresetButtons[nButton]
+ .setOnLongClickListener(mPresetButtonOnLongClickListener);
+ }
+ }
+
+ mTuneStationFrequencyTV = (TextView)findViewById(R.id.prog_frequency_tv);
+ if (mTuneStationFrequencyTV != null) {
+ mTuneStationFrequencyTV.setOnLongClickListener(mFrequencyViewClickListener);
+ }
+ mProgramServiceTV = (TextView)findViewById(R.id.prog_service_tv);
+ mStereoTV = (TextView)findViewById(R.id.stereo_text_tv);
+
+ mStationCallSignTV = (TextView)findViewById(R.id.call_sign_tv);
+ mProgramTypeTV = (TextView)findViewById(R.id.pty_tv);
+
+ mRadioTextTV = (TextView)findViewById(R.id.radio_text_tv);
+ mERadioTextTV = (TextView)findViewById(R.id.eradio_text_tv);
+ mSleepMsgTV = (TextView)findViewById(R.id.sleep_msg_tv);
+ mRecordingMsgTV = (TextView)findViewById(R.id.record_msg_tv);
+ if (mRecordingMsgTV != null) {
+ mRecordingMsgTV.setOnClickListener(mRecordButtonListener);
+ }
+ /* Disable displaying RSSI */
+ mRSSI = (ImageView)findViewById(R.id.signal_level);
+ if (mRSSI != null) {
+ mRSSI.setVisibility(View.INVISIBLE);
+ }
+
+ if ((mRadioTextScroller == null) && (mRadioTextTV != null)) {
+ mRadioTextScroller = new ScrollerText(mRadioTextTV);
+ }
+
+ if ((mERadioTextScroller == null) && (mERadioTextTV != null)) {
+ mERadioTextScroller = new ScrollerText(mERadioTextTV);
+ }
+
+
+ //HDMI and FM concurrecny is not supported.
+ if (isHdmiOn()) {
+ showDialog(DIALOG_CMD_FAILED_HDMI_ON);
+ }
+ else {
+ if (false == bindToService(this, osc)) {
+ Log.d(LOGTAG, "onCreate: Failed to Start Service");
+ } else {
+ Log.d(LOGTAG, "onCreate: Start Service completed successfully");
+ }
+ }
+ }
+
+ protected void setDisplayvalue(){
+ int max = mPrefs.getUpperLimit();
+ int min = mPrefs.getLowerLimit();
+ int step = mPrefs.getFrequencyStepSize();
+ switch(step) {
+ case FREQUENCY_STEP_SMALL:
+ mPicker.setScale(Scale.SCALE_SMALL);
+ break;
+ case FREQUENCY_STEP_MEDIUM:
+ mPicker.setScale(Scale.SCALE_MEDIUM);
+ break;
+ case FREQUENCY_STEP_LARGE:
+ mPicker.setScale(Scale.SCALE_LARGE);
+ }
+
+ int channels = (int)((max - min) / step);
+ String [] displayValues = new String[channels + 1];
+ for(int i = 0; i < displayValues.length; i++) {
+ displayValues[i] = String.valueOf((min + i * step) / 1000.0f);
+ }
+ mPicker.setDisplayedValues(displayValues, true);
+ mPicker.setWrapSelectorWheel(true);
+ mPicker.invalidate();
+ }
+ protected int valueToFrequency(int value) {
+ mFrequency = mPrefs.getLowerLimit() + value *
+ mPrefs.getFrequencyStepSize();
+ return mFrequency;
+ }
+
+ @Override
+ public void onRestart() {
+ Log.d(LOGTAG, "FMRadio: onRestart");
+ try {
+ if (null != mService) {
+ mService.requestFocus();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ super.onRestart();
+ }
+
+ @Override
+ public void onStop() {
+ Log.d(LOGTAG, "FMRadio: onStop");
+ if(isSleepTimerActive()) {
+ mSleepUpdateHandlerThread.interrupt();
+ long timeNow = ((SystemClock.elapsedRealtime()));
+ if (timeNow < mSleepAtPhoneTime) {
+ try {
+ if (null != mService) {
+ mService.delayedStop((mSleepAtPhoneTime - timeNow),
+ FMRadioService.STOP_SERVICE);
+ }
+ }catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ if(isRecordTimerActive()) {
+ try {
+ if (null != mRecordUpdateHandlerThread) {
+ mRecordUpdateHandlerThread.interrupt();
+ }
+ }catch (NullPointerException e) {
+ e.printStackTrace();
+ }
+ long rtimeNow = ((SystemClock.elapsedRealtime()));
+ if (rtimeNow < mRecordDuration) {
+ try {
+ if (null != mService) {
+ mService.delayedStop((mRecordDuration - rtimeNow),
+ FMRadioService.STOP_RECORD);
+ }
+ }catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Log.d(LOGTAG, "FMRadio: onStart");
+ try {
+ if(mService != null) {
+ mService.registerCallbacks(mServiceCallbacks);
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ if(isSleepTimerActive()) {
+ Log.d(LOGTAG, "isSleepTimerActive is true");
+ try {
+ if (null != mService) {
+ mService.cancelDelayedStop(FMRadioService.STOP_SERVICE);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ initiateSleepThread();
+ }
+ if(isRecordTimerActive()) {
+ Log.d(LOGTAG, "isRecordTimerActive is true");
+ try {
+ if (null != mService) {
+ mService.cancelDelayedStop(FMRadioService.STOP_RECORD);
+ }
+ }catch (Exception e) {
+ e.printStackTrace();
+ }
+ if(isRecording()) {
+ initiateRecordThread();
+ }
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ Log.d(LOGTAG, "FMRadio: onPause");
+ super.onPause();
+ mRadioTextScroller.stopScroll();
+ mERadioTextScroller.stopScroll();
+ FmSharedPreferences.setTunedFrequency(mTunedStation.getFrequency());
+ mPrefs.Save();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ Log.d(LOGTAG, "FMRadio: onResume");
+ mPrefs.Load();
+ if (mPicker != null) {
+ setDisplayvalue();
+ }
+ PresetStation station = new PresetStation("",
+ FmSharedPreferences.getTunedFrequency());
+ if (station != null) {
+ mTunedStation.Copy(station);
+ }
+ mHandler.post(mUpdateProgramService);
+ mHandler.post(mUpdateRadioText);
+ mHandler.post(mOnStereo);
+ mUpdatePickerValue = true;
+ updateStationInfoToUI();
+ enableRadioOnOffUI();
+ }
+ private class LoadedDataAndState {
+ public LoadedDataAndState(){};
+ public boolean onOrOff;
+ }
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ final LoadedDataAndState data = new LoadedDataAndState();
+ if (mService != null) {
+ try {
+ data.onOrOff = mService.isFmOn();
+ }catch(RemoteException e) {
+ data.onOrOff = false;
+ e.printStackTrace();
+ }
+ }else {
+ data.onOrOff = false;
+ }
+ return data;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(LOGTAG, "FMRadio: onDestroy");
+ cleanupTimeoutHandler();
+ if (mService != null) {
+ try {
+ if(!mService.isFmOn()) {
+ endSleepTimer();
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ unbindFromService(this);
+ mService = null;
+ Log.d(LOGTAG, "onDestroy: unbindFromService completed");
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuItem item;
+ boolean radioOn = isFmOn();
+ boolean recording = isRecording();
+ boolean mSpeakerPhoneOn = isSpeakerEnabled();
+ boolean sleepActive = isSleepTimerActive();
+ boolean searchActive = isScanActive() || isSeekActive();
+
+ item = menu.add(0, MENU_SCAN_START, 0, R.string.menu_scan_start).
+ setIcon(R.drawable.ic_btn_search);
+ if (item != null) {
+ item.setVisible((!searchActive) && radioOn);
+ }
+ item = menu.add(0, MENU_SCAN_STOP, 0, R.string.menu_scan_stop).
+ setIcon(R.drawable.ic_btn_search);
+ if (item != null) {
+ item.setVisible(searchActive && radioOn);
+ }
+
+ if (RECORDING_ENABLE) {
+ item = menu.add(0, MENU_RECORD_START, 0, R.string.menu_record_start)
+ .setIcon(R.drawable.ic_menu_record);
+ if (item != null) {
+ item.setVisible(true);
+ item.setEnabled((!recording) && radioOn);
+ }
+ item = menu.add(0, MENU_RECORD_STOP, 0, R.string.menu_record_stop)
+ .setIcon(R.drawable.ic_menu_record);
+ if (item != null) {
+ item.setVisible(true);
+ item.setEnabled(recording && radioOn);
+ }
+ }
+ /* Settings can be active */
+ item = menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings).
+ setIcon(android.R.drawable.ic_menu_preferences);
+
+ item = menu.add(0, MENU_SLEEP, 0, R.string.menu_sleep).
+ setTitle(R.string.menu_sleep);
+ if (item != null) {
+ item.setVisible((!sleepActive) && radioOn);
+ }
+ item = menu.add(0, MENU_SLEEP_CANCEL, 0, R.string.menu_sleep_cancel).
+ setTitle(R.string.menu_sleep_cancel);
+ if (item != null) {
+ item.setVisible(sleepActive && radioOn);
+ }
+ mFMStats = SystemProperties.getBoolean(FM_STATS_PROP, false);
+ if(mFMStats) {
+ item = menu.add(0, MENU_STAT_TEST, 0,R.string.menu_stats).
+ setIcon(android.R.drawable.ic_menu_info_details);
+ }
+ menu.add(0, MENU_STATION_LIST, 0, R.string.menu_all_channels);
+ item = menu.add(0, MENU_TAGS, 0, R.string.menu_display_tags);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ MenuItem item;
+ boolean radioOn = isFmOn();
+ boolean recording = isRecording();
+ boolean mSpeakerPhoneOn = isSpeakerEnabled();
+ boolean searchActive = isScanActive() || isSeekActive();
+
+ item = menu.findItem(MENU_SCAN_START);
+ if (item != null) {
+ item.setVisible((!searchActive) && radioOn);
+ }
+ item = menu.findItem(MENU_SCAN_STOP);
+ if (item != null) {
+ item.setVisible(searchActive && radioOn);
+ }
+ if (RECORDING_ENABLE) {
+ item = menu.findItem(MENU_RECORD_START);
+ if (item != null) {
+ item.setVisible(true);
+ item.setEnabled((!recording) && radioOn && (!isAnalogModeEnabled()));
+ }
+ item = menu.findItem(MENU_RECORD_STOP);
+ if (item != null) {
+ item.setVisible(true);
+ item.setEnabled(recording && radioOn && (!isAnalogModeEnabled()));
+ }
+ }
+
+ boolean sleepActive = isSleepTimerActive();
+ item = menu.findItem(MENU_SLEEP);
+ if (item != null) {
+ item.setVisible((!sleepActive) && radioOn);
+ }
+ item = menu.findItem(MENU_SLEEP_CANCEL);
+ if (item != null) {
+ item.setVisible(sleepActive && radioOn);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_SETTINGS:
+ Intent launchPreferencesIntent = new Intent().setClass(this,
+ Settings.class);
+ launchPreferencesIntent.putExtra(Settings.RX_MODE,true);
+ startActivityForResult(launchPreferencesIntent,
+ ACTIVITY_RESULT_SETTINGS);
+ return true;
+ case MENU_STAT_TEST:
+ Intent launchFMStatIntent = new Intent().setClass(this,
+ FMStats.class);
+ startActivity(launchFMStatIntent);
+ return true;
+ case MENU_SCAN_START:
+ showDialog(DIALOG_SEARCH);
+ return true;
+ case MENU_SCAN_STOP:
+ cancelSearch();
+ return true;
+ case MENU_RECORD_START:
+ startRecording();
+ return true;
+ case MENU_RECORD_STOP:
+ stopRecording();
+ return true;
+ case MENU_SLEEP:
+ showDialog(DIALOG_SLEEP);
+ return true;
+ case MENU_SLEEP_CANCEL:
+ DebugToasts("Sleep Cancelled", Toast.LENGTH_SHORT);
+ endSleepTimer();
+ return true;
+ case MENU_STATION_LIST:
+ Intent stationListIntent = new Intent().setClass(this,
+ StationListActivity.class);
+ startActivity(stationListIntent);
+ return true;
+ case MENU_TAGS:
+ Intent launchFMTagsIntent = new Intent().setClass(this,
+ FmTags.class);
+ startActivity(launchFMTagsIntent);
+ return true;
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private boolean isHdmiOn() {
+ //HDMI and FM concurrecny is not supported.
+ try {
+ String hdmiUserOption = android.provider.Settings.System.getString(
+ getContentResolver(), "HDMI_USEROPTION");
+ }catch (Exception ex) {
+ }
+ return false;
+ }
+
+ private void enableSpeaker() {
+ //This method with toggle Speaker phone based on existing state .
+ boolean bSpeakerPhoneOn = isSpeakerEnabled();
+ if(mService != null) {
+ try {
+ if (bSpeakerPhoneOn) { // as Speaker is already on turn it off.
+ mService.enableSpeaker(false);
+ Log.d(LOGTAG, "Speaker phone is turned off");
+ mSpeakerButton.setImageResource(R.drawable.btn_earphone);
+ }else { // as Speaker is off turn it on.
+ mService.enableSpeaker(true);
+ Log.d(LOGTAG, "Speaker phone is turned on");
+ mSpeakerButton.setImageResource(R.drawable.btn_speaker);
+ }
+ invalidateOptionsMenu();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static final int RECORDTIMER_EXPIRED = 0x1003;
+ private static final int RECORDTIMER_UPDATE = 0x1004;
+
+ private boolean hasRecordTimerExpired() {
+ boolean expired = true;
+ /* If record duration is 'until stopped' then return false and exit */
+ if (mRecordUntilStopped)
+ return false;
+ if (isRecordTimerActive()) {
+ long timeNow = ((SystemClock.elapsedRealtime()));
+ //Log.d(LOGTAG, "hasSleepTimerExpired - " + mSleepAtPhoneTime + " now: "+ timeNow);
+ if ((mRecording == false) || (timeNow < mRecordDuration)) {
+ expired = false;
+ }
+ }
+ return expired;
+ }
+
+ private boolean isRecordTimerActive() {
+ boolean active = false;
+ if (mRecordDuration > 0) {
+ active = true;
+ }
+ return active;
+ }
+
+ private void updateExpiredRecordTime() {
+ int vis = View.VISIBLE;
+ if (mRecordUntilStopped || isRecordTimerActive()) {
+ long timeNow = ((SystemClock.elapsedRealtime()));
+ if (mRecordUntilStopped || (mRecordDuration >= timeNow)) {
+ long seconds = (timeNow - mRecordStartTime) / 1000;
+ String Msg = makeTimeString(seconds);
+ mRecordingMsgTV.setText(Msg);
+ }else {
+ /* Clean up timer */
+ mRecordDuration = 0;
+ }
+ }
+ mRecordingMsgTV.setVisibility(vis);
+ }
+
+ /* Recorder Thread processing */
+ private Runnable doRecordProcessing = new Runnable() {
+ public void run() {
+ boolean recordTimerExpired;
+ if (mRecordUntilStopped) {
+ recordTimerExpired = false;
+ }else {
+ recordTimerExpired = hasRecordTimerExpired();
+ }
+ while ((recordTimerExpired == false) &&
+ (!Thread.currentThread().isInterrupted())) {
+ try {
+ Thread.sleep(500);
+ Message statusUpdate = new Message();
+ statusUpdate.what = RECORDTIMER_UPDATE;
+ mUIUpdateHandlerHandler.sendMessage(statusUpdate);
+ recordTimerExpired = hasRecordTimerExpired();
+ }catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ if(true == recordTimerExpired) {
+ Message finished = new Message();
+ finished.what = RECORDTIMER_EXPIRED;
+ mUIUpdateHandlerHandler.sendMessage(finished);
+ }
+ }
+ }
+ };
+
+ private static long mRecordDuration = 0;
+ private static long mRecordStartTime = 0;
+ private static boolean mRecordUntilStopped = false;
+ private Thread mRecordUpdateHandlerThread = null;
+
+ private void initiateRecordDurationTimer(long mins) {
+
+ if(mins == FmSharedPreferences.RECORD_DUR_INDEX_3_VAL) {
+ mRecordUntilStopped = true;
+ mRecordStartTime = SystemClock.elapsedRealtime();
+ }else {
+ mRecordUntilStopped = false;
+ mRecordDuration = (mRecordStartTime = SystemClock.elapsedRealtime())
+ +
+ (mins * 60 * 1000);
+ }
+
+ Log.d(LOGTAG, "Stop Recording in mins: " + mins);
+ initiateRecordThread();
+ }
+ private void initiateRecordThread() {
+ if (mRecordUpdateHandlerThread == null) {
+ mRecordUpdateHandlerThread = new Thread(null, doRecordProcessing,
+ "RecordUpdateThread");
+ }
+ /* Launch the dummy thread to simulate the transfer progress */
+ Log.d(LOGTAG, "Thread State: " + mRecordUpdateHandlerThread.getState());
+ if (mRecordUpdateHandlerThread.getState() == Thread.State.TERMINATED) {
+ mRecordUpdateHandlerThread = new Thread(null, doRecordProcessing,
+ "RecordUpdateThread");
+ }
+ /* If the thread state is "new" then the thread has not yet started */
+ if (mRecordUpdateHandlerThread.getState() == Thread.State.NEW) {
+ mRecordUpdateHandlerThread.start();
+ }
+ }
+
+ private void audioRoute (int audioDevice) {
+ boolean bStatus;
+ if (mService != null) {
+ try {
+ bStatus = mService.routeAudio(audioDevice);
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(this);
+ dlgBuilder.setOnKeyListener(new OnKeyListener() {
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ Log.d(LOGTAG, "OnKeyListener event received"+keyCode);
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case 126: //KeyEvent.KEYCODE_MEDIA_PLAY:
+ case 127: //KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ return true;
+ }
+ return false;
+ }
+ });
+ switch (id) {
+ case DIALOG_SEARCH: {
+ return createSearchDlg(id, dlgBuilder);
+ }
+ case DIALOG_SLEEP: {
+ return createSleepDlg(id, dlgBuilder);
+ }
+ case DIALOG_PROGRESS_PROGRESS: {
+ return createProgressDialog(id);
+ }
+ case DIALOG_PRESET_OPTIONS: {
+ return createPresetOptionsDlg(id, dlgBuilder);
+ }
+ case DIALOG_PRESET_RENAME: {
+ return createPresetRenameDlg(id, dlgBuilder);
+ }
+ case DIALOG_CMD_TIMEOUT:{
+ return createCmdTimeoutDlg(id, dlgBuilder);
+ }
+ case DIALOG_CMD_FAILED:{
+ return createCmdFailedDlg(id, dlgBuilder);
+ }
+ case DIALOG_CMD_FAILED_HDMI_ON:{
+ return createCmdFailedDlgHdmiOn(id);
+ }
+ case DIALOG_CMD_FAILED_CALL_ON:{
+ return createCmdFailedDlgCallOn(id);
+ }
+ case DIALOG_PICK_FREQUENCY: {
+ FmConfig fmConfig = FmSharedPreferences.getFMConfiguration();
+ return new FrequencyPickerDialog(this, fmConfig, mTunedStation.getFrequency(), mFrequencyChangeListener);
+ }
+ default:
+ break;
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ super.onPrepareDialog(id, dialog);
+ int curListIndex = FmSharedPreferences.getCurrentListIndex();
+ PresetList curList = FmSharedPreferences.getStationList(curListIndex);
+ switch (id) {
+ case DIALOG_PRESET_RENAME: {
+ EditText et = (EditText) dialog.findViewById(R.id.list_edit);
+ if ((et != null) && (mPresetButtonStation != null)) {
+ et.setText(mPresetButtonStation.getName());
+ }
+ break;
+ }
+ case DIALOG_PRESET_OPTIONS: {
+ AlertDialog alertDlg = ((AlertDialog) dialog);
+ if ((alertDlg != null) && (mPresetButtonStation != null)) {
+ alertDlg.setTitle(mPresetButtonStation.getName());
+ }
+ break;
+ }
+ case DIALOG_PICK_FREQUENCY:
+ {
+ if (dialog != null && mTunedStation != null)
+ {
+ FmConfig fmConfig = FmSharedPreferences.getFMConfiguration();
+ ((FrequencyPickerDialog) dialog).updateSteps(fmConfig.getChSpacing());
+ ((FrequencyPickerDialog) dialog).updateMinFreq(fmConfig.getLowerLimit());
+ ((FrequencyPickerDialog) dialog).updateMaxFreq(fmConfig.getUpperLimit());
+ ((FrequencyPickerDialog) dialog).UpdateFrequency(mTunedStation.getFrequency());
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ Log.d(LOGTAG, "onActivityResult : requestCode -> " + requestCode);
+ Log.d(LOGTAG, "onActivityResult : resultCode -> " + resultCode);
+ if (requestCode == ACTIVITY_RESULT_SETTINGS) {
+ if (resultCode == RESULT_OK) {
+ if (data != null) {
+ String action = data.getAction();
+ if (action != null) {
+ if (action.equals(Settings.RESTORE_FACTORY_DEFAULT_ACTION)) {
+ RestoreDefaults();
+ enableRadioOnOffUI();
+ tuneRadio(FmSharedPreferences.DEFAULT_NO_FREQUENCY);
+ }
+ }
+ }
+ } //if ACTIVITY_RESULT_SETTINGS
+ }//if (resultCode == RESULT_OK)
+ }
+
+ /**
+ * @return true if a wired headset is connected.
+ */
+ boolean isWiredHeadsetAvailable() {
+ boolean bAvailable = false;
+ if(mService != null) {
+ try {
+ bAvailable = mService.isWiredHeadsetAvailable();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ Log.e(LOGTAG, "isWiredHeadsetAvailable: " + bAvailable);
+ return bAvailable;
+ }
+
+ /**
+ * @return true if a internal antenna is available.
+ *
+ */
+ boolean isAntennaAvailable() {
+ boolean bAvailable = false;
+ if(mService != null) {
+ try {
+ bAvailable = mService.isAntennaAvailable();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return bAvailable;
+ }
+
+ boolean isCallActive(){
+ boolean bCallActive = false;
+ if(mService != null) {
+ try {
+ bCallActive = mService.isCallActive();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return bCallActive;
+ }
+
+ private Dialog createSearchDlg(int id, AlertDialog.Builder dlgBuilder) {
+ String[] items;
+ dlgBuilder.setIcon(R.drawable.ic_btn_search);
+ dlgBuilder.setTitle(getString(R.string.search_dialog_title));
+ /* Pick RBDS or RDS */
+ if(FmSharedPreferences.isRBDSStd()) {
+ items = getResources().getStringArray(R.array.search_category_rbds_entries);
+ }else { // if(FmSharedPreferences.isRDSStd())
+ items = getResources().getStringArray(R.array.search_category_rds_entries);
+ }
+ dlgBuilder.setItems(items, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ String[] items;
+ String[] values;
+
+ mItemsIndex = item ;
+ /* Pick RBDS or RDS */
+ if(FmSharedPreferences.isRBDSStd()) {
+ items = getResources().
+ getStringArray(R.array.search_category_rbds_entries);
+ values = getResources().
+ getStringArray(R.array.search_category_rbds_values);
+ }else { // if(FmSharedPreferences.isRDSStd())
+ items = getResources().
+ getStringArray(R.array.search_category_rds_entries);
+ values = getResources().
+ getStringArray(R.array.search_category_rds_values);
+ }
+ if ((items != null) && (values != null) && (item >= 0)) {
+ if ((item >= 0) && (item <= items.length)
+ && (item <= items.length)) {
+ DebugToasts("Search Stations for : " + items[item]
+ + " (" + values[item] + ")",
+ Toast.LENGTH_SHORT);
+ int pty = Integer.parseInt(values[item]);
+ clearStationList();
+ initiateSearch(pty);
+ }
+ }
+ removeDialog(DIALOG_SEARCH);
+ }
+ });
+ return dlgBuilder.create();
+ }
+
+ private Dialog createPresetOptionsDlg(int id, AlertDialog.Builder dlgBuilder) {
+ if(mPresetButtonStation != null) {
+ dlgBuilder.setTitle(mPresetButtonStation.getName());
+ ArrayList<String> arrayList = new ArrayList<String>();
+ //PRESETS_OPTIONS_TUNE=0
+ arrayList.add(getResources().getString(R.string.preset_tune));
+ //PRESETS_OPTIONS_REPLACE=1
+ arrayList.add(getResources().getString(R.string.preset_replace));
+ //PRESETS_OPTIONS_RENAME=2
+ arrayList.add(getResources().getString(R.string.preset_rename));
+ //PRESETS_OPTIONS_DELETE=3
+ arrayList.add(getResources().getString(R.string.preset_delete));
+ String piString = mPresetButtonStation.getPIString();
+ if (!TextUtils.isEmpty(piString)) {
+ //PRESETS_OPTIONS_SEARCHPI=4
+ arrayList.add(getResources().getString(R.string.preset_search, piString));
+ }
+
+ dlgBuilder.setCancelable(true);
+ dlgBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ mPresetButtonStation = null;
+ removeDialog(DIALOG_PRESET_OPTIONS);
+ }
+ });
+ String[] items = new String [arrayList.size ()];
+ arrayList.toArray(items);
+ dlgBuilder.setItems(items, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ if(mPresetButtonStation != null) {
+ switch(item) {
+ case PRESETS_OPTIONS_TUNE: {
+ // Tune to the station
+ tuneRadio(mPresetButtonStation.getFrequency());
+ mPresetButtonStation = null;
+ break;
+ }
+ case PRESETS_OPTIONS_REPLACE: {
+ // Replace preset Station with currently tuned station
+ if(!stationExists(mTunedStation)) {
+ Log.d(LOGTAG, "station - " + mPresetButtonStation.getName() + " ("
+ + mPresetButtonStation.getFrequency() + ")");
+ mPresetButtonStation.Copy(mTunedStation);
+ mPresetButtonStation = null;
+ setupPresetLayout();
+ mPrefs.Save();
+ }
+ break;
+ }
+ case PRESETS_OPTIONS_RENAME: {
+ // Rename
+ showDialog(DIALOG_PRESET_RENAME);
+ break;
+ }
+ case PRESETS_OPTIONS_DELETE: {
+ // Delete
+ int curListIndex = FmSharedPreferences.getCurrentListIndex();
+ FmSharedPreferences.removeStation(curListIndex, mPresetButtonStation);
+ if (mPresetButtonStation.getFrequency() == mTunedStation.getFrequency()) {
+ // Restore current tuned station's name as its frequency
+ mTunedStation.setName("");
+ }
+ mPresetButtonStation = null;
+ setupPresetLayout();
+ mPrefs.Save();
+ break;
+ }
+ case PRESETS_OPTIONS_SEARCHPI: {
+ // SearchPI
+ String piString = mPresetButtonStation.getPIString();
+ int pi = mPresetButtonStation.getPI();
+ if ((!TextUtils.isEmpty(piString)) && (pi > 0)) {
+ initiatePISearch(pi);
+ }
+ mPresetButtonStation = null;
+ break;
+ }
+ default: {
+ // Should not happen
+ mPresetButtonStation = null;
+ break;
+ }
+ }//switch item
+ }//if(mPresetButtonStation != null)
+ removeDialog (DIALOG_PRESET_OPTIONS);
+ }//onClick
+ });
+ return dlgBuilder.create();
+ }
+ return null;
+ }
+
+ private Dialog createSleepDlg(int id, AlertDialog.Builder dlgBuilder) {
+ dlgBuilder.setTitle(R.string.dialog_sleep_title);
+ String[] items = getResources().getStringArray(R.array.sleep_duration_values);
+
+ dlgBuilder.setItems(items, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ String[] items = getResources().getStringArray(R.array.sleep_duration_values);
+ Toast.makeText(getApplicationContext(), items[item], Toast.LENGTH_SHORT).show();
+ if ((item >= 0) && (item <= items.length)) {
+ long seconds = (long) (900 * (item + 1));
+ initiateSleepTimer(seconds);
+ }
+ removeDialog (DIALOG_SLEEP);
+ }
+ });
+ return dlgBuilder.create();
+ }
+
+ private Dialog createProgressDialog(int id) {
+ String msgStr = "";
+ String titleStr = "";
+ double frequency = mTunedStation.getFrequency() / 1000.0;
+ boolean bSearchActive = false;
+
+ if (isSeekActive()) {
+ msgStr = getString(R.string.msg_seeking);
+ bSearchActive = true;
+ }else if (isScanActive()) {
+ String ptyStr = PresetStation.parsePTY(mScanPty);
+ if (!TextUtils.isEmpty(ptyStr)) {
+ msgStr = getString(R.string.msg_scanning_pty, ptyStr);
+ }else {
+ msgStr = getString(R.string.msg_scanning);
+ }
+ titleStr = getString(R.string.msg_search_title, ("" + frequency));
+ bSearchActive=true;
+ }else if (isSearchActive()) {
+ msgStr = getString(R.string.msg_searching);
+ titleStr = getString(R.string.msg_searching_title);
+ bSearchActive = true;
+ }
+ if (bSearchActive) {
+ mProgressDialog = new ProgressDialog(FMRadio.this);
+ if (mProgressDialog != null) {
+ mProgressDialog.setTitle(titleStr);
+ mProgressDialog.setMessage(msgStr);
+ mProgressDialog.setIcon(R.drawable.ic_launcher_fmradio);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setCanceledOnTouchOutside(false);
+ mProgressDialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ getText(R.string.button_text_stop),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ cancelSearch();
+ }
+ });
+ mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ cancelSearch();
+ }
+ });
+ mProgressDialog.setOnKeyListener(new OnKeyListener() {
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ Log.d(LOGTAG, "OnKeyListener event received in ProgressDialog" + keyCode);
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case 126: //KeyEvent.KEYCODE_MEDIA_PLAY:
+ case 127: //KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+ Message msg = new Message();
+ msg.what = TIMEOUT_PROGRESS_DLG;
+ mSearchProgressHandler.sendMessageDelayed(msg, SHOWBUSY_TIMEOUT);
+ }
+ return mProgressDialog;
+ }
+
+ private void updateSelectPresetListDlg(ListView lv) {
+ if (lv != null) {
+ List<PresetList> presetLists = FmSharedPreferences.getPresetLists();
+ ListIterator<PresetList> presetIter;
+ presetIter = presetLists.listIterator();
+ int numLists = presetLists.size();
+ int curIndex = FmSharedPreferences.getCurrentListIndex();
+ ArrayAdapter<String> typeAdapter = new ArrayAdapter<String>
+ (this, android.R.layout.
+ simple_list_item_single_choice);
+ for (int stationIter = 0; stationIter < numLists; stationIter++) {
+ PresetList temp = presetIter.next();
+ if (temp != null) {
+ typeAdapter.add(getString(R.string.presetlist_select_name,
+ temp.getName()));
+ }
+ }
+ typeAdapter.add(getString(R.string.presetlist_add_new));
+ lv.setAdapter(typeAdapter);
+ lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ lv.clearChoices();
+ if (curIndex >= numLists) {
+ curIndex = 0;
+ }
+ if (lv.getCount() >= curIndex) {
+ lv.setItemChecked(curIndex, true);
+ lv.setSelection(curIndex);
+ }else {
+ lv.setItemChecked(0, true);
+ lv.setSelection(0);
+ }
+ }
+ }
+
+ private Dialog createPresetRenameDlg(int id, AlertDialog.Builder dlgBuilder) {
+ if(mPresetButtonStation == null) {
+ return null;
+ }
+ LayoutInflater factory = LayoutInflater.from(this);
+ final View textEntryView = factory.inflate(
+ R.layout.alert_dialog_text_entry, null);
+ dlgBuilder.setTitle(R.string.dialog_presetlist_rename_title);
+ dlgBuilder.setView(textEntryView);
+ dlgBuilder.setPositiveButton(R.string.alert_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ //int curList = FmSharedPreferences.getCurrentListIndex();
+
+ EditText mTV = (EditText) textEntryView
+ .findViewById(R.id.list_edit);
+ CharSequence newName = null;
+ if (mTV != null) {
+ newName = mTV.getEditableText();
+ }
+ String nName = String.valueOf(newName);
+ mPresetButtonStation.setName(nName);
+ mPresetButtonStation=null;
+ setupPresetLayout();
+ mPrefs.Save();
+ removeDialog(DIALOG_PRESET_RENAME);
+ }
+ });
+ dlgBuilder.setNegativeButton(R.string.alert_dialog_cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ removeDialog(DIALOG_PRESET_RENAME);
+ }
+ });
+ return(dlgBuilder.create());
+ }
+
+ private Dialog createCmdTimeoutDlg(int id, AlertDialog.Builder dlgBuilder) {
+ if(mCommandActive > 0) {
+ dlgBuilder.setIcon(R.drawable.alert_dialog_icon)
+ .setTitle(R.string.fm_command_timeout_title);
+ dlgBuilder.setMessage(R.string.fm_tune_timeout_msg);
+ dlgBuilder.setPositiveButton(R.string.alert_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ cleanupTimeoutHandler();
+ removeDialog(DIALOG_CMD_TIMEOUT);
+ }
+ });
+ return(dlgBuilder.create());
+ }else {
+ return(null);
+ }
+ }
+
+ private Dialog createCmdFailedDlg(int id, AlertDialog.Builder dlgBuilder) {
+ dlgBuilder.setIcon(R.drawable.alert_dialog_icon)
+ .setTitle(R.string.fm_command_failed_title);
+ dlgBuilder.setMessage(R.string.fm_cmd_failed_msg);
+
+ dlgBuilder.setPositiveButton(R.string.alert_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ removeDialog(DIALOG_CMD_TIMEOUT);
+ mCommandFailed = CMD_NONE;
+ }
+ });
+
+ return(dlgBuilder.create());
+ }
+
+ private Dialog createCmdFailedDlgHdmiOn(int id) {
+ AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(this);
+ dlgBuilder.setIcon(R.drawable.alert_dialog_icon)
+ .setTitle(R.string.fm_command_failed_title);
+ dlgBuilder.setMessage(R.string.fm_cmd_failed_msg_hdmi);
+
+ dlgBuilder.setPositiveButton(R.string.alert_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ removeDialog(DIALOG_CMD_TIMEOUT);
+ mCommandFailed = CMD_NONE;
+ }
+ });
+
+ return(dlgBuilder.create());
+ }
+
+ private Dialog createCmdFailedDlgCallOn(int id) {
+ AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(this);
+ dlgBuilder.setIcon(R.drawable.alert_dialog_icon)
+ .setTitle(R.string.fm_command_failed_title);
+ dlgBuilder.setMessage(R.string.fm_cmd_failed_call_on);
+
+ dlgBuilder.setPositiveButton(R.string.alert_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ removeDialog(DIALOG_CMD_TIMEOUT);
+ mCommandFailed = CMD_NONE;
+ }
+ });
+
+ return(dlgBuilder.create());
+ }
+ private void RestoreDefaults() {
+ FmSharedPreferences.SetDefaults();
+ mPrefs.Save();
+ }
+
+ private View.OnLongClickListener mFrequencyViewClickListener =
+ new View.OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ showDialog(DIALOG_PICK_FREQUENCY);
+ return true;
+ }
+ };
+
+ private View.OnClickListener mForwardClickListener =
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ int frequency = FmSharedPreferences.getNextTuneFrequency();
+ Log.d(LOGTAG, "Tune Up: to " + frequency);
+ tuneRadio(frequency);
+ }
+ };
+
+ private View.OnClickListener mBackClickListener =
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ int frequency = FmSharedPreferences.getPrevTuneFrequency();
+ Log.d(LOGTAG, "Tune Down: to " + frequency);
+ tuneRadio(frequency);
+ }
+ };
+
+ private View.OnLongClickListener mForwardLongClickListener =
+ new View.OnLongClickListener() {
+ public boolean onLongClick(View view) {
+ SeekNextStation();
+ return true;
+ }
+ };
+
+ private View.OnLongClickListener mBackLongClickListener =
+ new View.OnLongClickListener() {
+ public boolean onLongClick(View view) {
+ SeekPreviousStation();
+ return true;
+ }
+ };
+
+ private View.OnClickListener mPresetListClickListener =
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ showDialog(DIALOG_SELECT_PRESET_LIST);
+ }
+ };
+ private View.OnLongClickListener mPresetListButtonOnLongClickListener =
+ new View.OnLongClickListener() {
+ public boolean onLongClick(View view) {
+ showDialog(DIALOG_PRESETS_LIST);
+ return true;
+ }
+ };
+
+ private View.OnClickListener mPresetsPageClickListener =
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ mPresetPageNumber++;
+ setupPresetLayout();
+ }
+ };
+
+ private View.OnClickListener mPresetButtonClickListener =
+ new View.OnClickListener() {
+ public void onClick(View view) {
+ PresetStation station = (PresetStation)view.getTag();
+ if (station != null) {
+ Log.d(LOGTAG, "station - " + station.getName() + " ("
+ + station.getFrequency() + ")");
+ tuneRadio(station.getFrequency());
+ view.startAnimation(mAnimation);
+ }
+ }
+ };
+
+ private View.OnLongClickListener mPresetButtonOnLongClickListener =
+ new View.OnLongClickListener() {
+ public boolean onLongClick(View view) {
+ PresetStation station = (PresetStation)view.getTag();
+ mPresetButtonStation = station;
+ if (station != null) {
+ showDialog(DIALOG_PRESET_OPTIONS);
+ }else {
+ addToPresets();
+ view.startAnimation(mAnimation);
+ }
+ return true;
+ }
+ };
+
+ final FrequencyPickerDialog.OnFrequencySetListener mFrequencyChangeListener
+ = new FrequencyPickerDialog.OnFrequencySetListener() {
+ public void onFrequencySet(FrequencyPicker view, int frequency) {
+ Log.d(LOGTAG, "mFrequencyChangeListener: onFrequencyChanged to: " +
+ frequency);
+ tuneRadio(frequency);
+ }
+ };
+
+ private View.OnClickListener mSpeakerClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ enableSpeaker();
+ }
+ };
+
+ private View.OnClickListener mMuteModeClickListener =
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ boolean bStatus = false;
+ if (mService != null) {
+ try {
+ if (true == isMuted()) {
+ bStatus = mService.unMute();
+ }else {
+ bStatus = mService.mute();
+ }
+ if (bStatus) {
+ setMuteModeButtonImage(true);
+ v.startAnimation(mAnimation);
+ }else {
+ mCommandFailed = CMD_MUTE;
+ if(isCallActive()) {
+ showDialog(DIALOG_CMD_FAILED_CALL_ON);
+ }else {
+ showDialog(DIALOG_CMD_FAILED);
+ }
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ private View.OnClickListener mRecordButtonListener =
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ if (isRecording()) {
+ stopRecording();
+ }else if(!isAnalogModeEnabled()) {
+ startRecording();
+ }
+ invalidateOptionsMenu();
+ }
+ };
+ private Handler mEnableRadioHandler = new Handler();
+ private Handler mDisableRadioHandler = new Handler();
+ private Runnable mEnableRadioTask = new Runnable() {
+ public void run() {
+ enableRadio();
+ mOnOffButton.setEnabled(true);
+ mOnOffButton.setClickable(true);
+ mOnOffButton.setOnClickListener(mTurnOnOffClickListener);
+ }
+ };
+
+ private Runnable mDisableRadioTask = new Runnable() {
+ public void run() {
+ disableRadio();
+ mOnOffButton.setEnabled(true);
+ mOnOffButton.setClickable(true);
+ mOnOffButton.setOnClickListener(mTurnOnOffClickListener);
+ }
+ };
+ private View.OnClickListener mTurnOnOffClickListener =
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ mOnOffButton.setEnabled(false);
+ mOnOffButton.setClickable(false);
+ mOnOffButton.setOnClickListener(null);
+ if (isFmOn()) {
+ mEnableRadioHandler.removeCallbacks(mEnableRadioTask);
+ mDisableRadioHandler.removeCallbacks(mDisableRadioTask);
+ mDisableRadioHandler.postDelayed(mDisableRadioTask, 0);
+ }else {
+ mDisableRadioHandler.removeCallbacks(mDisableRadioTask);
+ mEnableRadioHandler.removeCallbacks(mEnableRadioTask);
+ mEnableRadioHandler.postDelayed(mEnableRadioTask, 0);
+ }
+ cleanupTimeoutHandler();
+ }
+ };
+
+ private void setTurnOnOffButtonImage() {
+ if (isFmOn() == true) {
+ mOnOffButton.setEnabled(true);
+ }else {
+ mOnOffButton.setEnabled(false);
+ }
+ }
+
+ private void setMuteModeButtonImage(boolean notify) {
+ String fmMutedString;
+ if (isMuted() == true) {
+ mMuteButton.setImageResource(R.drawable.ic_silent_mode);
+ fmMutedString = "FM Radio Muted";
+ }else {
+ /* Find a icon for Stations */
+ mMuteButton.setImageResource(R.drawable.ic_silent_mode_off);
+ fmMutedString = "FM Radio Playing";
+ }
+ if (notify) {
+ //Toast.makeText(this, fmMutedString, Toast.LENGTH_SHORT).show();
+ Log.d(LOGTAG, fmMutedString);
+ }
+ }
+
+ private void enableRadio() {
+ mIsScaning = false;
+ mIsSeeking = false;
+ mIsSearching = false;
+ boolean bStatus = false;
+ if (isHdmiOn()) {
+ showDialog(DIALOG_CMD_FAILED_HDMI_ON);
+ }else {
+ if (mService != null) {
+ try {
+ if((false == mService.isFmOn()) && isAntennaAvailable()) {
+ bStatus = mService.fmOn();
+ if(bStatus) {
+ tuneRadio(FmSharedPreferences.getTunedFrequency());
+ enableRadioOnOffUI();
+ }else {
+ Log.e(LOGTAG, "mService.fmOn failed");
+ mCommandFailed = CMD_FMON;
+ if(isCallActive()) {
+ enableRadioOnOffUI();
+ showDialog(DIALOG_CMD_FAILED_CALL_ON);
+ }else {
+ showDialog(DIALOG_CMD_FAILED);
+ }
+ }
+ }else {
+ enableRadioOnOffUI();
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private void disableRadio() {
+ boolean bStatus = false;
+ boolean bSpeakerPhoneOn = isSpeakerEnabled();
+ cancelSearch();
+ endSleepTimer();
+ if(mRecording) {
+ //Stop if there is an ongoing Record
+ stopRecording();
+ }
+ if(mService != null) {
+ try {
+ if(bSpeakerPhoneOn) {
+ mService.enableSpeaker(false);
+ }
+ bStatus = mService.fmOff();
+ enableRadioOnOffUI();
+ if (bStatus == false) {
+ mCommandFailed = CMD_FMOFF;
+ Log.e(LOGTAG, " mService.fmOff failed");
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void resetRadio() {
+ boolean bSpeakerPhoneOn = isSpeakerEnabled();
+ resetSearch();
+ endSleepTimer();
+ if (mRecording) {
+ //Stop if there is an ongoing Record
+ stopRecording();
+ }
+ if (mService != null) {
+ try {
+ if(bSpeakerPhoneOn) {
+ mService.enableSpeaker(false);
+ }
+ mService.fmRadioReset();
+ enableRadioOnOffUI(false);
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ public void clearStationList() {
+ SharedPreferences sp = getSharedPreferences(SCAN_STATION_PREFS_NAME, 0);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.clear();
+ editor.commit();
+ }
+ public static boolean fmConfigure() {
+ boolean bStatus = true;
+ if(mService != null) {
+ try {
+ bStatus = mService.fmReconfigure();
+ if (bStatus == false) {
+ mCommandFailed = CMD_FMCONFIGURE;
+ Log.e(LOGTAG, "mService.fmReconfigure failed");
+ }else {
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return bStatus;
+ }
+ public static void fmAutoAFSwitch() {
+ boolean bStatus = false;
+ if (mService != null) {
+ try {
+ bStatus = mService.enableAutoAF(FmSharedPreferences.getAutoAFSwitch());
+ if (bStatus == false) {
+ mCommandFailed = CMD_SET_AUTOAF;
+ Log.e(LOGTAG, " mService.enableAutoAF failed");
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static void fmAudioOutputMode() {
+ boolean bStatus = false;
+ if (mService != null) {
+ try {
+ bStatus = mService.enableStereo(FmSharedPreferences.getAudioOutputMode());
+ if (bStatus == false) {
+ mCommandFailed = CMD_SET_AUDIO_MODE;
+ Log.e(LOGTAG, "mService.enableStereo failed");
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void startRecording() {
+ if(mService != null) {
+ try {
+ mRecording = mService.startRecording();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ //Initiate record timer thread here
+ if(mRecording == true) {
+ mRecordingMsgTV.setCompoundDrawablesWithIntrinsicBounds
+ (R.drawable.recorder_stop, 0, 0, 0);
+ int durationInMins = FmSharedPreferences.getRecordDuration();
+ Log.e(LOGTAG, "Fected duration: " + durationInMins);
+ initiateRecordDurationTimer( durationInMins );
+ }
+ }
+ }
+
+ private void stopRecording() {
+ mRecording = false;
+ DebugToasts("Stopped Recording", Toast.LENGTH_SHORT);
+ if(null != mRecordUpdateHandlerThread) {
+ mRecordUpdateHandlerThread.interrupt();
+ }
+ if(null != mRecordingMsgTV) {
+ mRecordingMsgTV.setText("");
+ mRecordingMsgTV.setCompoundDrawablesWithIntrinsicBounds
+ (R.drawable.recorder_start, 0, 0, 0);
+ }
+ if (mService != null) {
+ try {
+ mService.stopRecording();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ mRecordDuration = 0;
+ }
+
+ private boolean isRecording() {
+ mRecording = false;
+ if (mService != null) {
+ try {
+ mRecording = mService.isFmRecordingOn();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return(mRecording);
+ }
+
+ private boolean isSpeakerEnabled() {
+ boolean speakerEnabled = false;
+ if (mService != null) {
+ try {
+ speakerEnabled = mService.isSpeakerEnabled();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return(speakerEnabled);
+ }
+
+ private boolean stationExists(PresetStation station ){
+ boolean exists = FmSharedPreferences.sameStationExists(station);
+ if(exists){
+ Toast t = Toast.makeText(this, getString(R.string.station_exists), Toast.LENGTH_SHORT);
+ t.show();
+ }
+ return exists;
+ }
+
+ private void addToPresets() {
+ int currentList = FmSharedPreferences.getCurrentListIndex();
+ PresetStation selectedStation = getCurrentTunedStation();
+ if(!stationExists(selectedStation)) {
+ FmSharedPreferences.addStation(selectedStation.getName(), selectedStation
+ .getFrequency(), currentList);
+ setupPresetLayout();
+ }
+ }
+
+ private void enableRadioOnOffUI() {
+ boolean bEnable = isFmOn();
+ /* Disable if no antenna/headset is available */
+ if (!isAntennaAvailable()) {
+ bEnable = false;
+ }
+ enableRadioOnOffUI(bEnable);
+ }
+
+ private void enableRadioOnOffUI(boolean bEnable) {
+ if (mMuteButton != null) {
+ mMuteButton.setEnabled(bEnable);
+ setMuteModeButtonImage(false);
+ }
+ if (bEnable) {
+ if (mRadioTextScroller != null) {
+ mRadioTextScroller.startScroll();
+ }
+ if (mERadioTextScroller != null) {
+ mERadioTextScroller.startScroll();
+ }
+ if (mTuneStationFrequencyTV != null) {
+ mTuneStationFrequencyTV.setOnLongClickListener(mFrequencyViewClickListener);
+ }
+ invalidateOptionsMenu();
+ if ((mRecordingMsgTV != null) && !isRecording()) {
+ mRecordingMsgTV.setText("");
+ }
+ }else {
+ if (mRadioTextScroller != null) {
+ mRadioTextScroller.stopScroll();
+ }
+ if (mERadioTextScroller != null) {
+ mERadioTextScroller.stopScroll();
+ }
+ }
+ if (mForwardButton != null) {
+ mForwardButton.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if (mBackButton != null) {
+ mBackButton.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if (mTuneStationFrequencyTV != null) {
+ mTuneStationFrequencyTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if (mPicker != null) {
+ mPicker.setVisibility(
+ bEnable ? View.VISIBLE : View.INVISIBLE );
+ }
+ if (mStationCallSignTV != null) {
+ mStationCallSignTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if (mProgramTypeTV != null) {
+ mProgramTypeTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if (mSleepMsgTV != null) {
+ mSleepMsgTV.setVisibility(((bEnable && isSleepTimerActive()) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if (mRecordingMsgTV != null) {
+ mRecordingMsgTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if (mRadioTextTV != null) {
+ mRadioTextTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if(mERadioTextTV != null) {
+ mERadioTextTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if (mProgramServiceTV != null) {
+ mProgramServiceTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+
+ if (!isAntennaAvailable()) {
+ if (mRadioTextTV != null) {
+ mRadioTextTV.setVisibility(View.VISIBLE);
+ mRadioTextTV.setText(getString(R.string.msg_noantenna));
+ mRadioTextScroller.mOriginalString = getString(R.string.msg_noantenna);
+ }
+ if (mOnOffButton != null) {
+ mOnOffButton.setEnabled(false);
+ }
+ }else if (isCallActive()) {
+ if (mRadioTextTV != null) {
+ mRadioTextTV.setText("");
+ mRadioTextScroller.mOriginalString = "";
+ }
+ if (mERadioTextTV != null) {
+ mERadioTextTV.setText("");
+ mERadioTextScroller.mOriginalString = "";
+ }
+ if (mOnOffButton != null) {
+ mOnOffButton.setEnabled(false);
+ }
+ }else {
+ if (mRadioTextTV != null) {
+ mRadioTextTV.setText("");
+ mRadioTextScroller.mOriginalString = "";
+ }
+ if (mERadioTextTV != null) {
+ mERadioTextTV.setText("");
+ mERadioTextScroller.mOriginalString = "";
+ }
+ if (mOnOffButton != null) {
+ mOnOffButton.setEnabled(true);
+ }
+ }
+
+ if (mStereoTV != null) {
+ mStereoTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ for (int nButton = 0; nButton < MAX_PRESETS_PER_PAGE; nButton++) {
+ if (mPresetButtons[nButton] != null) {
+ mPresetButtons[nButton].setEnabled(bEnable);
+ }
+ }
+ if (mPresetListButton != null) {
+ mPresetListButton.setEnabled(bEnable);
+ }
+ if (mPresetPageButton != null) {
+ mPresetPageButton.setEnabled(bEnable &&
+ (FmSharedPreferences.getListStationCount() >= MAX_PRESETS_PER_PAGE));
+ }
+ if(mSpeakerButton != null) {
+ mSpeakerButton.setEnabled(bEnable);
+ if (bEnable) {
+ if(isSpeakerEnabled()) {
+ mSpeakerButton.setImageResource(R.drawable.btn_speaker);
+ }else {
+ mSpeakerButton.setImageResource(R.drawable.btn_earphone);
+ }
+ }else{
+ mSpeakerButton.setImageResource(R.drawable.btn_earphone);
+ }
+ }
+ }
+
+ private void resetSearchProgress() {
+ Message msg = new Message();
+ msg.what = END_PROGRESS_DLG;
+ mSearchProgressHandler.sendMessage(msg);
+ }
+
+ private void updateSearchProgress() {
+ boolean searchActive = isScanActive() || isSeekActive() || isSearchActive();
+ if (searchActive) {
+ synchronized (this) {
+ if(mProgressDialog == null) {
+ showDialog(DIALOG_PROGRESS_PROGRESS);
+ }else {
+ Message msg = new Message();
+ msg.what = UPDATE_PROGRESS_DLG;
+ mSearchProgressHandler.sendMessage(msg);
+ }
+ }
+ }else {
+ Message msg = new Message();
+ msg.what = END_PROGRESS_DLG;
+ mSearchProgressHandler.sendMessage(msg);
+ }
+ }
+
+ private void setupPresetLayout() {
+ int numStations = FmSharedPreferences.getListStationCount();
+ int addedStations = 0;
+
+ /*
+ * Validate mPresetPageNumber (Preset Page Number)
+ */
+ if (mPresetPageNumber > ((numStations) / MAX_PRESETS_PER_PAGE)) {
+ mPresetPageNumber = 0;
+ }
+
+ /*
+ * For every station, save the station as a tag and update the display
+ * on the preset Button.
+ */
+ for (int buttonIndex = 0; (buttonIndex < MAX_PRESETS_PER_PAGE); buttonIndex++) {
+ if (mPresetButtons[buttonIndex] != null) {
+ int stationIdex = (mPresetPageNumber * MAX_PRESETS_PER_PAGE)
+ + buttonIndex;
+ PresetStation station = FmSharedPreferences.getStationInList(stationIdex);
+ String display = "";
+ if (station != null) {
+ display = station.getName();
+ mPresetButtons[buttonIndex].setText(display);
+ mPresetButtons[buttonIndex].setTag(station);
+ addedStations++;
+ }else {
+ mPresetButtons[buttonIndex].setText(R.string.add_station);
+ mPresetButtons[buttonIndex].setTag(station);
+ }
+ }
+ }
+ }
+
+ private void updateStationInfoToUI() {
+ double frequency = mTunedStation.getFrequency() / 1000.0;
+ mTuneStationFrequencyTV.setText("" + frequency + "MHz");
+ if ((mPicker != null) && mUpdatePickerValue) {
+ mPicker.setValue(((mTunedStation.getFrequency() - mPrefs.getLowerLimit())
+ / mPrefs.getFrequencyStepSize()));
+ }
+ mStationCallSignTV.setText(mTunedStation.getPIString());
+ mProgramTypeTV.setText(mTunedStation.getPtyString());
+ mRadioTextTV.setText("");
+ mERadioTextTV.setText("");
+ mRadioTextScroller.mOriginalString = "";
+ mRadioTextScroller.mStringlength = 0;
+ mRadioTextScroller.mIteration = 0;
+ mERadioTextScroller.mOriginalString = "";
+ mERadioTextScroller.mStringlength = 0;
+ mERadioTextScroller.mIteration = 0;
+ mProgramServiceTV.setText("");
+ mStereoTV.setText("");
+ setupPresetLayout();
+ }
+
+
+ private boolean isFmOn() {
+ boolean bOn = false;
+ if(mService != null) {
+ try {
+ bOn = mService.isFmOn();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return(bOn);
+ }
+
+ private boolean isAnalogModeEnabled() {
+ boolean aEnabled = false;
+ if (mService != null) {
+ try {
+ aEnabled = mService.isAnalogModeEnabled();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return (aEnabled);
+ }
+
+ private boolean isMuted() {
+ boolean bMuted = false;
+ if (mService != null) {
+ try {
+ bMuted = mService.isMuted();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return(bMuted);
+ }
+
+ private boolean isScanActive() {
+ return(mIsScaning);
+ }
+
+ private boolean isSeekActive() {
+ return(mIsSeeking);
+ }
+ private boolean isSearchActive() {
+ return(mIsSearching);
+ }
+
+ public static PresetStation getCurrentTunedStation() {
+ return mTunedStation;
+ }
+
+ private void SeekPreviousStation() {
+ Log.d(LOGTAG, "SeekPreviousStation");
+ if (mService != null) {
+ try {
+ if(!isSeekActive()) {
+ mIsSeeking = mService.seek(false);
+ if (mIsSeeking == false) {
+ mCommandFailed = CMD_SEEK;
+ Log.e(LOGTAG, "mService.seek failed");
+ showDialog(DIALOG_CMD_FAILED);
+ }
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ updateSearchProgress();
+ }
+
+ private void SeekNextStation() {
+ Log.d(LOGTAG, "SeekNextStation");
+ if(mService != null) {
+ try {
+ if(!isSeekActive()) {
+ mIsSeeking = mService.seek(true);
+ if (mIsSeeking == false) {
+ mCommandFailed = CMD_SEEK;
+ Log.e(LOGTAG, "mService.seek failed");
+ showDialog(DIALOG_CMD_FAILED);
+ }
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ updateSearchProgress();
+ }
+
+ /** Scan related */
+ private void initiateSearch(int pty) {
+ synchronized (this) {
+ mIsScaning = true;
+ if(mService != null) {
+ try {
+ mIsScaning = mService.scan(pty);
+ if (mIsScaning == false) {
+ mCommandFailed = CMD_SCAN;
+ Log.e(LOGTAG, "mService.scan failed");
+ showDialog(DIALOG_CMD_FAILED);
+ }else {
+ mScanPty = pty;
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ updateSearchProgress();
+ }
+ }
+ }
+
+ /** SEEK Station with the matching PI */
+ private void initiatePISearch(int pi) {
+ Log.d(LOGTAG, "initiatePISearch");
+ if(mService != null) {
+ try {
+ if(!isSeekActive()) {
+ mIsSeeking = mService.seekPI(pi);
+ if (mIsSeeking == false) {
+ mCommandFailed = CMD_SEEKPI;
+ Log.e(LOGTAG, "mService.seekPI failed");
+ showDialog(DIALOG_CMD_FAILED);
+ }
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ updateSearchProgress();
+ }
+
+ private void resetSearch() {
+ mIsScaning = false;
+ mIsSeeking = false;
+ mIsSearching = false;
+ resetSearchProgress();
+ }
+
+ private void cancelSearch() {
+ synchronized (this) {
+ if (mService != null) {
+ try {
+ if ((mIsScaning == true)
+ || (mIsSeeking == true)
+ || (mIsSearching == true)) {
+ mService.cancelSearch();
+ mIsScaning = false;
+ mIsSeeking = false;
+ mIsSearching=false;
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ updateSearchProgress();
+ invalidateOptionsMenu();
+ }
+
+ /** get Strongest Stations */
+ private void initiateSearchList() {
+ synchronized (this) {
+ mIsSearching = false;
+ if (mService != null) {
+ try {
+ mIsSearching = mService.searchStrongStationList(NUM_AUTO_PRESETS_SEARCH);
+ if (mIsSearching == false) {
+ mCommandFailed = CMD_SEARCHLIST;
+ Log.e(LOGTAG, "mService.searchStrongStationList failed");
+ showDialog(DIALOG_CMD_FAILED);
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ updateSearchProgress();
+ }
+ }
+ }
+
+ private static final int UPDATE_PROGRESS_DLG = 1;
+ private static final int END_PROGRESS_DLG = 2;
+ private static final int TIMEOUT_PROGRESS_DLG = 3;
+ private static final int SHOWBUSY_TIMEOUT = 300000;
+ private Handler mSearchProgressHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ if (msg.what == UPDATE_PROGRESS_DLG) {
+ if(mProgressDialog != null) {
+ double frequency = mTunedStation.getFrequency() / 1000.0;
+ String titleStr = getString(R.string.msg_search_title, ("" + frequency));
+ mProgressDialog.setTitle(titleStr);
+ }
+ }else if (msg.what == END_PROGRESS_DLG) {
+ mSearchProgressHandler.removeMessages(END_PROGRESS_DLG);
+ mSearchProgressHandler.removeMessages(UPDATE_PROGRESS_DLG);
+ mSearchProgressHandler.removeMessages(TIMEOUT_PROGRESS_DLG);
+ removeDialog(DIALOG_PROGRESS_PROGRESS);
+ mProgressDialog = null;
+ }else if (msg.what == TIMEOUT_PROGRESS_DLG) {
+ cancelSearch();
+ }
+ }
+ };
+
+ /** Sleep Handling: After the timer expires, the app needs to shut down */
+ private static final int SLEEPTIMER_EXPIRED = 0x1001;
+ private static final int SLEEPTIMER_UPDATE = 0x1002;
+ private Thread mSleepUpdateHandlerThread = null;
+ /*
+ * Phone time when the App has to be shut down, calculated based on what the
+ * user configured
+ */
+ private static long mSleepAtPhoneTime = 0;
+
+ private void initiateSleepTimer(long seconds) {
+ mSleepAtPhoneTime = (SystemClock.elapsedRealtime()) + (seconds * 1000);
+ Log.d(LOGTAG, "Sleep in seconds: " + seconds);
+ initiateSleepThread();
+ }
+ private void initiateSleepThread() {
+ if (mSleepUpdateHandlerThread == null) {
+ mSleepUpdateHandlerThread = new Thread(null, doSleepProcessing,
+ "SleepUpdateThread");
+ }
+ /* Launch he dummy thread to simulate the transfer progress */
+ Log.d(LOGTAG, "Thread State: " + mSleepUpdateHandlerThread.getState());
+ if (mSleepUpdateHandlerThread.getState() == Thread.State.TERMINATED) {
+ mSleepUpdateHandlerThread = new Thread(null, doSleepProcessing,
+ "SleepUpdateThread");
+ }
+ /* If the thread state is "new" then the thread has not yet started */
+ if (mSleepUpdateHandlerThread.getState() == Thread.State.NEW) {
+ mSleepUpdateHandlerThread.start();
+ }
+ }
+
+ private void endSleepTimer() {
+ mSleepAtPhoneTime = 0;
+ if(null != mSleepUpdateHandlerThread) {
+ mSleepUpdateHandlerThread.interrupt();
+ }
+ if(null != mSleepMsgTV) {
+ mSleepMsgTV.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ private boolean hasSleepTimerExpired() {
+ boolean expired = true;
+ if (isSleepTimerActive()) {
+ long timeNow = ((SystemClock.elapsedRealtime()));
+ if (timeNow < mSleepAtPhoneTime) {
+ expired = false;
+ }
+ }
+ return expired;
+ }
+
+ private boolean isSleepTimerActive() {
+ boolean active = false;
+ if (mSleepAtPhoneTime > 0) {
+ active = true;
+ }
+ return active;
+ }
+
+ private void updateExpiredSleepTime() {
+ int vis = View.INVISIBLE;
+ if (isSleepTimerActive()) {
+ long timeNow = ((SystemClock.elapsedRealtime()));
+ if (mSleepAtPhoneTime >= timeNow) {
+ long seconds = (mSleepAtPhoneTime - timeNow) / 1000;
+ String sleepMsg = makeTimeString(seconds);
+ mSleepMsgTV.setText(sleepMsg);
+ if (seconds < SLEEP_TOGGLE_SECONDS) {
+ int nowVis = mSleepMsgTV.getVisibility();
+ vis = (nowVis == View.INVISIBLE) ? View.VISIBLE
+ : View.INVISIBLE;
+ }else {
+ vis = View.VISIBLE;
+ }
+ }else {
+ /* Clean up timer */
+ mSleepAtPhoneTime = 0;
+ }
+ }
+ mSleepMsgTV.setVisibility(vis);
+ }
+
+ private Handler mUIUpdateHandlerHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SLEEPTIMER_EXPIRED: {
+ mSleepAtPhoneTime = 0;
+ DebugToasts("Turning Off FM Radio", Toast.LENGTH_SHORT);
+ disableRadio();
+ return;
+ }
+ case SLEEPTIMER_UPDATE: {
+ updateExpiredSleepTime();
+ break;
+ }
+ case RECORDTIMER_EXPIRED: {
+ Log.d(LOGTAG, "mUIUpdateHandlerHandler - RECORDTIMER_EXPIRED");
+ mRecordDuration = 0;
+ //Clear the Recorder text
+ mRecordingMsgTV.setText("");
+ if (mRecording != false) {
+ DebugToasts("Stop Recording", Toast.LENGTH_SHORT);
+ stopRecording();
+ invalidateOptionsMenu();
+ }
+ return;
+ }
+ case RECORDTIMER_UPDATE: {
+ Log.d(LOGTAG, "mUIUpdateHandlerHandler - RECORDTIMER_UPDATE");
+ updateExpiredRecordTime();
+ break;
+ }
+ default:
+ break;
+ }
+ super.handleMessage(msg);
+ }
+ };
+
+ /* Thread processing */
+ private Runnable doSleepProcessing = new Runnable() {
+ public void run() {
+ boolean sleepTimerExpired = hasSleepTimerExpired();
+ while ((sleepTimerExpired == false) &&
+ (!Thread.currentThread().isInterrupted())) {
+ try {
+ Thread.sleep(500);
+ Message statusUpdate = new Message();
+ statusUpdate.what = SLEEPTIMER_UPDATE;
+ mUIUpdateHandlerHandler.sendMessage(statusUpdate);
+ sleepTimerExpired = hasSleepTimerExpired();
+ }catch (Exception ex) {
+ Log.d( LOGTAG, "RunningThread InterruptedException");
+ Thread.currentThread().interrupt();
+ }//try
+ }
+ if(true == sleepTimerExpired) {
+ Message finished = new Message();
+ finished.what = SLEEPTIMER_EXPIRED;
+ mUIUpdateHandlerHandler.sendMessage(finished);
+ }
+ }
+ };
+
+ private static StringBuilder sFormatBuilder = new StringBuilder();
+ private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale
+ .getDefault());
+ private static final Object[] sTimeArgs = new Object[5];
+
+ private String makeTimeString(long secs) {
+ String durationformat = getString(R.string.durationformat);
+
+ /*
+ * Provide multiple arguments so the format can be changed easily by
+ * modifying the xml.
+ */
+ sFormatBuilder.setLength(0);
+
+ final Object[] timeArgs = sTimeArgs;
+ timeArgs[0] = secs / 3600;
+ timeArgs[1] = secs / 60;
+ timeArgs[2] = (secs / 60) % 60;
+ timeArgs[3] = secs;
+ timeArgs[4] = secs % 60;
+
+ return sFormatter.format(durationformat, timeArgs).toString();
+ }
+
+ private void tuneRadio(int frequency){
+ /* Issue the tune command only if tuneCommand is already not active */
+ if((mService != null) && (mCommandActive != CMD_TUNE) && isFmOn()) {
+ boolean bStatus = false;
+ try {
+ bStatus = mService.tune(frequency);
+ if (bStatus) {
+ postTimeoutHandler(CMD_TUNE);
+ }else {
+ if (isFmOn()) {
+ mCommandFailed = CMD_TUNE;
+ Log.e(LOGTAG, "mService.tune failed");
+ showDialog(DIALOG_CMD_FAILED);
+ }
+ }
+ mTunedStation.setName("");
+ mTunedStation.setPI(0);
+ mTunedStation.setPty(0);
+ updateStationInfoToUI();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }else {
+ Log.e(LOGTAG, "Delayed Tune handler stopped");
+ }
+ }
+
+ /* Start a Command timeout
+ */
+ private synchronized void postTimeoutHandler(int cmd){
+ mCommandActive = cmd;
+ mCommandTimeoutHandler.sendEmptyMessageDelayed(MSG_CMD_TIMEOUT, CMD_TIMEOUT_DELAY_MS);
+ }
+
+ /* Stop the Command timeout
+ */
+ private synchronized void cleanupTimeoutHandler(){
+ mCommandActive = CMD_NONE;
+ mCommandTimeoutHandler.removeMessages(MSG_CMD_TIMEOUT);
+ }
+ /* Command timeout Handler
+ Routine to handle the Command timeouts for FM operations
+ that return asynchronous event callbacks
+ */
+ private Handler mCommandTimeoutHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CMD_TIMEOUT: {
+ if (mCommandActive > 0) {
+ Log.d(LOGTAG, "mCommandTimeoutHandler: Cmd failed: " + mCommandActive);
+ mCommandTimeoutHandler.removeMessages(MSG_CMD_TIMEOUT);
+ showDialog(DIALOG_CMD_TIMEOUT);
+ return;
+ }
+ break;
+ }//case MSG_CMD_TIMEOUT
+ }//switch
+ }//handleMessage
+ };
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ Log.d(LOGTAG, "KEY event received" + keyCode);
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case 126: //KeyEvent.KEYCODE_MEDIA_PLAY:
+ case 127: //KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+ private void resetFMStationInfoUI() {
+ mTunedStation.setFrequency(FmSharedPreferences.getTunedFrequency());
+ mTunedStation.setName("");
+ mTunedStation.setPI(0);
+ mTunedStation.setRDSSupported(false);
+ mTunedStation.setPty(0);
+ mRadioTextTV.setText("");
+ mERadioTextTV.setText("");
+ mRadioTextScroller.mOriginalString = "";
+ mProgramServiceTV.setText("");
+ mRadioTextScroller.stopScroll();
+ mERadioTextScroller.mOriginalString = "";
+ mERadioTextScroller.stopScroll();
+ mUpdatePickerValue = true;
+ updateStationInfoToUI();
+ }
+
+ final Runnable mRadioEnabled = new Runnable() {
+ public void run() {
+ /* Update UI to FM On State */
+ enableRadioOnOffUI(true);
+ /* Tune to the last tuned frequency */
+ mUpdatePickerValue = true;
+ tuneRadio(FmSharedPreferences.getTunedFrequency());
+ }
+ };
+
+ final Runnable mRadioDisabled = new Runnable() {
+ public void run() {
+ /* Update UI to FM Off State */
+ cleanupTimeoutHandler();
+ endSleepTimer();
+ stopRecording();
+ cancelSearch();
+ enableRadioOnOffUI(false);
+ }
+ };
+ final Runnable mRadioReset = new Runnable() {
+ public void run() {
+ /* Update UI to FM Reset (Off) State */
+ resetRadio();
+ }
+ };
+
+ final Runnable mUpdateStationInfo = new Runnable() {
+ public void run() {
+ cleanupTimeoutHandler();
+ PresetStation station = new PresetStation("", FmSharedPreferences.getTunedFrequency());
+ if (station != null) {
+ mTunedStation.Copy(station);
+ }
+ updateSearchProgress();
+ resetFMStationInfoUI();
+ }
+ };
+
+ final Runnable mSearchComplete = new Runnable() {
+ public void run() {
+ Log.d(LOGTAG, "mSearchComplete: ");
+ mScanPty=0;
+ mIsScaning = false;
+ mIsSeeking = false;
+ mIsSearching = false;
+ updateSearchProgress();
+ resetFMStationInfoUI();
+ invalidateOptionsMenu();
+ }
+ };
+
+ final Runnable mOnMute = new Runnable() {
+ public void run() {
+ setMuteModeButtonImage(true);
+ }
+ };
+
+ final Runnable mOnStereo = new Runnable() {
+ public void run() {
+ if (FMRADIO_UI_STATION_AUDIO_STEREO == mStereo) {
+ mStereoTV.setText(R.string.audio_type_stereo);
+ }else if (FMRADIO_UI_STATION_AUDIO_MONO == mStereo) {
+ mStereoTV.setText(R.string.audio_type_mono);
+ }else {
+ mStereoTV.setText("");
+ }
+ }
+ };
+
+ final Runnable mUpdateRadioText = new Runnable() {
+ public void run() {
+ String str = "";
+ if ((mService != null) && isFmOn()) {
+ try {
+ /* Get Radio Text and update the display */
+ str = mService.getRadioText();
+
+ /* Update only if all the characters are printable */
+ if (TextUtils.isPrintableAsciiOnly(str)) {
+ Log.d(LOGTAG, "mUpdateRadioText: Updatable string: [" + str + "]");
+ mRadioTextTV.setText(str);
+ mRadioTextScroller.mOriginalString = str;
+ }else if(TextUtils.isEmpty(str)) { /* Rest the string to empty*/
+ mRadioTextTV.setText("");
+ mRadioTextScroller.mOriginalString = "";
+ }else {
+ //Log.d(LOGTAG, "mUpdateRadioText: Leaving old string " + mRadioTextTV.getText());
+ }
+
+ /* Get PTY and PI and update the display */
+ int tempInt = mService.getProgramType();
+ /* Save PTY */
+ mTunedStation.setPty(tempInt);
+ mProgramTypeTV.setText(PresetStation.parsePTY(tempInt));
+ tempInt = mService.getProgramID();
+ mStationCallSignTV.setText(PresetStation.parsePI(tempInt));
+ if (tempInt != 0) {
+ mTunedStation.setPI(tempInt);
+ }
+ /* For non-Empty, non-Printable string, just leave the
+ existing old string
+ */
+ mRadioTextScroller.startScroll();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ final Runnable mRadioChangeFrequency = new Runnable(){
+ public void run() {
+ mUpdatePickerValue = false;
+ tuneRadio(mFrequency);
+ }
+ };
+
+ final Runnable mUpdateExtenRadioText = new Runnable() {
+ public void run() {
+ String str = "";
+ if ((mService != null) && isFmOn()) {
+ try {
+ /* Get Extended Radio Text and update the display */
+ str = mService.getExtenRadioText();
+ if (TextUtils.isEmpty(str)) {
+ mERadioTextTV.setText("");
+ mERadioTextScroller.mOriginalString = "";
+ }else {
+ mERadioTextTV.setText(str);
+ mERadioTextScroller.mOriginalString = str;
+ }
+ mERadioTextScroller.startScroll();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ /* Create runnable for posting */
+ final Runnable mUpdateProgramService = new Runnable() {
+ public void run() {
+ String str = "";
+ if (mService != null) {
+ try {
+ /* Get the Station PS and update the display */
+ str = mService.getProgramService();
+ /* Update only if all the characters are printable */
+ //if(isStringPrintable(str))
+ if (TextUtils.isPrintableAsciiOnly(str)) {
+ Log.d(LOGTAG, "mUpdateProgramService: Updatable string: [" + str + "]");
+ mProgramServiceTV.setText(str);
+ }else if (TextUtils.isEmpty(str)) { /* Rest the string to empty*/
+ mProgramServiceTV.setText("");
+ }else {
+ /* For non-Empty, non-Printable string, just leave the
+ existing old string
+ */
+ }
+ /* Get PTY and PI and update the display */
+ int tempInt = mService.getProgramType();
+ /* Save PTY */
+ mTunedStation.setPty(tempInt);
+
+ mProgramTypeTV.setText(PresetStation.parsePTY(tempInt));
+ tempInt =mService.getProgramID();
+ /* Save the program ID */
+ if (tempInt != 0) {
+ mTunedStation.setPI(tempInt);
+ }
+ mStationCallSignTV.setText(PresetStation.parsePI(tempInt));
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ private void DebugToasts(String str, int duration) {
+ //Toast.makeText(this, str, duration).show();
+ Log.d(LOGTAG, "Debug:" + str);
+ }
+
+ /**
+ * This Handler will scroll the text view.
+ * On startScroll, the scrolling starts after SCROLLER_START_DELAY_MS
+ * The Text View is scrolled left one character after every
+ * SCROLLER_UPDATE_DELAY_MS
+ * When the entire text is scrolled, the scrolling will restart
+ * after SCROLLER_RESTART_DELAY_MS
+ */
+ private static final class ScrollerText extends Handler {
+ private static final byte SCROLLER_STOPPED = 0x51;
+ private static final byte SCROLLER_STARTING = 0x52;
+ private static final byte SCROLLER_RUNNING = 0x53;
+
+ private static final int SCROLLER_MSG_START = 0xF1;
+ private static final int SCROLLER_MSG_TICK = 0xF2;
+ private static final int SCROLLER_MSG_RESTART = 0xF3;
+
+ private static final int SCROLLER_START_DELAY_MS = 1000;
+ private static final int SCROLLER_RESTART_DELAY_MS = 3000;
+ private static final int SCROLLER_UPDATE_DELAY_MS = 200;
+
+ private final WeakReference<TextView> mView;
+
+ private byte mStatus = SCROLLER_STOPPED;
+ String mOriginalString;
+ int mStringlength = 0;
+ int mIteration = 0;
+
+ ScrollerText(TextView v) {
+ mView = new WeakReference<TextView>(v);
+ }
+
+ /**
+ * Scrolling Message Handler
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SCROLLER_MSG_START:
+ mStatus = SCROLLER_RUNNING;
+ updateText();
+ break;
+ case SCROLLER_MSG_TICK:
+ updateText();
+ break;
+ case SCROLLER_MSG_RESTART:
+ if (mStatus == SCROLLER_RUNNING) {
+ startScroll();
+ }
+ break;
+ }
+ }
+
+ /**
+ * Moves the text left by one character and posts a
+ * delayed message for next update after SCROLLER_UPDATE_DELAY_MS.
+ * If the entire string is scrolled, then it displays the entire string
+ * and waits for SCROLLER_RESTART_DELAY_MS for scrolling restart
+ */
+ void updateText() {
+ if (mStatus != SCROLLER_RUNNING) {
+ return;
+ }
+ removeMessages(SCROLLER_MSG_TICK);
+ final TextView textView = mView.get();
+ if (textView != null) {
+ mStringlength = mOriginalString.length();
+ String szStr2 = "";
+ if (mStringlength > 0) {
+ mIteration++;
+ if (mIteration >= mStringlength) {
+ mIteration = 0;
+ sendEmptyMessageDelayed(SCROLLER_MSG_RESTART, SCROLLER_RESTART_DELAY_MS);
+ }else {
+ sendEmptyMessageDelayed(SCROLLER_MSG_TICK, SCROLLER_UPDATE_DELAY_MS);
+ }
+ if ((mOriginalString !=null) && (mOriginalString.length() >= mIteration))
+ szStr2 = mOriginalString.substring(mIteration);
+ }
+ textView.setText(szStr2);
+ }
+ }
+
+ /**
+ * Stops the scrolling
+ * The textView will be set to the original string.
+ */
+ void stopScroll() {
+ mStatus = SCROLLER_STOPPED;
+ removeMessages(SCROLLER_MSG_TICK);
+ removeMessages(SCROLLER_MSG_RESTART);
+ removeMessages(SCROLLER_MSG_START);
+ resetScroll();
+ }
+
+ /**
+ * Resets the scroll to display the original string.
+ */
+ private void resetScroll() {
+ mIteration = 0;
+ final TextView textView = mView.get();
+ if (textView != null) {
+ textView.setText(mOriginalString);
+ }
+ }
+
+ /** Starts the Scrolling of the TextView after a
+ * delay of SCROLLER_START_DELAY_MS
+ * Starts only if Length > 0
+ */
+ void startScroll() {
+ final TextView textView = mView.get();
+ if (textView != null) {
+ mOriginalString = (String)textView.getText();
+ mStringlength = mOriginalString.length();
+ if (mStringlength > 0) {
+ mStatus = SCROLLER_STARTING;
+ sendEmptyMessageDelayed(SCROLLER_MSG_START, SCROLLER_START_DELAY_MS);
+ }
+ }
+ }
+ }
+
+
+ public static IFMRadioService sService = null;
+ private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>();
+
+ public static boolean bindToService(Context context) {
+ Log.e(LOGTAG, "bindToService: Context");
+ return bindToService(context, null);
+ }
+
+ public static boolean bindToService(Context context, ServiceConnection callback) {
+ Log.e(LOGTAG, "bindToService: Context with serviceconnection callback");
+ context.startService(new Intent(context, FMRadioService.class));
+ ServiceBinder sb = new ServiceBinder(callback);
+ sConnectionMap.put(context, sb);
+ return context.bindService((new Intent()).setClass(context,
+ FMRadioService.class), sb, 0);
+ }
+
+ public static void unbindFromService(Context context) {
+ ServiceBinder sb = (ServiceBinder) sConnectionMap.remove(context);
+ Log.e(LOGTAG, "unbindFromService: Context");
+ if (sb == null) {
+ Log.e(LOGTAG, "Trying to unbind for unknown Context");
+ return;
+ }
+ context.unbindService(sb);
+ if (sConnectionMap.isEmpty()) {
+ // presumably there is nobody interested in the service at this point,
+ // so don't hang on to the ServiceConnection
+ sService = null;
+ }
+ }
+
+ private static class ServiceBinder implements ServiceConnection {
+ ServiceConnection mCallback;
+ ServiceBinder(ServiceConnection callback) {
+ mCallback = callback;
+ }
+
+ public void onServiceConnected(ComponentName className, android.os.IBinder service) {
+ sService = IFMRadioService.Stub.asInterface(service);
+ if (mCallback != null) {
+ Log.e(LOGTAG, "onServiceConnected: mCallback");
+ mCallback.onServiceConnected(className, service);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ if (mCallback != null) {
+ mCallback.onServiceDisconnected(className);
+ }
+ sService = null;
+ }
+ }
+
+ private ServiceConnection osc = new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ mService = IFMRadioService.Stub.asInterface(obj);
+ Log.e(LOGTAG, "ServiceConnection: onServiceConnected: ");
+ if (mService != null) {
+ try {
+ mService.registerCallbacks(mServiceCallbacks);
+ if (SavedDataAndState == null) {
+ enableRadio();
+ }else if (SavedDataAndState.onOrOff) {
+ enableRadioOnOffUI(true);
+ }else {
+ enableRadioOnOffUI(false);
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ if (isRecording()) {
+ mRecordingMsgTV.setCompoundDrawablesWithIntrinsicBounds
+ (R.drawable.recorder_stop, 0, 0, 0);
+ initiateRecordThread();
+ }else if((mRecordDuration > 0) &&
+ (mRecordUpdateHandlerThread != null)) {
+ mRecordUpdateHandlerThread.interrupt();
+ if(mRecordingMsgTV != null) {
+ mRecordingMsgTV.setText("");
+ }
+ mRecordDuration = 0;
+ }
+ return;
+ }else {
+ Log.e(LOGTAG, "IFMRadioService onServiceConnected failed");
+ }
+ if (getIntent().getData() == null) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(FMRadio.this, FMRadio.class);
+ startActivity(intent);
+ }
+ finish();
+ }
+ public void onServiceDisconnected(ComponentName classname) {
+ }
+ };
+
+ private IFMRadioServiceCallbacks.Stub mServiceCallbacks =
+ new IFMRadioServiceCallbacks.Stub() {
+ public void onEnabled() {
+ Log.d(LOGTAG, "mServiceCallbacks.onEnabled :");
+ mHandler.post(mRadioEnabled);
+ }
+ public void onDisabled() {
+ Log.d(LOGTAG, "mServiceCallbacks.onDisabled :");
+ mHandler.post(mRadioDisabled);
+ }
+ public void onRadioReset() {
+ Log.d(LOGTAG, "mServiceCallbacks.onRadioReset :");
+ mHandler.post(mRadioReset);
+ }
+ public void onTuneStatusChanged() {
+ Log.d(LOGTAG, "mServiceCallbacks.onTuneStatusChanged: ");
+ if (mIsScaning) {
+ Log.d(LOGTAG, "isScanning....................");
+ SharedPreferences sp = getSharedPreferences(SCAN_STATION_PREFS_NAME, 0);
+ SharedPreferences.Editor editor = sp.edit();
+ int station_number = sp.getInt(NUM_OF_STATIONS, 0);
+ station_number++;
+ editor.putInt(NUM_OF_STATIONS, station_number);
+ editor.putString(STATION_NAME + station_number, station_number + "");
+ editor.putInt(STATION_FREQUENCY + station_number,
+ FmSharedPreferences.getTunedFrequency());
+ editor.commit();
+ }
+ cleanupTimeoutHandler();
+ mHandler.post(mUpdateStationInfo);
+ }
+
+ public void onProgramServiceChanged() {
+ Log.d(LOGTAG, "mServiceCallbacks.onProgramServiceChanged :");
+ mHandler.post(mUpdateProgramService);
+ }
+ public void onRadioTextChanged() {
+ Log.d(LOGTAG, "mServiceCallbacks.onRadioTextChanged :");
+ mHandler.post(mUpdateRadioText);
+ }
+ public void onExtenRadioTextChanged() {
+ mHandler.post(mUpdateExtenRadioText);
+ }
+ public void onAlternateFrequencyChanged() {
+ Log.d(LOGTAG, "mServiceCallbacks.onAlternateFrequencyChanged :");
+ }
+ public void onSignalStrengthChanged() {
+ Log.d(LOGTAG, "mServiceCallbacks.onSignalStrengthChanged :");
+ }
+ public void onSearchComplete() {
+ Log.d(LOGTAG, "mServiceCallbacks.onSearchComplete :");
+ mHandler.post(mSearchComplete);
+ }
+ public void onSearchListComplete() {
+ Log.d(LOGTAG, "mServiceCallbacks.onSearchListComplete :");
+ }
+ public void onMute(boolean bMuted) {
+ Log.d(LOGTAG, "mServiceCallbacks.onMute :" + bMuted);
+ mHandler.post(mOnMute);
+ }
+ public void onAudioUpdate(boolean bStereo) {
+ if((bStereo) && (FmSharedPreferences.getAudioOutputMode())) {
+ mStereo = FMRADIO_UI_STATION_AUDIO_STEREO;
+ }else {
+ mStereo = FMRADIO_UI_STATION_AUDIO_MONO;
+ }
+ Log.d(LOGTAG, "mServiceCallbacks.onAudioUpdate :" + mStereo);
+ mHandler.post(mOnStereo);
+ }
+ public void onStationRDSSupported(boolean bRDSSupported) {
+ Log.d(LOGTAG, "mServiceCallbacks.onStationRDSSupported :" + bRDSSupported);
+ /*
+ * Depending on the signal strength etc, RDS Lock Sync/Supported may toggle,
+ * Since if a station Supports RDS, it will not change its support intermittently
+ * just save the status and ignore any "unsupported" state.
+ */
+ if (bRDSSupported) {
+ mTunedStation.setRDSSupported(true);
+ }
+ }
+ public void onRecordingStopped() {
+ Log.d(LOGTAG, "mServiceCallbacks.onRecordingStopped:");
+ stopRecording();
+ }
+ };
+}
diff --git a/fmapp2/src/com/caf/fmradio/FMRadioService.java b/fmapp2/src/com/caf/fmradio/FMRadioService.java
new file mode 100644
index 0000000..5d971cf
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FMRadioService.java
@@ -0,0 +1,3009 @@
+/*
+ * Copyright (c) 2009-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import java.io.File;
+import java.util.*;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.AudioSystem;
+import android.media.MediaRecorder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+import android.view.KeyEvent;
+import android.os.SystemProperties;
+
+import qcom.fmradio.FmReceiver;
+import qcom.fmradio.FmRxEvCallbacksAdaptor;
+import qcom.fmradio.FmRxRdsData;
+import qcom.fmradio.FmConfig;
+import android.net.Uri;
+import android.content.res.Resources;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import android.provider.MediaStore;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import com.caf.utils.A2dpDeviceStatus;
+import android.media.AudioManager;
+import android.content.ComponentName;
+import android.os.StatFs;
+import android.os.SystemClock;
+
+/**
+ * Provides "background" FM Radio (that uses the hardware) capabilities,
+ * allowing the user to switch between activities without stopping playback.
+ */
+public class FMRadioService extends Service
+{
+
+ public static final int RADIO_AUDIO_DEVICE_WIRED_HEADSET = 0;
+ public static final int RADIO_AUDIO_DEVICE_SPEAKER = 1;
+
+ private static final int FMRADIOSERVICE_STATUS = 101;
+ private static final String FMRADIO_DEVICE_FD_STRING = "/dev/radio0";
+ private static final String LOGTAG = "FMService";//FMRadio.LOGTAG;
+
+ private FmReceiver mReceiver;
+ private BroadcastReceiver mHeadsetReceiver = null;
+ private BroadcastReceiver mSdcardUnmountReceiver = null;
+ private BroadcastReceiver mMusicCommandListener = null;
+ private BroadcastReceiver mSleepExpiredListener = null;
+ private BroadcastReceiver mRecordTimeoutListener = null;
+ private BroadcastReceiver mDelayedServiceStopListener = null;
+ private boolean mOverA2DP = false;
+ private BroadcastReceiver mFmMediaButtonListener;
+ private IFMRadioServiceCallbacks mCallbacks;
+ private static FmSharedPreferences mPrefs;
+ private boolean mHeadsetPlugged = false;
+ private boolean mInternalAntennaAvailable = false;
+ private WakeLock mWakeLock;
+ private int mServiceStartId = -1;
+ private boolean mServiceInUse = false;
+ private static boolean mMuted = false;
+ private static boolean mResumeAfterCall = false;
+ private static String mAudioDevice="headset";
+ MediaRecorder mRecorder = null;
+ MediaRecorder mA2dp = null;
+ private boolean mFMOn = false;
+ private boolean mFmRecordingOn = false;
+ private boolean mSpeakerPhoneOn = false;
+ private int mCallStatus = 0;
+ private BroadcastReceiver mScreenOnOffReceiver = null;
+ final Handler mHandler = new Handler();
+ private boolean misAnalogModeSupported = false;
+ private boolean misAnalogPathEnabled = false;
+ private boolean mA2dpDisconnected = false;
+ //PhoneStateListener instances corresponding to each
+
+ private FmRxRdsData mFMRxRDSData=null;
+ // interval after which we stop the service when idle
+ private static final int IDLE_DELAY = 60000;
+ private File mA2DPSampleFile = null;
+ //Track FM playback for reenter App usecases
+ private boolean mPlaybackInProgress = false;
+ private boolean mStoppedOnFocusLoss = false;
+ private File mSampleFile = null;
+ long mSampleStart = 0;
+ // Messages handled in FM Service
+ private static final int FM_STOP =1;
+ private static final int RESET_NOTCH_FILTER =2;
+ private static final int STOPSERVICE_ONSLEEP = 3;
+ private static final int STOPRECORD_ONTIMEOUT = 4;
+ private static final int FOCUSCHANGE = 5;
+ //Track notch filter settings
+ private boolean mNotchFilterSet = false;
+ public static final int STOP_SERVICE = 0;
+ public static final int STOP_RECORD = 1;
+ // A2dp Device Status will be queried through this class
+ A2dpDeviceStatus mA2dpDeviceState = null;
+ private boolean mA2dpDeviceSupportInHal = false;
+ //on shutdown not to send start Intent to AudioManager
+ private boolean mAppShutdown = false;
+ private boolean mSingleRecordingInstanceSupported = false;
+ private AudioManager mAudioManager;
+ public static final long UNAVAILABLE = -1L;
+ public static final long PREPARING = -2L;
+ public static final long UNKNOWN_SIZE = -3L;
+ public static final long LOW_STORAGE_THRESHOLD = 50000000;
+ private long mStorageSpace;
+ private static final String IOBUSY_UNVOTE = "com.android.server.CpuGovernorService.action.IOBUSY_UNVOTE";
+ private static final String SLEEP_EXPIRED_ACTION = "com.caf.fmradio.SLEEP_EXPIRED";
+ private static final String RECORD_EXPIRED_ACTION = "com.caf.fmradio.RECORD_TIMEOUT";
+ private static final String SERVICE_DELAYED_STOP_ACTION = "com.caf.fmradio.SERVICE_STOP";
+
+ public FMRadioService() {
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mPrefs = new FmSharedPreferences(this);
+ mCallbacks = null;
+ TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE |
+ PhoneStateListener.LISTEN_DATA_ACTIVITY);
+ PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
+ mWakeLock.setReferenceCounted(false);
+ misAnalogModeSupported = SystemProperties.getBoolean("ro.fm.analogpath.supported",false);
+ /* Register for Screen On/off broadcast notifications */
+ mA2dpDeviceState = new A2dpDeviceStatus(getApplicationContext());
+ registerScreenOnOffListener();
+ registerHeadsetListener();
+ registerSleepExpired();
+ registerRecordTimeout();
+ registerDelayedServiceStop();
+ registerExternalStorageListener();
+ // registering media button receiver seperately as we need to set
+ // different priority for receiving media events
+ registerFmMediaButtonReceiver();
+ if ( false == SystemProperties.getBoolean("ro.fm.mulinst.recording.support",true)) {
+ mSingleRecordingInstanceSupported = true;
+ }
+
+ // Register for pause commands from other apps to stop FM
+ registerMusicServiceCommandReceiver();
+
+ // If the service was idle, but got killed before it stopped itself, the
+ // system will relaunch it. Make sure it gets stopped again in that case.
+ setAlarmDelayedServiceStop();
+ /* Query to check is a2dp supported in Hal */
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ String valueStr = audioManager.getParameters("isA2dpDeviceSupported");
+ mA2dpDeviceSupportInHal = valueStr.contains("=true");
+ Log.d(LOGTAG, " is A2DP device Supported In HAL"+mA2dpDeviceSupportInHal);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(LOGTAG, "onDestroy");
+ if (isFmOn())
+ {
+ Log.e(LOGTAG, "Service being destroyed while still playing.");
+ }
+
+ // make sure there aren't any other messages coming
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ cancelAlarms();
+ //release the audio focus listener
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (isMuted()) {
+ mMuted = false;
+ audioManager.setStreamMute(AudioManager.STREAM_MUSIC,false);
+ }
+ audioManager.abandonAudioFocus(mAudioFocusListener);
+ /* Remove the Screen On/off listener */
+ if (mScreenOnOffReceiver != null) {
+ unregisterReceiver(mScreenOnOffReceiver);
+ mScreenOnOffReceiver = null;
+ }
+ /* Unregister the headset Broadcase receiver */
+ if (mHeadsetReceiver != null) {
+ unregisterReceiver(mHeadsetReceiver);
+ mHeadsetReceiver = null;
+ }
+ if( mSdcardUnmountReceiver != null ) {
+ unregisterReceiver(mSdcardUnmountReceiver);
+ mSdcardUnmountReceiver = null;
+ }
+ if( mMusicCommandListener != null ) {
+ unregisterReceiver(mMusicCommandListener);
+ mMusicCommandListener = null;
+ }
+ if( mFmMediaButtonListener != null ) {
+ unregisterReceiver(mFmMediaButtonListener);
+ mFmMediaButtonListener = null;
+ }
+ if (mSleepExpiredListener != null ) {
+ unregisterReceiver(mSleepExpiredListener);
+ mSleepExpiredListener = null;
+ }
+ if (mRecordTimeoutListener != null) {
+ unregisterReceiver(mRecordTimeoutListener);
+ mRecordTimeoutListener = null;
+ }
+ if (mDelayedServiceStopListener != null) {
+ unregisterReceiver(mDelayedServiceStopListener);
+ mDelayedServiceStopListener = null;
+ }
+ /* Since the service is closing, disable the receiver */
+ fmOff();
+
+ TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ tmgr.listen(mPhoneStateListener, 0);
+
+ Log.d(LOGTAG, "onDestroy: unbindFromService completed");
+
+ //unregisterReceiver(mIntentReceiver);
+ mWakeLock.release();
+ super.onDestroy();
+ }
+
+/**
+ * Registers an intent to listen for ACTION_MEDIA_UNMOUNTED notifications.
+ * The intent will call closeExternalStorageFiles() if the external media
+ * is going to be ejected, so applications can clean up.
+ */
+ public void registerExternalStorageListener() {
+ if (mSdcardUnmountReceiver == null) {
+ mSdcardUnmountReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if ((action.equals(Intent.ACTION_MEDIA_UNMOUNTED))
+ || (action.equals(Intent.ACTION_MEDIA_EJECT))) {
+ Log.d(LOGTAG, "ACTION_MEDIA_UNMOUNTED Intent received");
+ if (mFmRecordingOn == true) {
+ try {
+ stopRecording();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ };
+ IntentFilter iFilter = new IntentFilter();
+ iFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
+ iFilter.addDataScheme("file");
+ registerReceiver(mSdcardUnmountReceiver, iFilter);
+ }
+ }
+
+
+ /**
+ * Registers an intent to listen for ACTION_HEADSET_PLUG
+ * notifications. This intent is called to know if the headset
+ * was plugged in/out
+ */
+ public void registerHeadsetListener() {
+ if (mHeadsetReceiver == null) {
+ mHeadsetReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
+ Log.d(LOGTAG, "ACTION_HEADSET_PLUG Intent received");
+ // Listen for ACTION_HEADSET_PLUG broadcasts.
+ Log.d(LOGTAG, "mReceiver: ACTION_HEADSET_PLUG");
+ Log.d(LOGTAG, "==> intent: " + intent);
+ Log.d(LOGTAG, " state: " + intent.getIntExtra("state", 0));
+ Log.d(LOGTAG, " name: " + intent.getStringExtra("name"));
+ mHeadsetPlugged = (intent.getIntExtra("state", 0) == 1);
+ // if headset is plugged out it is required to disable
+ // in minimal duration to avoid race conditions with
+ // audio policy manager switch audio to speaker.
+ mHandler.removeCallbacks(mHeadsetPluginHandler);
+ mHandler.post(mHeadsetPluginHandler);
+ } else if(mA2dpDeviceState.isA2dpStateChange(action) ) {
+ boolean bA2dpConnected =
+ mA2dpDeviceState.isConnected(intent);
+ if (!bA2dpConnected) {
+ Log.d(LOGTAG, "A2DP device is dis-connected!");
+ mA2dpDisconnected = true;
+ } else {
+ mA2dpDisconnected = false;
+ }
+ if (isAnalogModeEnabled()) {
+ Log.d(LOGTAG, "FM Audio Path is Analog Mode: FM Over BT not allowed");
+ return ;
+ }
+ //when playback is overA2Dp and A2dp disconnected
+ //when playback is not overA2DP and A2DP Connected
+ // In above two cases we need to Stop and Start FM which
+ // will take care of audio routing
+ if( (isFmOn()) &&
+ (true == ((bA2dpConnected)^(mOverA2DP))) &&
+ (false == mStoppedOnFocusLoss) &&
+ (!isSpeakerEnabled())) {
+ stopFM();
+ startFM();
+ }
+ } else if (action.equals("HDMI_CONNECTED")) {
+ //FM should be off when HDMI is connected.
+ fmOff();
+ try
+ {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if((mServiceInUse) && (mCallbacks != null) )
+ {
+ mCallbacks.onDisabled();
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ } else if( action.equals(Intent.ACTION_SHUTDOWN)) {
+ mAppShutdown = true;
+ }
+
+ }
+ };
+ IntentFilter iFilter = new IntentFilter();
+ iFilter.addAction(Intent.ACTION_HEADSET_PLUG);
+ iFilter.addAction(mA2dpDeviceState.getActionSinkStateChangedString());
+ iFilter.addAction("HDMI_CONNECTED");
+ iFilter.addAction(Intent.ACTION_SHUTDOWN);
+ iFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ registerReceiver(mHeadsetReceiver, iFilter);
+ }
+ }
+
+ public void registerFmMediaButtonReceiver() {
+ if (mFmMediaButtonListener == null) {
+ mFmMediaButtonListener = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ Log.d(LOGTAG, "FMMediaButtonIntentReceiver.FM_MEDIA_BUTTON");
+ Log.d(LOGTAG, "KeyEvent = " +intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
+ String action = intent.getAction();
+ if (action.equals(FMMediaButtonIntentReceiver.FM_MEDIA_BUTTON)) {
+ KeyEvent event = (KeyEvent)
+ intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ int keycode = event.getKeyCode();
+ switch (keycode) {
+ case KeyEvent.KEYCODE_HEADSETHOOK :
+ if (isFmOn()){
+ //FM should be off when Headset hook pressed.
+ fmOff();
+ if (isOrderedBroadcast()) {
+ abortBroadcast();
+ }
+ try {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if ((mServiceInUse) && (mCallbacks != null) ) {
+ mCallbacks.onDisabled();
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ } else if( mServiceInUse ) {
+ fmOn();
+ if (isOrderedBroadcast()) {
+ abortBroadcast();
+ }
+ try {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if (mCallbacks != null ) {
+ mCallbacks.onEnabled();
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PAUSE :
+ if (isFmOn()){
+ //FM should be off when Headset hook pressed.
+ fmOff();
+ if (isOrderedBroadcast()) {
+ abortBroadcast();
+ }
+ try {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if ((mServiceInUse) && (mCallbacks != null) ) {
+ mCallbacks.onDisabled();
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ if (mServiceInUse ) {
+ fmOn();
+ if (isOrderedBroadcast()) {
+ abortBroadcast();
+ }
+ try {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if (mCallbacks != null ) {
+ mCallbacks.onEnabled();
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ break;
+ } // end of switch
+ } // end of FMMediaButtonIntentReceiver.FM_MEDIA_BUTTON
+ } // end of onReceive
+ };
+ IntentFilter iFilter = new IntentFilter();
+ iFilter.addAction(FMMediaButtonIntentReceiver.FM_MEDIA_BUTTON);
+ registerReceiver(mFmMediaButtonListener, iFilter);
+ }
+ }
+
+ public void registerMusicServiceCommandReceiver() {
+ if (mMusicCommandListener == null) {
+ mMusicCommandListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals("com.android.music.musicservicecommand")) {
+ String cmd = intent.getStringExtra("command");
+ Log.d(LOGTAG, "Music Service command : "+cmd+ " received");
+ if (cmd != null && cmd.equals("pause")) {
+ if (mA2dpDisconnected) {
+ Log.d(LOGTAG, "not to pause,this is a2dp disconnected's pause");
+ mA2dpDisconnected = false;
+ return;
+ }
+ if(isFmOn()){
+ fmOff();
+ if (isOrderedBroadcast()) {
+ abortBroadcast();
+ }
+ try {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if((mServiceInUse) && (mCallbacks != null) ){
+ mCallbacks.onDisabled();
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ };
+ IntentFilter commandFilter = new IntentFilter();
+ commandFilter.addAction("com.android.music.musicservicecommand");
+ registerReceiver(mMusicCommandListener, commandFilter);
+ }
+ }
+ public void registerSleepExpired() {
+ if (mSleepExpiredListener == null) {
+ mSleepExpiredListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(LOGTAG, "registerSleepExpired");
+ mWakeLock.acquire(10 * 1000);
+ fmOff();
+ }
+ };
+ IntentFilter intentFilter = new IntentFilter(SLEEP_EXPIRED_ACTION);
+ registerReceiver(mSleepExpiredListener, intentFilter);
+ }
+ }
+ public void registerRecordTimeout() {
+ if (mRecordTimeoutListener == null) {
+ mRecordTimeoutListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(LOGTAG, "registerRecordTimeout");
+ mWakeLock.acquire(5 * 1000);
+ stopRecording();
+ }
+ };
+ IntentFilter intentFilter = new IntentFilter(RECORD_EXPIRED_ACTION);
+ registerReceiver(mRecordTimeoutListener, intentFilter);
+ }
+ }
+ public void registerDelayedServiceStop() {
+ if (mDelayedServiceStopListener == null) {
+ mDelayedServiceStopListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(LOGTAG, "registerDelayedServiceStop");
+ mWakeLock.acquire(5 * 1000);
+ if (isFmOn() || mServiceInUse) {
+ return;
+ }
+ stopSelf(mServiceStartId);
+ }
+ };
+ IntentFilter intentFilter = new IntentFilter(SERVICE_DELAYED_STOP_ACTION);
+ registerReceiver(mDelayedServiceStopListener, intentFilter);
+ }
+ }
+
+
+
+ final Runnable mHeadsetPluginHandler = new Runnable() {
+ public void run() {
+ /* Update the UI based on the state change of the headset/antenna*/
+ if(!isAntennaAvailable())
+ {
+ /* Disable FM and let the UI know */
+ fmOff();
+ try
+ {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if((mServiceInUse) && (mCallbacks != null) )
+ {
+ mCallbacks.onDisabled();
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ else
+ {
+ /* headset is plugged back in,
+ So turn on FM if:
+ - FM is not already ON.
+ - If the FM UI/Activity is in the foreground
+ (the service is "bound" by an activity
+ and if Callbacks are registered)
+ */
+ if ((!isFmOn()) && (mServiceInUse)
+ && (mCallbacks != null))
+ {
+ if( true != fmOn() ) {
+ return;
+ }
+ try
+ {
+ mCallbacks.onEnabled();
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ };
+
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ cancelAlarms();
+ mServiceInUse = true;
+ /* Application/UI is attached, so get out of lower power mode */
+ setLowPowerMode(false);
+ Log.d(LOGTAG, "onBind");
+ return mBinder;
+ }
+
+ @Override
+ public void onRebind(Intent intent) {
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ cancelAlarms();
+ mServiceInUse = true;
+ /* Application/UI is attached, so get out of lower power mode */
+ setLowPowerMode(false);
+ if((mPlaybackInProgress == false) && isWiredHeadsetAvailable())
+ startFM();
+ Log.d(LOGTAG, "onRebind");
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ Log.d(LOGTAG, "onStart");
+ mServiceStartId = startId;
+ // make sure the service will shut down on its own if it was
+ // just started but not bound to and nothing is playing
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ cancelAlarms();
+ setAlarmDelayedServiceStop();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mServiceInUse = false;
+ Log.d(LOGTAG, "onUnbind");
+
+ /* Application/UI is not attached, so go into lower power mode */
+ unregisterCallbacks();
+ setLowPowerMode(true);
+ if (isFmOn())
+ {
+ // something is currently playing, or will be playing once
+ // an in-progress call ends, so don't stop the service now.
+ return true;
+ }
+ gotoIdleState();
+ return true;
+ }
+
+ private void startFM(){
+ Log.d(LOGTAG, "In startFM");
+ if(true == mAppShutdown) { // not to send intent to AudioManager in Shutdown
+ return;
+ }
+ if (isCallActive()) { // when Call is active never let audio playback
+ mResumeAfterCall = true;
+ return;
+ }
+ mResumeAfterCall = false;
+ if ( true == mPlaybackInProgress ) // no need to resend event
+ return;
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ audioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ Log.d(LOGTAG,"FM registering for registerMediaButtonEventReceiver");
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ ComponentName fmRadio = new ComponentName(this.getPackageName(),
+ FMMediaButtonIntentReceiver.class.getName());
+ mAudioManager.registerMediaButtonEventReceiver(fmRadio);
+ mStoppedOnFocusLoss = false;
+
+ if (!mA2dpDeviceSupportInHal && (true == mA2dpDeviceState.isDeviceAvailable()) &&
+ (!isSpeakerEnabled()) && !isAnalogModeEnabled()
+ && (true == startA2dpPlayback())) {
+ mOverA2DP=true;
+ } else {
+ Log.d(LOGTAG, "FMRadio: Requesting to start FM");
+ //reason for resending the Speaker option is we are sending
+ //ACTION_FM=1 to AudioManager, the previous state of Speaker we set
+ //need not be retained by the Audio Manager.
+ if (isSpeakerEnabled()) {
+ mSpeakerPhoneOn = true;
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_SPEAKER);
+ }
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_FM,
+ AudioSystem.DEVICE_STATE_AVAILABLE, "");
+ }
+ mPlaybackInProgress = true;
+ }
+
+ private void stopFM(){
+ Log.d(LOGTAG, "In stopFM");
+ if (mOverA2DP==true){
+ mOverA2DP=false;
+ stopA2dpPlayback();
+ }else{
+ Log.d(LOGTAG, "FMRadio: Requesting to stop FM");
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_FM,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, "");
+ }
+ mPlaybackInProgress = false;
+ }
+
+ private void resetFM(){
+ Log.d(LOGTAG, "resetFM");
+ if (mOverA2DP==true){
+ mOverA2DP=false;
+ resetA2dpPlayback();
+ }else{
+ Log.d(LOGTAG, "FMRadio: Requesting to stop FM");
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_FM,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, "");
+ }
+ mPlaybackInProgress = false;
+ }
+
+ public boolean startRecording() {
+ Log.d(LOGTAG, "In startRecording of Recorder");
+ if( (true == mSingleRecordingInstanceSupported) &&
+ (true == mOverA2DP )) {
+ Toast.makeText( this,
+ "playback on BT in progress,can't record now",
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ stopRecording();
+
+ if (!updateAndShowStorageHint())
+ return false;
+ long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD;
+ mRecorder = new MediaRecorder();
+ try {
+ mRecorder.setMaxFileSize(maxFileSize);
+ } catch (RuntimeException exception) {
+
+ }
+
+ mSampleFile = null;
+ File sampleDir = Environment.getExternalStorageDirectory();
+ if (!sampleDir.canWrite()) // Workaround for broken sdcard support on
+ // the device.
+ sampleDir = new File("/sdcard/sdcard");
+ try {
+ mSampleFile = File
+ .createTempFile("FMRecording", ".3gpp", sampleDir);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Not able to access SD Card");
+ Toast.makeText(this, "Not able to access SD Card", Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ if (mRecorder == null) {
+ Toast.makeText(this,"MediaRecorder failed to create an instance",
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
+ try {
+ mRecorder.setAudioSource(MediaRecorder.AudioSource.FM_RX);
+ mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ } catch (RuntimeException exception) {
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ return false;
+ }
+ mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
+ try {
+ mRecorder.prepare();
+ mRecorder.start();
+ } catch (IOException e) {
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ return false;
+ } catch (RuntimeException e) {
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ return false;
+ }
+ mFmRecordingOn = true;
+ mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
+ public void onInfo(MediaRecorder mr, int what, int extra) {
+ if ((what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) ||
+ (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)) {
+ if (mFmRecordingOn) {
+ Log.d(LOGTAG, "Maximum file size/duration reached, stopping the recording");
+ stopRecording();
+ }
+ // Show the toast.
+ Toast.makeText(FMRadioService.this, R.string.FMRecording_reach_size_limit,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ // from MediaRecorder.OnErrorListener
+ public void onError(MediaRecorder mr, int what, int extra) {
+ Log.e(LOGTAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
+ if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
+ // We may have run out of space on the sdcard.
+ if (mFmRecordingOn) {
+ stopRecording();
+ }
+ updateAndShowStorageHint();
+ }
+ }
+ });
+
+ mSampleStart = System.currentTimeMillis();
+ return true;
+ }
+
+ public boolean startA2dpPlayback() {
+ Log.d(LOGTAG, "In startA2dpPlayback");
+ if( (true == mSingleRecordingInstanceSupported) &&
+ (true == mFmRecordingOn )) {
+ Toast.makeText(this,
+ "Recording already in progress,can't play on BT",
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ if(mOverA2DP)
+ stopA2dpPlayback();
+ mA2dp = new MediaRecorder();
+ if (mA2dp == null) {
+ Toast.makeText(this,"A2dpPlayback failed to create an instance",
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ try {
+ mA2dp.setAudioSource(MediaRecorder.AudioSource.FM_RX_A2DP);
+ mA2dp.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
+ mA2dp.setAudioEncoder(MediaRecorder.OutputFormat.DEFAULT);
+ File sampleDir = new File(getFilesDir().getAbsolutePath());
+ try {
+ mA2DPSampleFile = File
+ .createTempFile("FMRecording", ".3gpp", sampleDir);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Not able to access Phone's internal memory");
+ Toast.makeText(this, "Not able to access Phone's internal memory",
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ mA2dp.setOutputFile(mA2DPSampleFile.getAbsolutePath());
+ mA2dp.prepare();
+ mA2dp.start();
+ } catch (Exception exception) {
+ mA2dp.reset();
+ mA2dp.release();
+ mA2dp = null;
+ return false;
+ }
+ return true;
+ }
+
+ public void stopA2dpPlayback() {
+ if (mA2dp == null)
+ return;
+ if(mA2DPSampleFile != null)
+ {
+ try {
+ mA2DPSampleFile.delete();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Not able to delete file");
+ }
+ }
+ try {
+ mA2dp.stop();
+ mA2dp.reset();
+ mA2dp.release();
+ mA2dp = null;
+ } catch (Exception exception ) {
+ Log.e( LOGTAG, "Stop failed with exception"+ exception);
+ }
+ return;
+ }
+
+ private void resetA2dpPlayback() {
+ if (mA2dp == null)
+ return;
+ if(mA2DPSampleFile != null)
+ {
+ try {
+ mA2DPSampleFile.delete();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Not able to delete file");
+ }
+ }
+ try {
+ // Send Intent for IOBUSY VOTE, because MediaRecorder.stop
+ // gets Activity context which might not be always available
+ // and would thus fail to send the intent.
+ Intent ioBusyUnVoteIntent = new Intent(IOBUSY_UNVOTE);
+ // Remove vote for io_is_busy to be turned off.
+ ioBusyUnVoteIntent.putExtra("com.android.server.CpuGovernorService.voteType", 0);
+ sendBroadcast(ioBusyUnVoteIntent);
+
+ mA2dp.stop();
+
+ mA2dp.reset();
+ mA2dp.release();
+ mA2dp = null;
+ } catch (Exception exception ) {
+ Log.e( LOGTAG, "Stop failed with exception"+ exception);
+ }
+ return;
+ }
+
+ private void resetRecording() {
+
+ Log.v(LOGTAG, "resetRecording()");
+
+ mFmRecordingOn = false;
+ if (mRecorder == null)
+ return;
+
+ // Send Intent for IOBUSY VOTE, because MediaRecorder.stop
+ // gets Activity context which might not be always available
+ // and would thus fail to send the intent.
+ Intent ioBusyUnVoteIntent = new Intent(IOBUSY_UNVOTE);
+ // Remove vote for io_is_busy to be turned off.
+ ioBusyUnVoteIntent.putExtra("com.android.server.CpuGovernorService.voteType", 0);
+ sendBroadcast(ioBusyUnVoteIntent);
+
+ mRecorder.stop();
+
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ int sampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 );
+ if (sampleLength == 0)
+ return;
+ String state = Environment.getExternalStorageState();
+ Log.d(LOGTAG, "storage state is " + state);
+
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ this.addToMediaDB(mSampleFile);
+ }
+ else{
+ Log.e(LOGTAG, "SD card must have removed during recording. ");
+ Toast.makeText(this, "Recording aborted", Toast.LENGTH_SHORT).show();
+ }
+ return;
+ }
+
+ public void stopRecording() {
+ mFmRecordingOn = false;
+ if (mRecorder == null)
+ return;
+ try {
+ mRecorder.stop();
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ int sampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 );
+ if (sampleLength == 0)
+ return;
+ String state = Environment.getExternalStorageState();
+ Log.d(LOGTAG, "storage state is " + state);
+
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ try {
+ this.addToMediaDB(mSampleFile);
+ }
+ catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+ else{
+ Log.e(LOGTAG, "SD card must have removed during recording. ");
+ Toast.makeText(this, "Recording aborted", Toast.LENGTH_SHORT).show();
+ }
+ try
+ {
+ if((mServiceInUse) && (mCallbacks != null) ) {
+ mCallbacks.onRecordingStopped();
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ return;
+ }
+
+ /*
+ * Adds file and returns content uri.
+ */
+ private Uri addToMediaDB(File file) {
+ Log.d(LOGTAG, "In addToMediaDB");
+ Resources res = getResources();
+ ContentValues cv = new ContentValues();
+ long current = System.currentTimeMillis();
+ long modDate = file.lastModified();
+ Date date = new Date(current);
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ res.getString(R.string.audio_db_title_format));
+ String title = formatter.format(date);
+
+ // Lets label the recorded audio file as NON-MUSIC so that the file
+ // won't be displayed automatically, except for in the playlist.
+ cv.put(MediaStore.Audio.Media.IS_MUSIC, "1");
+
+ cv.put(MediaStore.Audio.Media.TITLE, title);
+ cv.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
+ cv.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000));
+ cv.put(MediaStore.Audio.Media.DATE_MODIFIED, (int) (modDate / 1000));
+ cv.put(MediaStore.Audio.Media.MIME_TYPE, "AUDIO_AAC_MP4");
+ cv.put(MediaStore.Audio.Media.ARTIST,
+ res.getString(R.string.audio_db_artist_name));
+ cv.put(MediaStore.Audio.Media.ALBUM,
+ res.getString(R.string.audio_db_album_name));
+ Log.d(LOGTAG, "Inserting audio record: " + cv.toString());
+ ContentResolver resolver = getContentResolver();
+ Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ Log.d(LOGTAG, "ContentURI: " + base);
+ Uri result = resolver.insert(base, cv);
+ if (result == null) {
+ Toast.makeText(this, "Unable to save recorded audio", Toast.LENGTH_SHORT).show();
+ return null;
+ }
+ if (getPlaylistId(res) == -1) {
+ createPlaylist(res, resolver);
+ }
+ int audioId = Integer.valueOf(result.getLastPathSegment());
+ addToPlaylist(resolver, audioId, getPlaylistId(res));
+
+ // Notify those applications such as Music listening to the
+ // scanner events that a recorded audio file just created.
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
+ return result;
+ }
+
+ private int getPlaylistId(Resources res) {
+ Uri uri = MediaStore.Audio.Playlists.getContentUri("external");
+ final String[] ids = new String[] { MediaStore.Audio.Playlists._ID };
+ final String where = MediaStore.Audio.Playlists.NAME + "=?";
+ final String[] args = new String[] { res.getString(R.string.audio_db_playlist_name) };
+ Cursor cursor = query(uri, ids, where, args, null);
+ if (cursor == null) {
+ Log.v(LOGTAG, "query returns null");
+ }
+ int id = -1;
+ if (cursor != null) {
+ cursor.moveToFirst();
+ if (!cursor.isAfterLast()) {
+ id = cursor.getInt(0);
+ }
+ cursor.close();
+ }
+ return id;
+ }
+
+ private Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ try {
+ ContentResolver resolver = getContentResolver();
+ if (resolver == null) {
+ return null;
+ }
+ return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+ } catch (UnsupportedOperationException ex) {
+ return null;
+ }
+ }
+
+ private Uri createPlaylist(Resources res, ContentResolver resolver) {
+ ContentValues cv = new ContentValues();
+ cv.put(MediaStore.Audio.Playlists.NAME, res.getString(R.string.audio_db_playlist_name));
+ Uri uri = resolver.insert(MediaStore.Audio.Playlists.getContentUri("external"), cv);
+ if (uri == null) {
+ Toast.makeText(this, "Unable to save recorded audio", Toast.LENGTH_SHORT).show();
+ }
+ return uri;
+ }
+
+ private void addToPlaylist(ContentResolver resolver, int audioId, long playlistId) {
+ String[] cols = new String[] {
+ "count(*)"
+ };
+ Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
+ Cursor cur = resolver.query(uri, cols, null, null, null);
+ final int base;
+ if (cur != null) {
+ cur.moveToFirst();
+ base = cur.getInt(0);
+ cur.close();
+ }
+ else {
+ base = 0;
+ }
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + audioId));
+ values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, audioId);
+ resolver.insert(uri, values);
+ }
+ private void fmActionOnCallState( int state ) {
+ //if Call Status is non IDLE we need to Mute FM as well stop recording if
+ //any. Similarly once call is ended FM should be unmuted.
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ mCallStatus = state;
+
+ if((TelephonyManager.CALL_STATE_OFFHOOK == state)||
+ (TelephonyManager.CALL_STATE_RINGING == state)) {
+ if (state == TelephonyManager.CALL_STATE_RINGING) {
+ int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
+ if (ringvolume == 0) {
+ return;
+ }
+ }
+ boolean bTempSpeaker = mSpeakerPhoneOn; //need to restore SpeakerPhone
+ boolean bTempMute = mMuted;// need to restore Mute status
+ int bTempCall = mCallStatus;//need to restore call status
+ if (fmOff()) {
+ if((mServiceInUse) && (mCallbacks != null)) {
+ try {
+ mCallbacks.onDisabled();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ mResumeAfterCall = true;
+ mSpeakerPhoneOn = bTempSpeaker;
+ mCallStatus = bTempCall;
+ mMuted = bTempMute;
+ } else if (!mResumeAfterCall) {
+ mResumeAfterCall = false;
+ mSpeakerPhoneOn = bTempSpeaker;
+ mCallStatus = bTempCall;
+ mMuted = bTempMute;
+ }
+ }
+ else if (state == TelephonyManager.CALL_STATE_IDLE) {
+ // start playing again
+ if (mResumeAfterCall)
+ {
+ // resume playback only if FM Radio was playing
+ // when the call was answered
+ if (isAntennaAvailable() && (!isFmOn()) && mServiceInUse)
+ {
+ Log.d(LOGTAG, "Resuming after call:");
+ if(true != fmOn()) {
+ return;
+ }
+ mResumeAfterCall = false;
+ if(mCallbacks != null) {
+ try {
+ mCallbacks.onEnabled();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }//idle
+ }
+
+ /* Handle Phone Call + FM Concurrency */
+ private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ Log.d(LOGTAG, "onCallStateChanged: State - " + state );
+ Log.d(LOGTAG, "onCallStateChanged: incomingNumber - " + incomingNumber );
+ fmActionOnCallState(state );
+ }
+
+ @Override
+ public void onDataActivity (int direction) {
+ Log.d(LOGTAG, "onDataActivity - " + direction );
+ if (direction == TelephonyManager.DATA_ACTIVITY_NONE ||
+ direction == TelephonyManager.DATA_ACTIVITY_DORMANT) {
+ if (mReceiver != null) {
+ Message msg = mDelayedStopHandler.obtainMessage(RESET_NOTCH_FILTER);
+ mDelayedStopHandler.sendMessageDelayed(msg, 10000);
+ }
+ } else {
+ if (mReceiver != null) {
+ if( true == mNotchFilterSet )
+ {
+ mDelayedStopHandler.removeMessages(RESET_NOTCH_FILTER);
+ }
+ else
+ {
+ mReceiver.setNotchFilter(true);
+ mNotchFilterSet = true;
+ }
+ }
+ }
+ }
+ };
+
+ private Handler mDelayedStopHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case FM_STOP:
+ // Check again to make sure nothing is playing right now
+ if (isFmOn() || mServiceInUse)
+ {
+ return;
+ }
+ Log.d(LOGTAG, "mDelayedStopHandler: stopSelf");
+ stopSelf(mServiceStartId);
+ break;
+ case RESET_NOTCH_FILTER:
+ if (mReceiver != null) {
+ mReceiver.setNotchFilter(false);
+ mNotchFilterSet = false;
+ }
+ break;
+ case STOPSERVICE_ONSLEEP:
+ fmOff();
+ break;
+ case STOPRECORD_ONTIMEOUT:
+ stopRecording();
+ break;
+ case FOCUSCHANGE:
+ if( false == isFmOn() ) {
+ Log.v(LOGTAG, "FM is not running, not handling change");
+ return;
+ }
+ switch (msg.arg1) {
+ case AudioManager.AUDIOFOCUS_LOSS:
+ Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS");
+ //intentional fall through.
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_LOSS_TRANSIENT");
+ if (mSpeakerPhoneOn) {
+ mSpeakerPhoneOn = false;
+ if (isAnalogModeSupported()) {
+ setAudioPath(true);
+ }
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE);
+ }
+ if(true == isFmRecordingOn())
+ stopRecording();
+ if(true == mPlaybackInProgress) {
+ if (mMuted)
+ unMute();
+ stopFM();
+ }
+ mStoppedOnFocusLoss = true;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN:
+ Log.v(LOGTAG, "AudioFocus: received AUDIOFOCUS_GAIN");
+ if(false == mPlaybackInProgress)
+ startFM();
+ mStoppedOnFocusLoss = false;
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ default:
+ Log.e(LOGTAG, "Unknown audio focus change code"+msg.arg1);
+ }
+ break;
+ }
+ }
+ };
+
+
+ /**
+ * Registers an intent to listen for
+ * ACTION_SCREEN_ON/ACTION_SCREEN_OFF notifications. This intent
+ * is called to know iwhen the screen is turned on/off.
+ */
+ public void registerScreenOnOffListener() {
+ if (mScreenOnOffReceiver == null) {
+ mScreenOnOffReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ Log.d(LOGTAG, "ACTION_SCREEN_ON Intent received");
+ //Screen turned on, set FM module into normal power mode
+ mHandler.post(mScreenOnHandler);
+ }
+ else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ Log.d(LOGTAG, "ACTION_SCREEN_OFF Intent received");
+ //Screen turned on, set FM module into low power mode
+ mHandler.post(mScreenOffHandler);
+ }
+ }
+ };
+ IntentFilter iFilter = new IntentFilter();
+ iFilter.addAction(Intent.ACTION_SCREEN_ON);
+ iFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ registerReceiver(mScreenOnOffReceiver, iFilter);
+ }
+ }
+
+ /* Handle all the Screen On actions:
+ Set FM Power mode to Normal
+ */
+ final Runnable mScreenOnHandler = new Runnable() {
+ public void run() {
+ setLowPowerMode(false);
+ }
+ };
+ /* Handle all the Screen Off actions:
+ Set FM Power mode to Low Power
+ This will reduce all the interrupts coming up from the SoC, saving power
+ */
+ final Runnable mScreenOffHandler = new Runnable() {
+ public void run() {
+ setLowPowerMode(true);
+ }
+ };
+
+ /* Show the FM Notification */
+ public void startNotification() {
+ RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
+ views.setImageViewResource(R.id.icon, R.drawable.stat_notify_fm);
+ if (isFmOn())
+ {
+ views.setTextViewText(R.id.frequency, getTunedFrequencyString());
+ } else
+ {
+ views.setTextViewText(R.id.frequency, "");
+ }
+
+ Notification status = new Notification();
+ status.contentView = views;
+ status.flags |= Notification.FLAG_ONGOING_EVENT;
+ status.icon = R.drawable.stat_notify_fm;
+ status.contentIntent = PendingIntent.getActivity(this, 0,
+ new Intent("com.caf.fmradio.FMRADIO_ACTIVITY"), 0);
+ startForeground(FMRADIOSERVICE_STATUS, status);
+ //NotificationManager nm = (NotificationManager)
+ // getSystemService(Context.NOTIFICATION_SERVICE);
+ //nm.notify(FMRADIOSERVICE_STATUS, status);
+ //setForeground(true);
+ mFMOn = true;
+ }
+
+ private void stop() {
+ Log.d(LOGTAG,"in stop");
+ if (!mServiceInUse) {
+ Log.d(LOGTAG,"calling unregisterMediaButtonEventReceiver in stop");
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ ComponentName fmRadio = new ComponentName(this.getPackageName(),
+ FMMediaButtonIntentReceiver.class.getName());
+ mAudioManager.unregisterMediaButtonEventReceiver(fmRadio);
+ }
+ gotoIdleState();
+ mFMOn = false;
+ }
+
+ private void gotoIdleState() {
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ cancelAlarms();
+ setAlarmDelayedServiceStop();
+ stopForeground(true);
+ }
+
+ /** Read's the internal Antenna available state from the FM
+ * Device.
+ */
+ public void readInternalAntennaAvailable()
+ {
+ mInternalAntennaAvailable = false;
+ if (mReceiver != null)
+ {
+ mInternalAntennaAvailable = mReceiver.getInternalAntenna();
+ Log.d(LOGTAG, "getInternalAntenna: " + mInternalAntennaAvailable);
+ }
+ }
+
+ /*
+ * By making this a static class with a WeakReference to the Service, we
+ * ensure that the Service can be GCd even when the system process still
+ * has a remote reference to the stub.
+ */
+ static class ServiceStub extends IFMRadioService.Stub
+ {
+ WeakReference<FMRadioService> mService;
+
+ ServiceStub(FMRadioService service)
+ {
+ mService = new WeakReference<FMRadioService>(service);
+ }
+
+ public boolean fmOn() throws RemoteException
+ {
+ return(mService.get().fmOn());
+ }
+
+ public boolean fmOff() throws RemoteException
+ {
+ return(mService.get().fmOff());
+ }
+
+ public boolean fmRadioReset() throws RemoteException
+ {
+ return true;
+ }
+
+ public boolean isFmOn()
+ {
+ return(mService.get().isFmOn());
+ }
+
+ public boolean isAnalogModeEnabled()
+ {
+ return(mService.get().isAnalogModeEnabled());
+ }
+
+ public boolean isFmRecordingOn()
+ {
+ return(mService.get().isFmRecordingOn());
+ }
+
+ public boolean isSpeakerEnabled()
+ {
+ return(mService.get().isSpeakerEnabled());
+ }
+
+ public boolean fmReconfigure()
+ {
+ return(mService.get().fmReconfigure());
+ }
+
+ public void registerCallbacks(IFMRadioServiceCallbacks cb) throws RemoteException
+ {
+ mService.get().registerCallbacks(cb);
+ }
+
+ public void unregisterCallbacks() throws RemoteException
+ {
+ mService.get().unregisterCallbacks();
+ }
+
+ public boolean routeAudio(int device)
+ {
+ return(mService.get().routeAudio(device));
+ }
+
+ public boolean mute()
+ {
+ return(mService.get().mute());
+ }
+
+ public boolean unMute()
+ {
+ return(mService.get().unMute());
+ }
+
+ public boolean isMuted()
+ {
+ return(mService.get().isMuted());
+ }
+
+ public boolean startRecording()
+ {
+ return(mService.get().startRecording());
+ }
+
+ public void stopRecording()
+ {
+ mService.get().stopRecording();
+ }
+
+ public boolean tune(int frequency)
+ {
+ return(mService.get().tune(frequency));
+ }
+
+ public boolean seek(boolean up)
+ {
+ return(mService.get().seek(up));
+ }
+
+ public void enableSpeaker(boolean speakerOn)
+ {
+ mService.get().enableSpeaker(speakerOn);
+ }
+
+ public boolean scan(int pty)
+ {
+ return(mService.get().scan(pty));
+ }
+
+ public boolean seekPI(int piCode)
+ {
+ return(mService.get().seekPI(piCode));
+ }
+ public boolean searchStrongStationList(int numStations)
+ {
+ return(mService.get().searchStrongStationList(numStations));
+ }
+
+ public boolean cancelSearch()
+ {
+ return(mService.get().cancelSearch());
+ }
+
+ public String getProgramService()
+ {
+ return(mService.get().getProgramService());
+ }
+ public String getRadioText()
+ {
+ return(mService.get().getRadioText());
+ }
+ public String getExtenRadioText()
+ {
+ return(mService.get().getExtenRadioText());
+ }
+ public int getProgramType()
+ {
+ return(mService.get().getProgramType());
+ }
+ public int getProgramID()
+ {
+ return(mService.get().getProgramID());
+ }
+ public int[] getSearchList()
+ {
+ return(mService.get().getSearchList());
+ }
+
+ public boolean setLowPowerMode(boolean enable)
+ {
+ return(mService.get().setLowPowerMode(enable));
+ }
+
+ public int getPowerMode()
+ {
+ return(mService.get().getPowerMode());
+ }
+ public boolean enableAutoAF(boolean bEnable)
+ {
+ return(mService.get().enableAutoAF(bEnable));
+ }
+ public boolean enableStereo(boolean bEnable)
+ {
+ return(mService.get().enableStereo(bEnable));
+ }
+ public boolean isAntennaAvailable()
+ {
+ return(mService.get().isAntennaAvailable());
+ }
+ public boolean isWiredHeadsetAvailable()
+ {
+ return(mService.get().isWiredHeadsetAvailable());
+ }
+ public boolean isCallActive()
+ {
+ return(mService.get().isCallActive());
+ }
+ public int getRssi()
+ {
+ return (mService.get().getRssi());
+ }
+ public int getIoC()
+ {
+ return (mService.get().getIoC());
+ }
+ public int getMpxDcc()
+ {
+ return (mService.get().getMpxDcc());
+ }
+ public int getIntDet()
+ {
+ return (mService.get().getIntDet());
+ }
+ public void setHiLoInj(int inj)
+ {
+ mService.get().setHiLoInj(inj);
+ }
+ public void delayedStop(long duration, int nType)
+ {
+ mService.get().delayedStop(duration, nType);
+ }
+ public void cancelDelayedStop(int nType)
+ {
+ mService.get().cancelDelayedStop(nType);
+ }
+ public void requestFocus()
+ {
+ mService.get().requestFocus();
+ }
+ public int getSINR()
+ {
+ return (mService.get().getSINR());
+ }
+ public boolean setSinrSamplesCnt(int samplesCnt)
+ {
+ return (mService.get().setSinrSamplesCnt(samplesCnt));
+ }
+ public boolean setSinrTh(int sinr)
+ {
+ return (mService.get().setSinrTh(sinr));
+ }
+ public boolean setIntfDetLowTh(int intfLowTh)
+ {
+ return (mService.get().setIntfDetLowTh(intfLowTh));
+ }
+ public boolean setIntfDetHighTh(int intfHighTh)
+ {
+ return (mService.get().setIntfDetHighTh(intfHighTh));
+ }
+ }
+ private final IBinder mBinder = new ServiceStub(this);
+
+ private boolean setAudioPath(boolean analogMode) {
+
+ if (mReceiver == null) {
+ return false;
+ }
+ if (isAnalogModeEnabled() == analogMode) {
+ Log.d(LOGTAG,"Analog Path already is set to "+analogMode);
+ return false;
+ }
+ if (!isAnalogModeSupported()) {
+ Log.d(LOGTAG,"Analog Path is not supported ");
+ return false;
+ }
+ if (SystemProperties.getBoolean("hw.fm.digitalpath",false)) {
+ return false;
+ }
+
+ boolean state = mReceiver.setAnalogMode(analogMode);
+ if (false == state) {
+ Log.d(LOGTAG, "Error in toggling analog/digital path " + analogMode);
+ return false;
+ }
+ misAnalogPathEnabled = analogMode;
+ return true;
+ }
+ /*
+ * Turn ON FM: Powers up FM hardware, and initializes the FM module
+ * .
+ * @return true if fm Enable api was invoked successfully, false if the api failed.
+ */
+ private boolean fmOn() {
+ boolean bStatus=false;
+ mWakeLock.acquire(10*1000);
+ if ( TelephonyManager.CALL_STATE_IDLE != getCallState() ) {
+ return bStatus;
+ }
+
+ if(mReceiver == null)
+ {
+ try {
+ mReceiver = new FmReceiver(FMRADIO_DEVICE_FD_STRING, fmCallbacks);
+ }
+ catch (InstantiationException e)
+ {
+ throw new RuntimeException("FmReceiver service not available!");
+ }
+ }
+
+ if (mReceiver != null)
+ {
+ if (isFmOn())
+ {
+ /* FM Is already on,*/
+ bStatus = true;
+ Log.d(LOGTAG, "mReceiver.already enabled");
+ }
+ else
+ {
+ // This sets up the FM radio device
+ FmConfig config = FmSharedPreferences.getFMConfiguration();
+ Log.d(LOGTAG, "fmOn: RadioBand :"+ config.getRadioBand());
+ Log.d(LOGTAG, "fmOn: Emphasis :"+ config.getEmphasis());
+ Log.d(LOGTAG, "fmOn: ChSpacing :"+ config.getChSpacing());
+ Log.d(LOGTAG, "fmOn: RdsStd :"+ config.getRdsStd());
+ Log.d(LOGTAG, "fmOn: LowerLimit :"+ config.getLowerLimit());
+ Log.d(LOGTAG, "fmOn: UpperLimit :"+ config.getUpperLimit());
+ bStatus = mReceiver.enable(FmSharedPreferences.getFMConfiguration());
+ if (isSpeakerEnabled()) {
+ setAudioPath(false);
+ } else {
+ setAudioPath(true);
+ }
+ Log.d(LOGTAG, "mReceiver.enable done, Status :" + bStatus);
+ }
+
+ if (bStatus == true)
+ {
+ /* Put the hardware into normal mode */
+ bStatus = setLowPowerMode(false);
+ Log.d(LOGTAG, "setLowPowerMode done, Status :" + bStatus);
+
+
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if( (audioManager != null) &&(false == mPlaybackInProgress) )
+ {
+ Log.d(LOGTAG, "mAudioManager.setFmRadioOn = true \n" );
+ //audioManager.setParameters("FMRadioOn="+mAudioDevice);
+ int state = getCallState();
+ if ( TelephonyManager.CALL_STATE_IDLE != getCallState() )
+ {
+ fmActionOnCallState(state);
+ } else {
+ startFM(); // enable FM Audio only when Call is IDLE
+ }
+ Log.d(LOGTAG, "mAudioManager.setFmRadioOn done \n" );
+ }
+ if (mReceiver != null) {
+ bStatus = mReceiver.registerRdsGroupProcessing(FmReceiver.FM_RX_RDS_GRP_RT_EBL|
+ FmReceiver.FM_RX_RDS_GRP_PS_EBL|
+ FmReceiver.FM_RX_RDS_GRP_AF_EBL|
+ FmReceiver.FM_RX_RDS_GRP_PS_SIMPLE_EBL);
+ Log.d(LOGTAG, "registerRdsGroupProcessing done, Status :" + bStatus);
+ }
+ bStatus = enableAutoAF(FmSharedPreferences.getAutoAFSwitch());
+ Log.d(LOGTAG, "enableAutoAF done, Status :" + bStatus);
+
+ /* There is no internal Antenna*/
+ bStatus = mReceiver.setInternalAntenna(false);
+ Log.d(LOGTAG, "setInternalAntenna done, Status :" + bStatus);
+
+ /* Read back to verify the internal Antenna mode*/
+ readInternalAntennaAvailable();
+
+ startNotification();
+ bStatus = true;
+ }
+ else
+ {
+ mReceiver = null; // as enable failed no need to disable
+ // failure of enable can be because handle
+ // already open which gets effected if
+ // we disable
+ stop();
+ }
+ }
+ return(bStatus);
+ }
+
+ /*
+ * Turn OFF FM Operations: This disables all the current FM operations .
+ */
+ private void fmOperationsOff() {
+ if ( mSpeakerPhoneOn)
+ {
+ mSpeakerPhoneOn = false;
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE);
+ }
+ if (isFmRecordingOn())
+ {
+ stopRecording();
+ }
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if(audioManager != null)
+ {
+ Log.d(LOGTAG, "audioManager.setFmRadioOn = false \n" );
+ unMute();
+ stopFM();
+ //audioManager.setParameters("FMRadioOn=false");
+ Log.d(LOGTAG, "audioManager.setFmRadioOn false done \n" );
+ }
+
+ if (isAnalogModeEnabled()) {
+ SystemProperties.set("hw.fm.isAnalog","false");
+ misAnalogPathEnabled = false;
+ }
+ }
+
+ /*
+ * Reset (OFF) FM Operations: This resets all the current FM operations .
+ */
+ private void fmOperationsReset() {
+ if ( mSpeakerPhoneOn)
+ {
+ mSpeakerPhoneOn = false;
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE);
+ }
+
+ if (isFmRecordingOn())
+ {
+ resetRecording();
+ }
+
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if(audioManager != null)
+ {
+ Log.d(LOGTAG, "audioManager.setFmRadioOn = false \n" );
+ unMute();
+ resetFM();
+ //audioManager.setParameters("FMRadioOn=false");
+ Log.d(LOGTAG, "audioManager.setFmRadioOn false done \n" );
+ }
+
+ if (isAnalogModeEnabled()) {
+ SystemProperties.set("hw.fm.isAnalog","false");
+ misAnalogPathEnabled = false;
+ }
+ }
+
+ /*
+ * Turn OFF FM: Disable the FM Host and hardware .
+ * .
+ * @return true if fm Disable api was invoked successfully, false if the api failed.
+ */
+ private boolean fmOff() {
+ boolean bStatus=false;
+
+ fmOperationsOff();
+
+ // This will disable the FM radio device
+ if (mReceiver != null)
+ {
+ bStatus = mReceiver.disable();
+ mReceiver = null;
+ }
+ stop();
+ return(bStatus);
+ }
+
+ /*
+ * Turn OFF FM: Disable the FM Host when hardware resets asynchronously .
+ * .
+ * @return true if fm Reset api was invoked successfully, false if the api failed .
+ */
+ private boolean fmRadioReset() {
+ boolean bStatus=false;
+
+ Log.v(LOGTAG, "fmRadioReset");
+
+ fmOperationsReset();
+
+ // This will reset the FM radio receiver
+ if (mReceiver != null)
+ {
+ bStatus = mReceiver.reset();
+ mReceiver = null;
+ }
+ stop();
+ return(bStatus);
+ }
+
+ /* Returns whether FM hardware is ON.
+ *
+ * @return true if FM was tuned, searching. (at the end of
+ * the search FM goes back to tuned).
+ *
+ */
+ public boolean isFmOn() {
+ return mFMOn;
+ }
+
+ /* Returns true if Analog Path is enabled */
+ public boolean isAnalogModeEnabled() {
+ return misAnalogPathEnabled;
+ }
+
+ public boolean isAnalogModeSupported() {
+ return misAnalogModeSupported;
+ }
+
+ public boolean isFmRecordingOn() {
+ return mFmRecordingOn;
+ }
+
+ public boolean isSpeakerEnabled() {
+ return mSpeakerPhoneOn;
+ }
+ public boolean isExternalStorageAvailable() {
+ boolean mStorageAvail = false;
+ String state = Environment.getExternalStorageState();
+
+ if(Environment.MEDIA_MOUNTED.equals(state)){
+ Log.d(LOGTAG, "device available");
+ mStorageAvail = true;
+ }
+ return mStorageAvail;
+ }
+ public void enableSpeaker(boolean speakerOn) {
+ if(isCallActive())
+ return ;
+ mSpeakerPhoneOn = speakerOn;
+ boolean analogmode = isAnalogModeSupported();
+ if (false == speakerOn) {
+ if (analogmode) {
+ if (isFmRecordingOn())
+ stopRecording();
+ stopFM();
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE);
+ if (mMuted) {
+ setAudioPath(true);
+ } else {
+ mute();
+ setAudioPath(true);
+ unMute();
+ }
+ } else {
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE);
+ }
+ if (analogmode)
+ startFM();
+ }
+
+
+ //Need to turn off BT path when Speaker is set on vice versa.
+ if( !mA2dpDeviceSupportInHal && !analogmode && true == mA2dpDeviceState.isDeviceAvailable()) {
+ if( ((true == mOverA2DP) && (true == speakerOn)) ||
+ ((false == mOverA2DP) && (false == speakerOn)) ) {
+ //disable A2DP playback for speaker option
+ stopFM();
+ startFM();
+ }
+ }
+ if (speakerOn) {
+ if (analogmode) {
+ stopFM();
+ if (mMuted) {
+ setAudioPath(false);
+ } else {
+ mute();
+ setAudioPath(false);
+ unMute();
+ }
+ }
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_SPEAKER);
+ if (analogmode)
+ startFM();
+ }
+
+ }
+ /*
+ * ReConfigure the FM Setup parameters
+ * - Band
+ * - Channel Spacing (50/100/200 KHz)
+ * - Emphasis (50/75)
+ * - Frequency limits
+ * - RDS/RBDS standard
+ *
+ * @return true if configure api was invoked successfully, false if the api failed.
+ */
+ public boolean fmReconfigure() {
+ boolean bStatus=false;
+ Log.d(LOGTAG, "fmReconfigure");
+ if (mReceiver != null)
+ {
+ // This sets up the FM radio device
+ FmConfig config = FmSharedPreferences.getFMConfiguration();
+ Log.d(LOGTAG, "RadioBand :"+ config.getRadioBand());
+ Log.d(LOGTAG, "Emphasis :"+ config.getEmphasis());
+ Log.d(LOGTAG, "ChSpacing :"+ config.getChSpacing());
+ Log.d(LOGTAG, "RdsStd :"+ config.getRdsStd());
+ Log.d(LOGTAG, "LowerLimit :"+ config.getLowerLimit());
+ Log.d(LOGTAG, "UpperLimit :"+ config.getUpperLimit());
+ bStatus = mReceiver.configure(config);
+ }
+ return(bStatus);
+ }
+
+ /*
+ * Register UI/Activity Callbacks
+ */
+ public void registerCallbacks(IFMRadioServiceCallbacks cb)
+ {
+ mCallbacks = cb;
+ }
+
+ /*
+ * unRegister UI/Activity Callbacks
+ */
+ public void unregisterCallbacks()
+ {
+ mCallbacks=null;
+ }
+
+ /*
+ * Route Audio to headset or speaker phone
+ * @return true if routeAudio call succeeded, false if the route call failed.
+ */
+ public boolean routeAudio(int audioDevice) {
+ boolean bStatus=false;
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+ //Log.d(LOGTAG, "routeAudio: " + audioDevice);
+
+ switch (audioDevice) {
+
+ case RADIO_AUDIO_DEVICE_WIRED_HEADSET:
+ mAudioDevice = "headset";
+ break;
+
+ case RADIO_AUDIO_DEVICE_SPEAKER:
+ mAudioDevice = "speaker";
+ break;
+
+ default:
+ mAudioDevice = "headset";
+ break;
+ }
+
+ if (mReceiver != null)
+ {
+ //audioManager.setParameters("FMRadioOn=false");
+ //Log.d(LOGTAG, "mAudioManager.setFmRadioOn =" + mAudioDevice );
+ //audioManager.setParameters("FMRadioOn="+mAudioDevice);
+ //Log.d(LOGTAG, "mAudioManager.setFmRadioOn done \n");
+ }
+
+ return bStatus;
+ }
+
+ /*
+ * Mute FM Hardware (SoC)
+ * @return true if set mute mode api was invoked successfully, false if the api failed.
+ */
+ public boolean mute() {
+ boolean bCommandSent=true;
+ if(isMuted())
+ return bCommandSent;
+ if(isCallActive())
+ return false;
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ Log.d(LOGTAG, "mute:");
+ if (audioManager != null)
+ {
+ mMuted = true;
+ audioManager.setStreamMute(AudioManager.STREAM_MUSIC,true);
+ }
+ return bCommandSent;
+ }
+
+ /*
+ * UnMute FM Hardware (SoC)
+ * @return true if set mute mode api was invoked successfully, false if the api failed.
+ */
+ public boolean unMute() {
+ boolean bCommandSent=true;
+ if(!isMuted())
+ return bCommandSent;
+ if(isCallActive())
+ return false;
+ Log.d(LOGTAG, "unMute:");
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (audioManager != null)
+ {
+ mMuted = false;
+ audioManager.setStreamMute(AudioManager.STREAM_MUSIC,false);
+ if (mResumeAfterCall)
+ {
+ //We are unmuting FM in a voice call. Need to enable FM audio routing.
+ startFM();
+ }
+ }
+ return bCommandSent;
+ }
+
+ /* Returns whether FM Hardware(Soc) Audio is Muted.
+ *
+ * @return true if FM Audio is muted, false if not muted.
+ *
+ */
+ public boolean isMuted() {
+ return mMuted;
+ }
+
+ /* Tunes to the specified frequency
+ *
+ * @return true if Tune command was invoked successfully, false if not muted.
+ * Note: Callback FmRxEvRadioTuneStatus will be called when the tune
+ * is complete
+ */
+ public boolean tune(int frequency) {
+ boolean bCommandSent=false;
+ double doubleFrequency = frequency/1000.00;
+
+ Log.d(LOGTAG, "tuneRadio: " + doubleFrequency);
+ if (mReceiver != null)
+ {
+ mReceiver.setStation(frequency);
+ bCommandSent = true;
+ }
+ return bCommandSent;
+ }
+
+ /* Seeks (Search for strong station) to the station in the direction specified
+ * relative to the tuned station.
+ * boolean up: true - Search in the forward direction.
+ * false - Search in the backward direction.
+ * @return true if Seek command was invoked successfully, false if not muted.
+ * Note: 1. Callback FmRxEvSearchComplete will be called when the Search
+ * is complete
+ * 2. Callback FmRxEvRadioTuneStatus will also be called when tuned to a station
+ * at the end of the Search or if the seach was cancelled.
+ */
+ public boolean seek(boolean up)
+ {
+ boolean bCommandSent=false;
+ if (mReceiver != null)
+ {
+ if (up == true)
+ {
+ Log.d(LOGTAG, "seek: Up");
+ mReceiver.searchStations(FmReceiver.FM_RX_SRCH_MODE_SEEK,
+ FmReceiver.FM_RX_DWELL_PERIOD_1S,
+ FmReceiver.FM_RX_SEARCHDIR_UP);
+ }
+ else
+ {
+ Log.d(LOGTAG, "seek: Down");
+ mReceiver.searchStations(FmReceiver.FM_RX_SRCH_MODE_SEEK,
+ FmReceiver.FM_RX_DWELL_PERIOD_1S,
+ FmReceiver.FM_RX_SEARCHDIR_DOWN);
+ }
+ bCommandSent = true;
+ }
+ return bCommandSent;
+ }
+
+ /* Scan (Search for station with a "preview" of "n" seconds)
+ * FM Stations. It always scans in the forward direction relative to the
+ * current tuned station.
+ * int pty: 0 or a reserved PTY value- Perform a "strong" station search of all stations.
+ * Valid/Known PTY - perform RDS Scan for that pty.
+ *
+ * @return true if Scan command was invoked successfully, false if not muted.
+ * Note: 1. Callback FmRxEvRadioTuneStatus will be called when tuned to various stations
+ * during the Scan.
+ * 2. Callback FmRxEvSearchComplete will be called when the Search
+ * is complete
+ * 3. Callback FmRxEvRadioTuneStatus will also be called when tuned to a station
+ * at the end of the Search or if the seach was cancelled.
+ *
+ */
+ public boolean scan(int pty)
+ {
+ boolean bCommandSent=false;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "scan: PTY: " + pty);
+ if(FmSharedPreferences.isRBDSStd())
+ {
+ /* RBDS : Validate PTY value?? */
+ if( ((pty > 0) && (pty <= 23)) || ((pty >= 29) && (pty <= 31)) )
+ {
+ bCommandSent = mReceiver.searchStations(FmReceiver.FM_RX_SRCHRDS_MODE_SCAN_PTY,
+ FmReceiver.FM_RX_DWELL_PERIOD_2S,
+ FmReceiver.FM_RX_SEARCHDIR_UP,
+ pty,
+ 0);
+ }
+ else
+ {
+ bCommandSent = mReceiver.searchStations(FmReceiver.FM_RX_SRCH_MODE_SCAN,
+ FmReceiver.FM_RX_DWELL_PERIOD_2S,
+ FmReceiver.FM_RX_SEARCHDIR_UP);
+ }
+ }
+ else
+ {
+ /* RDS : Validate PTY value?? */
+ if( (pty > 0) && (pty <= 31) )
+ {
+ bCommandSent = mReceiver.searchStations(FmReceiver.FM_RX_SRCHRDS_MODE_SCAN_PTY,
+ FmReceiver.FM_RX_DWELL_PERIOD_2S,
+ FmReceiver.FM_RX_SEARCHDIR_UP,
+ pty,
+ 0);
+ }
+ else
+ {
+ bCommandSent = mReceiver.searchStations(FmReceiver.FM_RX_SRCH_MODE_SCAN,
+ FmReceiver.FM_RX_DWELL_PERIOD_2S,
+ FmReceiver.FM_RX_SEARCHDIR_UP);
+ }
+ }
+ }
+ return bCommandSent;
+ }
+
+ /* Search for the 'numStations' number of strong FM Stations.
+ *
+ * It searches in the forward direction relative to the current tuned station.
+ * int numStations: maximum number of stations to search.
+ *
+ * @return true if Search command was invoked successfully, false if not muted.
+ * Note: 1. Callback FmRxEvSearchListComplete will be called when the Search
+ * is complete
+ * 2. Callback FmRxEvRadioTuneStatus will also be called when tuned to
+ * the previously tuned station.
+ */
+ public boolean searchStrongStationList(int numStations)
+ {
+ boolean bCommandSent=false;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "searchStrongStationList: numStations: " + numStations);
+ bCommandSent = mReceiver.searchStationList(FmReceiver.FM_RX_SRCHLIST_MODE_STRONG,
+ FmReceiver.FM_RX_SEARCHDIR_UP,
+ numStations,
+ 0);
+ }
+ return bCommandSent;
+ }
+
+ /* Search for the FM Station that matches the RDS PI (Program Identifier) code.
+ * It always scans in the forward direction relative to the current tuned station.
+ * int piCode: PI Code of the station to search.
+ *
+ * @return true if Search command was invoked successfully, false if not muted.
+ * Note: 1. Callback FmRxEvSearchComplete will be called when the Search
+ * is complete
+ * 2. Callback FmRxEvRadioTuneStatus will also be called when tuned to a station
+ * at the end of the Search or if the seach was cancelled.
+ */
+ public boolean seekPI(int piCode)
+ {
+ boolean bCommandSent=false;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "seekPI: piCode: " + piCode);
+ bCommandSent = mReceiver.searchStations(FmReceiver.FM_RX_SRCHRDS_MODE_SEEK_PI,
+ FmReceiver.FM_RX_DWELL_PERIOD_1S,
+ FmReceiver.FM_RX_SEARCHDIR_UP,
+ 0,
+ piCode
+ );
+ }
+ return bCommandSent;
+ }
+
+
+ /* Cancel any ongoing Search (Seek/Scan/SearchStationList).
+ *
+ * @return true if Search command was invoked successfully, false if not muted.
+ * Note: 1. Callback FmRxEvSearchComplete will be called when the Search
+ * is complete/cancelled.
+ * 2. Callback FmRxEvRadioTuneStatus will also be called when tuned to a station
+ * at the end of the Search or if the seach was cancelled.
+ */
+ public boolean cancelSearch()
+ {
+ boolean bCommandSent=false;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "cancelSearch");
+ bCommandSent = mReceiver.cancelSearch();
+ }
+ return bCommandSent;
+ }
+
+ /* Retrieves the RDS Program Service (PS) String.
+ *
+ * @return String - RDS PS String.
+ * Note: 1. This is a synchronous call that should typically called when
+ * Callback FmRxEvRdsPsInfo is invoked.
+ * 2. Since PS contains multiple fields, this Service reads all the fields and "caches"
+ * the values and provides this helper routine for the Activity to get only the information it needs.
+ * 3. The "cached" data fields are always "cleared" when the tune status changes.
+ */
+ public String getProgramService() {
+ String str = "";
+ if (mFMRxRDSData != null)
+ {
+ str = mFMRxRDSData.getPrgmServices();
+ if(str == null)
+ {
+ str= "";
+ }
+ }
+ Log.d(LOGTAG, "Program Service: [" + str + "]");
+ return str;
+ }
+
+ /* Retrieves the RDS Radio Text (RT) String.
+ *
+ * @return String - RDS RT String.
+ * Note: 1. This is a synchronous call that should typically called when
+ * Callback FmRxEvRdsRtInfo is invoked.
+ * 2. Since RT contains multiple fields, this Service reads all the fields and "caches"
+ * the values and provides this helper routine for the Activity to get only the information it needs.
+ * 3. The "cached" data fields are always "cleared" when the tune status changes.
+ */
+ public String getRadioText() {
+ String str = "";
+ if (mFMRxRDSData != null)
+ {
+ str = mFMRxRDSData.getRadioText();
+ if(str == null)
+ {
+ str= "";
+ }
+ }
+ Log.d(LOGTAG, "Radio Text: [" + str + "]");
+ return str;
+ }
+
+ public String getExtenRadioText() {
+ String str = "";
+ if (mFMRxRDSData != null)
+ {
+ str = mFMRxRDSData.getERadioText();
+ if(str == null)
+ {
+ str= "";
+ }
+ }
+ Log.d(LOGTAG, "eRadio Text:[" + str +"]");
+ return str;
+ }
+ /* Retrieves the RDS Program Type (PTY) code.
+ *
+ * @return int - RDS PTY code.
+ * Note: 1. This is a synchronous call that should typically called when
+ * Callback FmRxEvRdsRtInfo and or FmRxEvRdsPsInfo is invoked.
+ * 2. Since RT/PS contains multiple fields, this Service reads all the fields and "caches"
+ * the values and provides this helper routine for the Activity to get only the information it needs.
+ * 3. The "cached" data fields are always "cleared" when the tune status changes.
+ */
+ public int getProgramType() {
+ int pty = -1;
+ if (mFMRxRDSData != null)
+ {
+ pty = mFMRxRDSData.getPrgmType();
+ }
+ Log.d(LOGTAG, "PTY: [" + pty + "]");
+ return pty;
+ }
+
+ /* Retrieves the RDS Program Identifier (PI).
+ *
+ * @return int - RDS PI code.
+ * Note: 1. This is a synchronous call that should typically called when
+ * Callback FmRxEvRdsRtInfo and or FmRxEvRdsPsInfo is invoked.
+ * 2. Since RT/PS contains multiple fields, this Service reads all the fields and "caches"
+ * the values and provides this helper routine for the Activity to get only the information it needs.
+ * 3. The "cached" data fields are always "cleared" when the tune status changes.
+ */
+ public int getProgramID() {
+ int pi = -1;
+ if (mFMRxRDSData != null)
+ {
+ pi = mFMRxRDSData.getPrgmId();
+ }
+ Log.d(LOGTAG, "PI: [" + pi + "]");
+ return pi;
+ }
+
+
+ /* Retrieves the station list from the SearchStationlist.
+ *
+ * @return Array of integers that represents the station frequencies.
+ * Note: 1. This is a synchronous call that should typically called when
+ * Callback onSearchListComplete.
+ */
+ public int[] getSearchList()
+ {
+ int[] frequencyList = null;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "getSearchList: ");
+ frequencyList = mReceiver.getStationList();
+ }
+ return frequencyList;
+ }
+
+ /* Set the FM Power Mode on the FM hardware SoC.
+ * Typically used when UI/Activity is in the background, so the Host is interrupted less often.
+ *
+ * boolean bLowPower: true: Enable Low Power mode on FM hardware.
+ * false: Disable Low Power mode on FM hardware. (Put into normal power mode)
+ * @return true if set power mode api was invoked successfully, false if the api failed.
+ */
+ public boolean setLowPowerMode(boolean bLowPower)
+ {
+ boolean bCommandSent=false;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "setLowPowerMode: " + bLowPower);
+ if(bLowPower)
+ {
+ bCommandSent = mReceiver.setPowerMode(FmReceiver.FM_RX_LOW_POWER_MODE);
+ }
+ else
+ {
+ bCommandSent = mReceiver.setPowerMode(FmReceiver.FM_RX_NORMAL_POWER_MODE);
+ }
+ }
+ return bCommandSent;
+ }
+
+ /* Get the FM Power Mode on the FM hardware SoC.
+ *
+ * @return the device power mode.
+ */
+ public int getPowerMode()
+ {
+ int powerMode=FmReceiver.FM_RX_NORMAL_POWER_MODE;
+ if (mReceiver != null)
+ {
+ powerMode = mReceiver.getPowerMode();
+ Log.d(LOGTAG, "getLowPowerMode: " + powerMode);
+ }
+ return powerMode;
+ }
+
+ /* Set the FM module to auto switch to an Alternate Frequency for the
+ * station if one the signal strength of that frequency is stronger than the
+ * current tuned frequency.
+ *
+ * boolean bEnable: true: Auto switch to stronger alternate frequency.
+ * false: Do not switch to alternate frequency.
+ *
+ * @return true if set Auto AF mode api was invoked successfully, false if the api failed.
+ * Note: Callback FmRxEvRadioTuneStatus will be called when tune
+ * is complete to a different frequency.
+ */
+ public boolean enableAutoAF(boolean bEnable)
+ {
+ boolean bCommandSent=false;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "enableAutoAF: " + bEnable);
+ bCommandSent = mReceiver.enableAFjump(bEnable);
+ }
+ return bCommandSent;
+ }
+
+ /* Set the FM module to Stereo Mode or always force it to Mono Mode.
+ * Note: The stereo mode will be available only when the station is broadcasting
+ * in Stereo mode.
+ *
+ * boolean bEnable: true: Enable Stereo Mode.
+ * false: Always stay in Mono Mode.
+ *
+ * @return true if set Stereo mode api was invoked successfully, false if the api failed.
+ */
+ public boolean enableStereo(boolean bEnable)
+ {
+ boolean bCommandSent=false;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "enableStereo: " + bEnable);
+ bCommandSent = mReceiver.setStereoMode(bEnable);
+ }
+ return bCommandSent;
+ }
+
+ /** Determines if an internal Antenna is available.
+ * Returns the cached value initialized on FMOn.
+ *
+ * @return true if internal antenna is available or wired
+ * headset is plugged in, false if internal antenna is
+ * not available and wired headset is not plugged in.
+ */
+ public boolean isAntennaAvailable()
+ {
+ boolean bAvailable = false;
+ if ((mInternalAntennaAvailable) || (mHeadsetPlugged) )
+ {
+ bAvailable = true;
+ }
+ return bAvailable;
+ }
+
+ public static long getAvailableSpace() {
+ String state = Environment.getExternalStorageState();
+ Log.d(LOGTAG, "External storage state=" + state);
+ if (Environment.MEDIA_CHECKING.equals(state)) {
+ return PREPARING;
+ }
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ return UNAVAILABLE;
+ }
+
+ try {
+ File sampleDir = Environment.getExternalStorageDirectory();
+ StatFs stat = new StatFs(sampleDir.getAbsolutePath());
+ return stat.getAvailableBlocks() * (long) stat.getBlockSize();
+ } catch (Exception e) {
+ Log.i(LOGTAG, "Fail to access external storage", e);
+ }
+ return UNKNOWN_SIZE;
+ }
+
+ private boolean updateAndShowStorageHint() {
+ mStorageSpace = getAvailableSpace();
+ return showStorageHint();
+ }
+
+ private boolean showStorageHint() {
+ String errorMessage = null;
+ if (mStorageSpace == UNAVAILABLE) {
+ errorMessage = getString(R.string.no_storage);
+ } else if (mStorageSpace == PREPARING) {
+ errorMessage = getString(R.string.preparing_sd);
+ } else if (mStorageSpace == UNKNOWN_SIZE) {
+ errorMessage = getString(R.string.access_sd_fail);
+ } else if (mStorageSpace < LOW_STORAGE_THRESHOLD) {
+ errorMessage = getString(R.string.spaceIsLow_content);
+ }
+
+ if (errorMessage != null) {
+ Toast.makeText(this, errorMessage,
+ Toast.LENGTH_LONG).show();
+ return false;
+ }
+ return true;
+ }
+
+ /** Determines if a Wired headset is plugged in. Returns the
+ * cached value initialized on broadcast receiver
+ * initialization.
+ *
+ * @return true if wired headset is plugged in, false if wired
+ * headset is not plugged in.
+ */
+ public boolean isWiredHeadsetAvailable()
+ {
+ return (mHeadsetPlugged);
+ }
+ public boolean isCallActive()
+ {
+ //Non-zero: Call state is RINGING or OFFHOOK on the available subscriptions
+ //zero: Call state is IDLE on all the available subscriptions
+ if(0 != getCallState()) return true;
+ return false;
+ }
+ public int getCallState()
+ {
+ return mCallStatus;
+ }
+
+ public void clearStationInfo() {
+ if(mFMRxRDSData != null) {
+ mFMRxRDSData.setRadioText("");
+ mFMRxRDSData.setPrgmId(0);
+ mFMRxRDSData.setPrgmType(0);
+ mFMRxRDSData.setPrgmServices("");
+ mFMRxRDSData.setERadioText("");
+ mFMRxRDSData.setTagValue("", 1);
+ mFMRxRDSData.setTagValue("", 2);
+ mFMRxRDSData.setTagCode((byte)0, 1);
+ mFMRxRDSData.setTagCode((byte)0, 2);
+ Log.d(LOGTAG, "clear tags data");
+ FmSharedPreferences.clearTags();
+ }
+ }
+
+ /* Receiver callbacks back from the FM Stack */
+ FmRxEvCallbacksAdaptor fmCallbacks = new FmRxEvCallbacksAdaptor()
+ {
+ public void FmRxEvEnableReceiver() {
+ Log.d(LOGTAG, "FmRxEvEnableReceiver");
+ mReceiver.setRawRdsGrpMask();
+ }
+ public void FmRxEvDisableReceiver()
+ {
+ Log.d(LOGTAG, "FmRxEvDisableReceiver");
+ mFMOn = false;
+ FmSharedPreferences.clearTags();
+ }
+ public void FmRxEvRadioReset()
+ {
+ Log.d(LOGTAG, "FmRxEvRadioReset");
+ if(isFmOn()) {
+ // Received radio reset event while FM is ON
+ Log.d(LOGTAG, "FM Radio reset");
+ fmRadioReset();
+ try
+ {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if((mServiceInUse) && (mCallbacks != null) )
+ {
+ mCallbacks.onRadioReset();
+ }
+ }
+ catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ public void FmRxEvConfigReceiver()
+ {
+ Log.d(LOGTAG, "FmRxEvConfigReceiver");
+ }
+ public void FmRxEvMuteModeSet()
+ {
+ Log.d(LOGTAG, "FmRxEvMuteModeSet");
+ }
+ public void FmRxEvStereoModeSet()
+ {
+ Log.d(LOGTAG, "FmRxEvStereoModeSet");
+ }
+ public void FmRxEvRadioStationSet()
+ {
+ Log.d(LOGTAG, "FmRxEvRadioStationSet");
+ }
+ public void FmRxEvPowerModeSet()
+ {
+ Log.d(LOGTAG, "FmRxEvPowerModeSet");
+ }
+ public void FmRxEvSetSignalThreshold()
+ {
+ Log.d(LOGTAG, "FmRxEvSetSignalThreshold");
+ }
+
+ public void FmRxEvRadioTuneStatus(int frequency)
+ {
+ Log.d(LOGTAG, "FmRxEvRadioTuneStatus: Tuned Frequency: " +frequency);
+ try
+ {
+ FmSharedPreferences.setTunedFrequency(frequency);
+ mPrefs.Save();
+ //Log.d(LOGTAG, "Call mCallbacks.onTuneStatusChanged");
+ /* Since the Tuned Status changed, clear out the RDSData cached */
+ if(mReceiver != null) {
+ clearStationInfo();
+ }
+ if(mCallbacks != null)
+ {
+ mCallbacks.onTuneStatusChanged();
+ }
+ /* Update the frequency in the StatusBar's Notification */
+ startNotification();
+
+ }
+ catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void FmRxEvStationParameters()
+ {
+ Log.d(LOGTAG, "FmRxEvStationParameters");
+ }
+
+ public void FmRxEvRdsLockStatus(boolean bRDSSupported)
+ {
+ Log.d(LOGTAG, "FmRxEvRdsLockStatus: " + bRDSSupported);
+ try
+ {
+ if(mCallbacks != null)
+ {
+ mCallbacks.onStationRDSSupported(bRDSSupported);
+ }
+ }
+ catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void FmRxEvStereoStatus(boolean stereo)
+ {
+ Log.d(LOGTAG, "FmRxEvStereoStatus: " + stereo);
+ try
+ {
+ if(mCallbacks != null)
+ {
+ mCallbacks.onAudioUpdate(stereo);
+ }
+ }
+ catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ public void FmRxEvServiceAvailable(boolean signal)
+ {
+ Log.d(LOGTAG, "FmRxEvServiceAvailable");
+ if(signal) {
+ Log.d(LOGTAG, "FmRxEvServiceAvailable: Tuned frequency is above signal threshold level");
+ }
+ else {
+ Log.d(LOGTAG, "FmRxEvServiceAvailable: Tuned frequency is below signal threshold level");
+ }
+ }
+ public void FmRxEvGetSignalThreshold()
+ {
+ Log.d(LOGTAG, "FmRxEvGetSignalThreshold");
+ }
+ public void FmRxEvSearchInProgress()
+ {
+ Log.d(LOGTAG, "FmRxEvSearchInProgress");
+ }
+ public void FmRxEvSearchRdsInProgress()
+ {
+ Log.d(LOGTAG, "FmRxEvSearchRdsInProgress");
+ }
+ public void FmRxEvSearchListInProgress()
+ {
+ Log.d(LOGTAG, "FmRxEvSearchListInProgress");
+ }
+ public void FmRxEvSearchComplete(int frequency)
+ {
+ Log.d(LOGTAG, "FmRxEvSearchComplete: Tuned Frequency: " +frequency);
+ try
+ {
+ FmSharedPreferences.setTunedFrequency(frequency);
+ //Log.d(LOGTAG, "Call mCallbacks.onSearchComplete");
+ /* Since the Tuned Status changed, clear out the RDSData cached */
+ if(mReceiver != null) {
+ clearStationInfo();
+ }
+ if(mCallbacks != null)
+ {
+ mCallbacks.onSearchComplete();
+ }
+ /* Update the frequency in the StatusBar's Notification */
+ startNotification();
+ }
+ catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void FmRxEvSearchRdsComplete()
+ {
+ Log.d(LOGTAG, "FmRxEvSearchRdsComplete");
+ }
+
+ public void FmRxEvSearchListComplete()
+ {
+ Log.d(LOGTAG, "FmRxEvSearchListComplete");
+ try
+ {
+ if(mCallbacks != null)
+ {
+ mCallbacks.onSearchListComplete();
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void FmRxEvSearchCancelled()
+ {
+ Log.d(LOGTAG, "FmRxEvSearchCancelled: Cancelled the on-going search operation.");
+ }
+ public void FmRxEvRdsGroupData()
+ {
+ Log.d(LOGTAG, "FmRxEvRdsGroupData");
+ }
+
+ public void FmRxEvRdsPsInfo() {
+ Log.d(LOGTAG, "FmRxEvRdsPsInfo: ");
+ try
+ {
+ if(mReceiver != null)
+ {
+ mFMRxRDSData = mReceiver.getPSInfo();
+ if(mFMRxRDSData != null)
+ {
+ Log.d(LOGTAG, "PI: [" + mFMRxRDSData.getPrgmId() + "]");
+ Log.d(LOGTAG, "PTY: [" + mFMRxRDSData.getPrgmType() + "]");
+ Log.d(LOGTAG, "PS: [" + mFMRxRDSData.getPrgmServices() + "]");
+ }
+ if(mCallbacks != null)
+ {
+ mCallbacks.onProgramServiceChanged();
+ }
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void FmRxEvRdsRtInfo() {
+ Log.d(LOGTAG, "FmRxEvRdsRtInfo");
+ try
+ {
+ //Log.d(LOGTAG, "Call mCallbacks.onRadioTextChanged");
+ if(mReceiver != null)
+ {
+ mFMRxRDSData = mReceiver.getRTInfo();
+ if(mFMRxRDSData != null)
+ {
+ Log.d(LOGTAG, "PI: [" + mFMRxRDSData.getPrgmId() + "]");
+ Log.d(LOGTAG, "PTY: [" + mFMRxRDSData.getPrgmType() + "]");
+ Log.d(LOGTAG, "RT: [" + mFMRxRDSData.getRadioText() + "]");
+ }
+ if(mCallbacks != null)
+ {
+ mCallbacks.onRadioTextChanged();
+ }
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+
+ public void FmRxEvRdsAfInfo()
+ {
+ Log.d(LOGTAG, "FmRxEvRdsAfInfo");
+ mReceiver.getAFInfo();
+ }
+ public void FmRxEvRTPlus()
+ {
+ int tag_nums;
+ Log.d(LOGTAG, "FmRxEvRTPlusInfo");
+ if (mReceiver != null) {
+ mFMRxRDSData = mReceiver.getRTPlusInfo();
+ tag_nums = mFMRxRDSData.getTagNums();
+ if (tag_nums >= 1) {
+ Log.d(LOGTAG, "tag1 is: " + mFMRxRDSData.getTagCode(1) + "value: "
+ + mFMRxRDSData.getTagValue(1));
+ FmSharedPreferences.addTags(mFMRxRDSData.getTagCode(1), mFMRxRDSData.getTagValue(1));
+ }
+ if(tag_nums == 2) {
+ Log.d(LOGTAG, "tag2 is: " + mFMRxRDSData.getTagCode(2) + "value: "
+ + mFMRxRDSData.getTagValue(2));
+ FmSharedPreferences.addTags(mFMRxRDSData.getTagCode(2), mFMRxRDSData.getTagValue(2));
+ }
+ }
+ }
+ public void FmRxEvERTInfo()
+ {
+ Log.d(LOGTAG, "FmRxEvERTInfo");
+ try {
+ if (mReceiver != null) {
+ mFMRxRDSData = mReceiver.getERTInfo();
+ if(mCallbacks != null)
+ mCallbacks.onExtenRadioTextChanged();
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ public void FmRxEvRdsPiMatchAvailable()
+ {
+ Log.d(LOGTAG, "FmRxEvRdsPiMatchAvailable");
+ }
+ public void FmRxEvRdsGroupOptionsSet()
+ {
+ Log.d(LOGTAG, "FmRxEvRdsGroupOptionsSet");
+ }
+ public void FmRxEvRdsProcRegDone()
+ {
+ Log.d(LOGTAG, "FmRxEvRdsProcRegDone");
+ }
+ public void FmRxEvRdsPiMatchRegDone()
+ {
+ Log.d(LOGTAG, "FmRxEvRdsPiMatchRegDone");
+ }
+ };
+
+
+ /*
+ * Read the Tuned Frequency from the FM module.
+ */
+ private String getTunedFrequencyString() {
+
+ double frequency = FmSharedPreferences.getTunedFrequency() / 1000.0;
+ String frequencyString = getString(R.string.stat_notif_frequency, (""+frequency));
+ return frequencyString;
+ }
+ public int getRssi() {
+ if (mReceiver != null)
+ return mReceiver.getRssi();
+ else
+ return Integer.MAX_VALUE;
+ }
+ public int getIoC() {
+ if (mReceiver != null)
+ return mReceiver.getIoverc();
+ else
+ return Integer.MAX_VALUE;
+ }
+ public int getIntDet() {
+ if (mReceiver != null)
+ return mReceiver.getIntDet();
+ else
+ return Integer.MAX_VALUE;
+ }
+ public int getMpxDcc() {
+ if (mReceiver != null)
+ return mReceiver.getMpxDcc();
+ else
+ return Integer.MAX_VALUE;
+ }
+ public void setHiLoInj(int inj) {
+ if (mReceiver != null)
+ mReceiver.setHiLoInj(inj);
+ }
+ public int getSINR() {
+ if (mReceiver != null)
+ return mReceiver.getSINR();
+ else
+ return Integer.MAX_VALUE;
+ }
+ public boolean setSinrSamplesCnt(int samplesCnt) {
+ if(mReceiver != null)
+ return mReceiver.setSINRsamples(samplesCnt);
+ else
+ return false;
+ }
+ public boolean setSinrTh(int sinr) {
+ if(mReceiver != null)
+ return mReceiver.setSINRThreshold(sinr);
+ else
+ return false;
+ }
+ public boolean setIntfDetLowTh(int intfLowTh) {
+ if(mReceiver != null)
+ return mReceiver.setOnChannelThreshold(intfLowTh);
+ else
+ return false;
+ }
+ public boolean setIntfDetHighTh(int intfHighTh) {
+ if(mReceiver != null)
+ return mReceiver.setOffChannelThreshold(intfHighTh);
+ else
+ return false;
+ }
+
+ private void setAlarmSleepExpired (long duration) {
+ Intent i = new Intent(SLEEP_EXPIRED_ACTION);
+ AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
+ Log.d(LOGTAG, "delayedStop called" + SystemClock.elapsedRealtime() + duration);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + duration, pi);
+ }
+ private void cancelAlarmSleepExpired() {
+ Intent i = new Intent(SLEEP_EXPIRED_ACTION);
+ AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
+ am.cancel(pi);
+ }
+ private void setAlarmRecordTimeout(long duration) {
+ Intent i = new Intent(RECORD_EXPIRED_ACTION);
+ AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
+ Log.d(LOGTAG, "delayedStop called" + SystemClock.elapsedRealtime() + duration);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + duration, pi);
+ }
+ private void cancelAlarmRecordTimeout() {
+ Intent i = new Intent(RECORD_EXPIRED_ACTION);
+ AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
+ am.cancel(pi);
+ }
+ private void setAlarmDelayedServiceStop() {
+ Intent i = new Intent(SERVICE_DELAYED_STOP_ACTION);
+ AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + IDLE_DELAY, pi);
+ }
+ private void cancelAlarmDealyedServiceStop() {
+ Intent i = new Intent(SERVICE_DELAYED_STOP_ACTION);
+ AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
+ am.cancel(pi);
+ }
+ private void cancelAlarms() {
+ cancelAlarmSleepExpired();
+ cancelAlarmRecordTimeout();
+ cancelAlarmDealyedServiceStop();
+ }
+ //handling the sleep and record stop when FM App not in focus
+ private void delayedStop(long duration, int nType) {
+ int whatId = (nType == STOP_SERVICE) ? STOPSERVICE_ONSLEEP: STOPRECORD_ONTIMEOUT;
+ if (nType == STOP_SERVICE)
+ setAlarmSleepExpired(duration);
+ else
+ setAlarmRecordTimeout(duration);
+ }
+ private void cancelDelayedStop(int nType) {
+ int whatId = (nType == STOP_SERVICE) ? STOPSERVICE_ONSLEEP: STOPRECORD_ONTIMEOUT;
+ if (nType == STOP_SERVICE)
+ cancelAlarmSleepExpired();
+ else
+ cancelAlarmRecordTimeout();
+ }
+ private void requestFocus() {
+ if( (false == mPlaybackInProgress) &&
+ (true == mStoppedOnFocusLoss) ) {
+ // adding code for audio focus gain.
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ audioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ startFM();
+ mStoppedOnFocusLoss = false;
+ }
+ }
+ private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
+ public void onAudioFocusChange(int focusChange) {
+ mDelayedStopHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
+ }
+ };
+}
diff --git a/fmapp2/src/com/caf/fmradio/FMStats.java b/fmapp2/src/com/caf/fmradio/FMStats.java
new file mode 100644
index 0000000..2afb301
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FMStats.java
@@ -0,0 +1,1346 @@
+/*
+ * Copyright (c) 2011-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.Spinner;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import qcom.fmradio.FmReceiver;
+import android.os.SystemProperties;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import android.os.SystemProperties;
+
+
+public class FMStats extends Activity {
+
+ EditText txtbox1;
+ TextView tv1;
+ Button SetButton;
+ Button RunButton;
+ ProgressBar pbar;
+ TableLayout tLayout;
+
+ private FmReceiver mReceiver;
+
+ /*Data structure for band*/
+ private class Band {
+
+ public int lFreq;
+ public int hFreq;
+ public int Spacing;
+ }
+ /* Data structure for Result*/
+ private class Result {
+
+ private String mFreq;
+ private String mRSSI;
+ private String mIoC;
+ private String mIntDet;
+ private String mMpxDcc;
+ private String mSINR;
+
+
+ public void setFreq(String aFreq) {
+ this.mFreq = aFreq;
+ }
+
+ public String getFreq() {
+ return mFreq;
+ }
+
+ public void setRSSI(String aRSSI) {
+ this.mRSSI = aRSSI;
+ }
+
+ public String getRSSI() {
+ return mRSSI;
+ }
+
+ public void setIoC(String aIoC) {
+ this.mIoC = aIoC;
+ }
+
+ public String getIoC() {
+ return mIoC;
+ }
+
+ public void setIntDet(String aIntDet) {
+ this.mIntDet = aIntDet;
+ }
+
+ public String getIntDet() {
+ return mIntDet;
+ }
+
+ public void setMpxDcc(String aMpxDcc) {
+ this.mMpxDcc = aMpxDcc;
+ }
+
+ public String getMpxDcc() {
+ return mMpxDcc;
+ }
+ public void setSINR(String aSINR) {
+ this.mSINR = aSINR;
+ }
+
+ public String getSINR() {
+ return mSINR;
+ }
+
+ };
+
+ /*constant column header*/
+ Result mColumnHeader = new Result();
+
+ boolean mTestRunning = false;
+ FmRfItemSelectedListener mSpinFmRfListener = new FmRfItemSelectedListener();
+ RfCfgItemSelectedListener mSpinRfCfgListener = new RfCfgItemSelectedListener();
+ CfgRfItemSelectedListener1 mSpinCfgRfListener1 = null;
+ CfgRfItemSelectedListener2 mSpinCfgRfListener2 = null;
+
+
+ int mTestSelected = 0;
+ boolean mIsSearching = false;
+ private static String LOGTAG = "FMStats";
+ private static IFMRadioService mService = null;
+ private Thread mMultiUpdateThread =null;
+ private static final int STATUS_UPDATE =1;
+ private static final int STATUS_DONE =2;
+ private static final int STOP_ROW_ID =200;
+ private static final int NEW_ROW_ID = 300;
+ private int mStopIds = STOP_ROW_ID;
+ private int mNewRowIds = NEW_ROW_ID;
+ private static final int SCAN_DWELL_PERIOD = 1;
+
+ private static final int CUR_FREQ_TEST =0;
+ private static final int CUR_MULTI_TEST = 1;
+ private static final int SEARCH_TEST =2;
+ private static final int SWEEP_TEST =3;
+ private Band mBand =null;
+ private Band mSync = null;
+ int Lo = 1, Auto = 0;
+
+ private FileOutputStream mFileCursor =null;
+ private String mCurrentFileName = null;
+
+ Spinner spinOptionFmRf;
+ ArrayAdapter<CharSequence> adaptCfgRf;
+ ArrayAdapter<CharSequence> adaptRfCfg;
+ ArrayAdapter<CharSequence> adaptFmRf;
+
+ private static boolean mIsTransportSMD = false;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.setContentView(R.layout.fmstats);
+
+ spinOptionFmRf = (Spinner) findViewById(R.id.spinner);
+ adaptFmRf = ArrayAdapter.createFromResource(
+ this, R.array.stats_options, android.R.layout.simple_spinner_item);
+ adaptFmRf.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ if (spinOptionFmRf != null) {
+ spinOptionFmRf.setAdapter(adaptFmRf);
+ spinOptionFmRf.setOnItemSelectedListener(mSpinFmRfListener);
+ }
+
+ checkTransportLayer();
+ if (!isTransportLayerSMD()) {
+ mSpinCfgRfListener1 = new CfgRfItemSelectedListener1();
+ adaptCfgRf = ArrayAdapter.createFromResource(
+ this, R.array.cfg_rf1,
+ android.R.layout.simple_spinner_item);
+ } else {
+ mSpinCfgRfListener2 = new CfgRfItemSelectedListener2();
+ adaptCfgRf = ArrayAdapter.createFromResource(
+ this, R.array.cfg_rf2,
+ android.R.layout.simple_spinner_item);
+ }
+ adaptRfCfg = ArrayAdapter.createFromResource(
+ this, R.array.rf_cfg, android.R.layout.simple_spinner_item);
+
+ tLayout = (TableLayout) findViewById(R.id.maintable);
+
+ if(mReceiver == null)
+ mReceiver = new FmReceiver();
+
+ long curTime = System.currentTimeMillis();
+ mCurrentFileName = "FMStats_".concat(Long.toString(curTime).concat(".txt"));
+ Log.e(LOGTAG,"Filename is "+mCurrentFileName);
+ try {
+ mFileCursor = openFileOutput(mCurrentFileName, Context.MODE_PRIVATE);
+ if(null != mFileCursor) {
+ Log.e(LOGTAG, "location of the file is"+getFilesDir());
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e(LOGTAG,"Couldn't create the file to writeLog");
+ mCurrentFileName = null;
+ }
+
+ if (false == bindToService(this, osc))
+ {
+ Log.d(LOGTAG, "onCreate: Failed to Start Service");
+ }
+ else
+ {
+ Log.d(LOGTAG, "onCreate: Start Service completed successfully");
+ }
+
+ /*Initialize the column header with
+ constant values*/
+ mColumnHeader.setFreq("Freq");
+ mColumnHeader.setRSSI("RMSSI");
+ mColumnHeader.setIoC("IoC");
+ mColumnHeader.setSINR("SINR");
+ mColumnHeader.setMpxDcc("Offset");
+ mColumnHeader.setIntDet("IntDet");
+ }
+
+ public void onDestroy() {
+ if(null != mFileCursor ) {
+ try {
+ mFileCursor.close();
+ } catch (IOException e) {
+
+ e.printStackTrace();
+ }
+ }
+ /*Stop the thread by interrupting it*/
+ if(mMultiUpdateThread != null) {
+ mMultiUpdateThread.interrupt();
+ mMultiUpdateThread = null;
+ }
+ /*Stop the search/scan if there is an ongoing*/
+ if(SEARCH_TEST == mTestSelected)
+ {
+ Log.d(LOGTAG, "Stop Search\n");
+ try {
+ mService.cancelSearch();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ unbindFromService(this);
+ Log.d(LOGTAG, "onDestroy: unbindFromService completed");
+ mReceiver = null;
+ mService = null;
+ super.onDestroy();
+ }
+
+ private View.OnClickListener mOnRunListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ if(false == mTestRunning)
+ {
+ clearPreviousTestResults();
+ mTestRunning = true;
+ runCurrentTest();
+ }
+ else
+ {
+ mTestRunning = false;
+ /*Set it back to ready to Run*/
+ SetButtonState(true);
+ /*Stop the thread by interrupting it*/
+ if(mMultiUpdateThread != null) {
+ mMultiUpdateThread.interrupt();
+ mMultiUpdateThread = null;
+ }
+
+ if(SEARCH_TEST == mTestSelected )
+ {
+ try {
+ mService.cancelSearch();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ };
+
+ private void clearPreviousTestResults()
+ {
+ TableLayout tl = (TableLayout) findViewById(R.id.maintable);
+ if (tl != null) {
+ tl.removeAllViewsInLayout();
+ }
+ mNewRowIds = NEW_ROW_ID;
+ }
+
+
+ private void SetButtonState(boolean state)
+ {
+ // Get the TableRow
+ Button RunButton = (Button)findViewById(R.id.Runbutton);
+ ProgressBar pbar = (ProgressBar) findViewById(R.id.progressbar);
+ /*Update the state of the button based on
+ state*/
+ if( state )
+ {
+ if (RunButton != null) {
+ RunButton.setText(R.string.test_run);
+ }
+ if (pbar != null) {
+ pbar.setVisibility(View.INVISIBLE);
+ }
+ }
+ else
+ {
+ if (RunButton != null) {
+ RunButton.setText("Stop Test");
+ }
+ if (pbar != null) {
+ pbar.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ private void chooseFMRFoption(){
+ String[] szTestInformation = getResources().getStringArray(
+ R.array.stats_options);
+ final StringBuilder szbTestHeader = new StringBuilder();
+ szbTestHeader.append("running test:").append(szTestInformation[mTestSelected]);
+ String szTestHeader = new String(szbTestHeader);
+ switch(mTestSelected){
+ case 1:
+ RunButton = (Button)findViewById(R.id.Runbutton);
+ if (RunButton != null) {
+ RunButton.setVisibility(View.INVISIBLE);
+ }
+ pbar = (ProgressBar) findViewById(R.id.progressbar);
+ if (pbar != null) {
+ pbar.setVisibility(View.INVISIBLE);
+ }
+ adaptCfgRf.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinOptionFmRf.setAdapter(adaptCfgRf);
+ if(isTransportLayerSMD())
+ spinOptionFmRf.setOnItemSelectedListener(mSpinCfgRfListener2);
+ else
+ spinOptionFmRf.setOnItemSelectedListener(mSpinCfgRfListener1);
+ break;
+ case 2:
+ txtbox1 = (EditText) findViewById(R.id.txtbox1);
+ tv1 = (TextView) findViewById(R.id.label);
+ if (txtbox1 != null) {
+ txtbox1.setVisibility(View.INVISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setVisibility(View.INVISIBLE);
+ }
+ Button SetButton = (Button)findViewById(R.id.Setbutton);
+ if (SetButton != null) {
+ SetButton.setVisibility(View.INVISIBLE);
+ }
+ adaptRfCfg.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinOptionFmRf.setAdapter(adaptRfCfg);
+ spinOptionFmRf.setOnItemSelectedListener(mSpinRfCfgListener);
+ break;
+ }
+ }
+
+ private View.OnClickListener mOnSetRmssitListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ String a;
+ a = txtbox1.getText().toString();
+ try {
+ int rdel = Integer.parseInt(a);
+ Log.d(LOGTAG, "Value of RMSSI DELTA is : " + rdel);
+ mReceiver.setRmssiDel(rdel);
+ } catch (NumberFormatException e) {
+ Log.e(LOGTAG, "Value entered is not in correct format: " + a);
+ txtbox1.setText("");
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ private View.OnClickListener mOnSetSigThListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ String a;
+ a = txtbox1.getText().toString();
+ try {
+ int rdel = Integer.parseInt(a);
+ Log.d(LOGTAG, "Value of Signal Th. is : " + rdel);
+ mReceiver.setSignalThreshold(rdel);
+ } catch (NumberFormatException e) {
+ Log.e(LOGTAG, "Value entered is not in correct format: " + a);
+ txtbox1.setText("");
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ private View.OnClickListener mOnSetSinrSmplCntListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ String a;
+ a = txtbox1.getText().toString();
+ try {
+ int rdel = Integer.parseInt(a);
+ Log.d(LOGTAG, "Value of Sinr Samples count is : " + rdel);
+ if(mService != null) {
+ try {
+ mService.setSinrSamplesCnt(rdel);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ } catch (NumberFormatException e) {
+ Log.e(LOGTAG, "Value entered is not in correct format: " + a);
+ txtbox1.setText("");
+ }
+ }
+ };
+ private View.OnClickListener mOnSetSinrThListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ String a;
+ a = txtbox1.getText().toString();
+ try {
+ int rdel = Integer.parseInt(a);
+ Log.d(LOGTAG, "Value of Sinr Th is : " + rdel);
+ if(mService != null) {
+ try {
+ mService.setSinrTh(rdel);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ } catch (NumberFormatException e) {
+ Log.e(LOGTAG, "Value entered is not in correct format: " + a);
+ txtbox1.setText("");
+ }
+ }
+ };
+ private View.OnClickListener mOnSetIntfLowThListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ String a;
+ a = txtbox1.getText().toString();
+ try {
+ int rdel = Integer.parseInt(a);
+ Log.d(LOGTAG, "Value of Intf Det Low Th is : " + rdel);
+ if(mService != null) {
+ try {
+ mService.setIntfDetLowTh(rdel);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ } catch (NumberFormatException e) {
+ Log.e(LOGTAG, "Value entered is not in correct format: " + a);
+ txtbox1.setText("");
+ }
+ }
+ };
+ private View.OnClickListener mOnSetIntfHighThListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ String a;
+ a = txtbox1.getText().toString();
+ try {
+ int rdel = Integer.parseInt(a);
+ Log.d(LOGTAG, "Value of Intf Det Low Th is : " + rdel);
+ if(mService != null) {
+ try {
+ mService.setIntfDetHighTh(rdel);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ } catch (NumberFormatException e) {
+ Log.e(LOGTAG, "Value entered is not in correct format : " + a);
+ txtbox1.setText("");
+ }
+ }
+ };
+
+ public class CfgRfItemSelectedListener1 implements OnItemSelectedListener {
+ public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+ Log.d("Table","onItemSelected is hit with " + pos);
+ txtbox1 = (EditText) findViewById(R.id.txtbox1);
+ tv1 = (TextView) findViewById(R.id.label);
+ Button SetButton = (Button)findViewById(R.id.Setbutton);
+ tLayout.setVisibility(View.INVISIBLE);
+ switch(pos)
+ {
+ case 0:
+ if (txtbox1 != null) {
+ txtbox1.setText(R.string.type_rd);
+ txtbox1.setVisibility(View.VISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setText(R.string.enter_rssi);
+ tv1.setVisibility(View.VISIBLE);
+ }
+ if (SetButton != null) {
+ SetButton.setText(R.string.set_rmmsi_delta);
+ SetButton.setVisibility(View.VISIBLE);
+ SetButton.setOnClickListener(mOnSetRmssitListener);
+ }
+ break;
+ case 1:
+ if (txtbox1 != null) {
+ txtbox1.setText(R.string.type_rd);
+ txtbox1.setVisibility(View.VISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setText(R.string.enter_sigth);
+ tv1.setVisibility(View.VISIBLE);
+ }
+ if (SetButton != null) {
+ SetButton.setText(R.string.set_sigth);
+ SetButton.setVisibility(View.VISIBLE);
+ SetButton.setOnClickListener(mOnSetSigThListener);
+ }
+ break;
+ case 2:
+ tLayout.removeAllViewsInLayout();
+ mNewRowIds = NEW_ROW_ID;
+ tLayout.setVisibility(View.VISIBLE);
+ if (txtbox1 != null) {
+ txtbox1.setVisibility(View.INVISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setVisibility(View.INVISIBLE);
+ }
+ if (SetButton != null) {
+ SetButton.setVisibility(View.INVISIBLE);
+ }
+ adaptRfCfg.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinOptionFmRf.setAdapter(adaptRfCfg);
+ spinOptionFmRf.setOnItemSelectedListener(mSpinRfCfgListener);
+ break;
+ }
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do Nothing
+ }
+ }
+
+ public class CfgRfItemSelectedListener2 implements OnItemSelectedListener {
+ public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+ Log.d("Table","onItemSelected is hit with " + pos);
+ txtbox1 = (EditText) findViewById(R.id.txtbox1);
+ tv1 = (TextView) findViewById(R.id.label);
+ Button SetButton = (Button)findViewById(R.id.Setbutton);
+ tLayout.setVisibility(View.INVISIBLE);
+ switch(pos)
+ {
+ case 0:
+ if (txtbox1 != null) {
+ txtbox1.setText(R.string.type_rd);
+ txtbox1.setVisibility(View.VISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setText(R.string.enter_SinrSmplsCnt);
+ tv1.setVisibility(View.VISIBLE);
+ }
+ if (SetButton != null) {
+ SetButton.setText(R.string.set_SinrSmplsCnt);
+ SetButton.setVisibility(View.VISIBLE);
+ SetButton.setOnClickListener(mOnSetSinrSmplCntListener);
+ }
+ break;
+ case 1:
+ if (txtbox1 != null) {
+ txtbox1.setText(R.string.type_rd);
+ txtbox1.setVisibility(View.VISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setText(R.string.enter_SinrTh);
+ tv1.setVisibility(View.VISIBLE);
+ }
+ if (SetButton != null) {
+ SetButton.setText(R.string.set_SinrTh);
+ SetButton.setVisibility(View.VISIBLE);
+ SetButton.setOnClickListener(mOnSetSinrThListener);
+ }
+ break;
+ case 2:
+ if (txtbox1 != null) {
+ txtbox1.setText(R.string.type_rd);
+ txtbox1.setVisibility(View.VISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setText(R.string.enter_IntfLowTh);
+ tv1.setVisibility(View.VISIBLE);
+ }
+ if (SetButton != null) {
+ SetButton.setText(R.string.set_IntfLowTh);
+ SetButton.setVisibility(View.VISIBLE);
+ SetButton.setOnClickListener(mOnSetIntfLowThListener);
+ }
+ break;
+ case 3:
+ if (txtbox1 != null) {
+ txtbox1.setText(R.string.type_rd);
+ txtbox1.setVisibility(View.VISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setText(R.string.enter_IntfHighTh);
+ tv1.setVisibility(View.VISIBLE);
+ }
+ if (SetButton != null) {
+ SetButton.setText(R.string.set_IntfHighTh);
+ SetButton.setVisibility(View.VISIBLE);
+ SetButton.setOnClickListener(mOnSetIntfHighThListener);
+ }
+ break;
+ case 4:
+ tLayout.removeAllViewsInLayout();
+ mNewRowIds = NEW_ROW_ID;
+ tLayout.setVisibility(View.VISIBLE);
+ if (txtbox1 != null) {
+ txtbox1.setVisibility(View.INVISIBLE);
+ }
+ if (tv1 != null) {
+ tv1.setVisibility(View.INVISIBLE);
+ }
+ if (SetButton != null) {
+ SetButton.setVisibility(View.INVISIBLE);
+ }
+ adaptRfCfg.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinOptionFmRf.setAdapter(adaptRfCfg);
+ spinOptionFmRf.setOnItemSelectedListener(mSpinRfCfgListener);
+ break;
+ }
+ }
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do Nothing
+ }
+ }
+
+ public class RfCfgItemSelectedListener implements OnItemSelectedListener {
+ public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+ Log.d("Table","onItemSelected is hit with "+pos);
+ tLayout.setVisibility(View.INVISIBLE);
+ if (mTestRunning)
+ stopCurTest();
+ switch(pos)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ mTestSelected = pos;
+ tLayout.removeAllViewsInLayout();
+ mNewRowIds = NEW_ROW_ID;
+ tLayout.setVisibility(View.VISIBLE);
+ RunButton = (Button)findViewById(R.id.Runbutton);
+ if (RunButton != null) {
+ RunButton.setText(R.string.test_run);
+ RunButton.setVisibility(View.VISIBLE);
+ RunButton.setOnClickListener(mOnRunListener);
+ }
+ break;
+ case 4:
+ RunButton = (Button)findViewById(R.id.Runbutton);
+ if (RunButton != null) {
+ RunButton.setVisibility(View.INVISIBLE);
+ }
+ pbar = (ProgressBar) findViewById(R.id.progressbar);
+ if (pbar != null) {
+ pbar.setVisibility(View.INVISIBLE);
+ }
+ adaptCfgRf.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinOptionFmRf.setAdapter(adaptCfgRf);
+ if(isTransportLayerSMD())
+ spinOptionFmRf.setOnItemSelectedListener(mSpinCfgRfListener2);
+ else
+ spinOptionFmRf.setOnItemSelectedListener(mSpinCfgRfListener1);
+ break;
+ }
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do Nothing
+ }
+ }
+ public class FmRfItemSelectedListener implements OnItemSelectedListener {
+ public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+ Log.d("Table","onItemSelected is hit with "+pos);
+ mTestSelected = pos;
+ tLayout.setVisibility(View.INVISIBLE);
+ chooseFMRFoption();
+
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do Nothing
+ }
+ }
+ private void checkTransportLayer() {
+ String transportLayer = "";
+
+ transportLayer = SystemProperties.get("ro.qualcomm.bt.hci_transport");
+ if(transportLayer.equals("smd"))
+ mIsTransportSMD = true;
+ }
+ private boolean isTransportLayerSMD() {
+ return mIsTransportSMD;
+ }
+ private void createResult(Result aRes) {
+ // Get the TableLayout
+ TableLayout tl = (TableLayout) findViewById(R.id.maintable);
+ if (tl == null) {
+ return;
+ }
+
+ /* Create a new row to be added. */
+ mNewRowIds++;
+ TableRow tr2 = new TableRow(getApplicationContext());
+ int width = getWindowManager().getDefaultDisplay().getWidth();
+ tr2.setLayoutParams(new LayoutParams(
+ LayoutParams.FILL_PARENT,
+ LayoutParams.WRAP_CONTENT));
+ tr2.setId(mNewRowIds);
+ /* Create a Button to be the row-content. */
+ TextView colFreq = new TextView(getApplicationContext());
+ colFreq.setText(aRes.getFreq());
+ colFreq.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
+ colFreq.setWidth(width/4);
+ /* Add Button to row. */
+ tr2.addView(colFreq);
+
+ TextView colRMSSI = new TextView(getApplicationContext());
+ colRMSSI.setText(aRes.getRSSI());
+ colRMSSI.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
+ colRMSSI.setWidth(width/4);
+ tr2.addView(colRMSSI);
+
+ TextView colIoC = new TextView(getApplicationContext());
+ colIoC.setText(aRes.getIoC());
+ colIoC.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
+ colIoC.setWidth(width/4);
+ tr2.addView(colIoC);
+ if(isTransportLayerSMD())
+ {
+ TextView colSINR = new TextView(getApplicationContext());
+ colSINR.setText(aRes.getSINR());
+ colSINR.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
+ colSINR.setWidth(width/4);
+ tr2.addView(colSINR);
+ } else
+ {
+ TextView colMpxDcc = new TextView(getApplicationContext());
+ colMpxDcc.setText(aRes.getMpxDcc());
+ colMpxDcc.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
+ colMpxDcc.setWidth(width/4);
+ tr2.addView(colMpxDcc);
+ }
+ /* Add row to TableLayout. */
+ /* Add row to TableLayout. */
+ tl.addView(tr2,new TableLayout.LayoutParams(
+ LayoutParams.FILL_PARENT,
+ LayoutParams.WRAP_CONTENT));
+ if(null != mFileCursor)
+ {
+ try {
+ StringBuilder tempStr = new StringBuilder();
+ tempStr.append(String.format("%10s", aRes.getFreq()));
+ tempStr.append(String.format("%10s", aRes.getRSSI()));
+ tempStr.append(String.format("%10s", aRes.getIoC()));
+ tempStr.append(String.format("%10s", aRes.getIntDet()));
+ if(isTransportLayerSMD())
+ {
+ tempStr.append(String.format("%10s", aRes.getSINR()));
+ } else
+ {
+ tempStr.append(String.format("%10s", aRes.getMpxDcc()));
+ }
+ tempStr.append("\r\n");
+ String testStr = new String(tempStr);
+ mFileCursor.write(testStr.getBytes());
+ } catch(IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+
+ private void runCurrentTest(){
+ Log.d(LOGTAG, "The test being run is" +mTestSelected);
+
+ //get test summary
+ String[] szTestInformation = getResources().getStringArray(
+ R.array.rf_cfg);
+ final StringBuilder szbTestHeader = new StringBuilder();
+ szbTestHeader.append("running test:").append(szTestInformation[mTestSelected]);
+ szbTestHeader.append("\r\n");
+ String szTestHeader = new String(szbTestHeader);
+ if(null != mFileCursor )
+ {
+ try {
+ mFileCursor.write(szTestHeader.getBytes());
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+ switch(mTestSelected){
+ case CUR_FREQ_TEST:
+ Log.d(LOGTAG,"Current Freq test is going to run");
+ int freq = FmSharedPreferences.getTunedFrequency();
+ Result res = GetFMStatsForFreq(freq);
+ createResult(mColumnHeader);
+ if(res != null)
+ createResult(res);
+ mTestRunning = false;
+ break;
+ case CUR_MULTI_TEST:
+ /*Set it to ready to Stop*/
+ SetButtonState(false);
+ createResult(mColumnHeader);
+
+ if (mMultiUpdateThread == null)
+ {
+ mMultiUpdateThread = new Thread(null, getMultipleResults,
+ "MultiResultsThread");
+ }
+ /* Launch dummy thread to simulate the transfer progress */
+ Log.d(LOGTAG, "Thread State: " + mMultiUpdateThread.getState());
+ if (mMultiUpdateThread.getState() == Thread.State.TERMINATED)
+ {
+ mMultiUpdateThread = new Thread(null, getMultipleResults,
+ "MultiResultsThread");
+ }
+ /* If the thread state is "new" then the thread has not yet started */
+ if (mMultiUpdateThread.getState() == Thread.State.NEW)
+ {
+ mMultiUpdateThread.start();
+ }
+ // returns and UI in different thread.
+ break;
+ case SEARCH_TEST:
+ try {
+ Log.d(LOGTAG, "start scanning\n");
+ if(isTransportLayerSMD()) {
+ Log.d(LOGTAG,"Scanning with 0 scan time");
+ if (mReceiver != null)
+ mIsSearching = mReceiver.searchStations(FmReceiver.FM_RX_SRCH_MODE_SCAN,
+ SCAN_DWELL_PERIOD, FmReceiver.FM_RX_SEARCHDIR_UP);
+ } else {
+ mIsSearching = mService.scan(0);
+ }
+ } catch (RemoteException e) {
+
+ e.printStackTrace();
+ }
+
+ if(mIsSearching)
+ {
+ /*Set it to Ready to Stop*/
+ SetButtonState(false);
+ createResult(mColumnHeader);
+ Log.d(LOGTAG, "Created the results and cancel UI\n");
+ }
+ else
+ {
+ mTestRunning = false;
+ }
+ break;
+ case SWEEP_TEST:
+ int Spacing = FmSharedPreferences.getChSpacing();
+ int lowerFreq = FmSharedPreferences.getLowerLimit();
+ int higherFreq = FmSharedPreferences.getUpperLimit();
+ try {
+ Log.d(LOGTAG, "Going to set low side injection\n");
+ mService.setHiLoInj(Lo);
+ } catch (RemoteException e) {
+
+ e.printStackTrace();
+ }
+ /* Set it to Ready to stop*/
+ SetButtonState(false);
+ createResult(mColumnHeader);
+ getFMStatsInBand(lowerFreq,higherFreq,Spacing);
+ break;
+ }
+ }
+
+ /* Thread processing */
+ private Runnable getMultipleResults = new Runnable() {
+ public void run() {
+ /*Collect the data for the current frequency
+ 20 times*/
+ int freq = FmSharedPreferences.getTunedFrequency();
+
+ for (int i = 0; i < 20; i++)
+ {
+ try
+ {
+ Thread.sleep(500);
+ Message updateUI = new Message();
+ updateUI.what = STATUS_UPDATE;
+ updateUI.obj = (Object)GetFMStatsForFreq(freq);
+ if (updateUI.obj == null)
+ break;
+ mUIUpdateHandlerHandler.sendMessage(updateUI);
+ } catch (InterruptedException e)
+ {
+ /*break the loop*/
+ break;
+ }
+ }
+ mTestRunning = false;
+ Message updateStop = new Message();
+ updateStop.what = STATUS_DONE;
+ mUIUpdateHandlerHandler.sendMessage(updateStop);
+ }
+ };
+
+ private void getFMStatsInBand(int lFreq, int hFreq, int Spacing)
+ {
+ if( null == mBand) {
+ mBand = new Band();
+ }
+ mBand.lFreq = lFreq;
+ mBand.hFreq = hFreq;
+ if(Spacing == 0)
+ {
+ mBand.Spacing = 200; // 200KHz
+ }
+ else if( Spacing == 1)
+ {
+ mBand.Spacing = 100; // 100KHz
+ }
+ else
+ {
+ mBand.Spacing = 50;
+ }
+
+ if (mMultiUpdateThread == null)
+ {
+ mMultiUpdateThread = new Thread(null, getSweepResults,
+ "MultiResultsThread");
+ }
+ /* Launch he dummy thread to simulate the transfer progress */
+ Log.d(LOGTAG, "Thread State: " + mMultiUpdateThread.getState());
+ if (mMultiUpdateThread.getState() == Thread.State.TERMINATED)
+ {
+ mMultiUpdateThread = new Thread(null, getSweepResults,
+ "MultiResultsThread");
+ }
+ /* If the thread state is "new" then the thread has not yet started */
+ if (mMultiUpdateThread.getState() == Thread.State.NEW)
+ {
+ mMultiUpdateThread.start();
+ }
+ }
+
+ /* Thread processing */
+ private Runnable getSweepResults = new Runnable() {
+ public void run() {
+ for (int i = mBand.lFreq; (i <= mBand.hFreq) && (mService != null);
+ i += mBand.Spacing)
+ {
+ try {
+ if (!mService.tune(i)) {
+ Log.e(LOGTAG, "tune failed");
+ break;
+ }
+ mSync = new Band();
+ synchronized(mSync) {
+ mSync.wait(); //wait till notified
+ }
+ mSync = null;
+ Message updateUI = new Message();
+ updateUI.what = STATUS_UPDATE;
+ updateUI.obj = (Object)GetFMStatsForFreq(i);
+ if (updateUI.obj == null) {
+ break;
+ } else {
+ mUIUpdateHandlerHandler.sendMessage(updateUI);
+ Log.d(LOGTAG,"highFerq is "+mBand.hFreq);
+ }
+ }
+ catch (RemoteException e) {
+ Log.e(LOGTAG, "SweepResults:Tune failed\n");
+ }
+
+ catch (InterruptedException e) {
+ /*Stop the thrad*/
+ break;
+ }
+ }
+ mTestRunning = false;
+ Message updateStop = new Message();
+ updateStop.what = STATUS_DONE;
+ try {
+ Log.d(LOGTAG, "Going to set auto hi-lo injection\n");
+ mService.setHiLoInj(Auto);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mUIUpdateHandlerHandler.sendMessage(updateStop);
+ }
+ };
+
+ private Result GetFMStatsForFreq(int freq)
+ {
+ Result result = new Result();
+ Log.d(LOGTAG,"freq is "+freq);
+ result.setFreq(Integer.toString(freq));
+ byte nRssi = 0;
+ int nIoC = 0;
+ int dummy = 0;
+ int nIntDet = 0;
+ int nMpxDcc = 0;
+ byte nSINR = 0;
+ if((null != mService)) {
+ try {
+ dummy = mService.getRssi();
+ if (dummy != Integer.MAX_VALUE) {
+ nRssi = (byte)dummy;
+ result.setRSSI(Byte.toString(nRssi));
+ } else {
+ return null;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ nIoC = mService.getIoC();
+ if (nIoC != Integer.MAX_VALUE)
+ result.setIoC(Integer.toString(nIoC));
+ else
+ return null;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+
+ if(isTransportLayerSMD()) {
+ try {
+ dummy = mService.getSINR();
+ if (dummy != Integer.MAX_VALUE) {
+ nSINR = (byte)dummy;
+ result.setSINR(Integer.toString(nSINR));
+ } else {
+ return null;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ } else {
+ try {
+ nMpxDcc = mService.getMpxDcc();
+ if (nMpxDcc != Integer.MAX_VALUE)
+ result.setMpxDcc(Integer.toString(nMpxDcc));
+ else
+ return null;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ try {
+ nIntDet = mService.getIntDet();
+ if (nIntDet != Integer.MAX_VALUE)
+ result.setIntDet(Integer.toString(nIntDet));
+ else
+ return null;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ } else {
+ return null;
+ }
+ return result;
+ }
+
+
+ private Handler mUIUpdateHandlerHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what)
+ {
+ case STATUS_UPDATE:
+ Result myRes = (Result) msg.obj;
+ Log.d(LOGTAG,"Status update is" +myRes.mFreq);
+ createResult(myRes);
+ break;
+ case STATUS_DONE:
+ SetButtonState(true);
+ break;
+ }
+ }
+ };
+
+ public static IFMRadioService sService = null;
+ private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>();
+
+ public static boolean bindToService(Context context) {
+ Log.e(LOGTAG, "bindToService: Context");
+ return bindToService(context, null);
+ }
+
+ public static boolean bindToService(Context context, ServiceConnection callback) {
+ Log.e(LOGTAG, "bindToService: Context with serviceconnection callback");
+ context.startService(new Intent(context, FMRadioService.class));
+ ServiceBinder sb = new ServiceBinder(callback);
+ sConnectionMap.put(context, sb);
+ return context.bindService((new Intent()).setClass(context,
+ FMRadioService.class), sb, 0);
+ }
+
+ public static void unbindFromService(Context context) {
+ ServiceBinder sb = (ServiceBinder) sConnectionMap.remove(context);
+ Log.e(LOGTAG, "unbindFromService: Context");
+ if (sb == null)
+ {
+ Log.e(LOGTAG, "Trying to unbind for unknown Context");
+ return;
+ }
+ context.unbindService(sb);
+ if (sConnectionMap.isEmpty())
+ {
+ // presumably there is nobody interested in the service at this point,
+ // so don't hang on to the ServiceConnection
+ sService = null;
+ }
+ }
+
+ private static class ServiceBinder implements ServiceConnection
+ {
+ ServiceConnection mCallback;
+ ServiceBinder(ServiceConnection callback) {
+ mCallback = callback;
+ }
+
+ public void onServiceConnected(ComponentName className, android.os.IBinder service) {
+ sService = IFMRadioService.Stub.asInterface(service);
+ if (mCallback != null)
+ {
+ Log.e(LOGTAG, "onServiceConnected: mCallback");
+ mCallback.onServiceConnected(className, service);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ if (mCallback != null)
+ {
+ mCallback.onServiceDisconnected(className);
+ }
+ sService = null;
+ }
+ }
+
+
+ private ServiceConnection osc = new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ mService = IFMRadioService.Stub.asInterface(obj);
+ Log.e(LOGTAG, "ServiceConnection: onServiceConnected: ");
+ if (mService != null)
+ {
+ try
+ {
+ mService.registerCallbacks(mServiceCallbacks);
+
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ return;
+ } else
+ {
+ Log.e(LOGTAG, "IFMRadioService onServiceConnected failed");
+ }
+ finish();
+ }
+ public void onServiceDisconnected(ComponentName classname) {
+ }
+ };
+
+
+ private IFMRadioServiceCallbacks.Stub mServiceCallbacks = new IFMRadioServiceCallbacks.Stub()
+ {
+ public void onEnabled()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onEnabled :");
+ }
+
+ public void onDisabled()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onDisabled :");
+ stopCurTest();
+ }
+
+ public void onRadioReset()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onRadioReset :");
+ }
+
+ public void onTuneStatusChanged()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onTuneStatusChanged :");
+ if (mTestRunning)
+ mHandler.post(mTuneComplete);
+ }
+
+ public void onProgramServiceChanged()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onProgramServiceChanged :");
+ }
+
+ public void onRadioTextChanged()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onRadioTextChanged :");
+ }
+ public void onExtenRadioTextChanged()
+ {
+ Log.d(LOGTAG, "Extended Radio Text changed:");
+ }
+ public void onAlternateFrequencyChanged()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onAlternateFrequencyChanged :");
+ }
+
+ public void onSignalStrengthChanged()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onSignalStrengthChanged :");
+ }
+
+ public void onSearchComplete()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onSearchComplete :");
+ /*Stop the update*/
+ mTestRunning = false;
+ Message updateStop = new Message();
+ updateStop.what = STATUS_DONE;
+ mUIUpdateHandlerHandler.sendMessage(updateStop);
+ }
+ public void onSearchListComplete()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onSearchListComplete :");
+
+ }
+
+ public void onMute(boolean bMuted)
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onMute :" + bMuted);
+ }
+
+ public void onAudioUpdate(boolean bStereo)
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onAudioUpdate :" + bStereo);
+ }
+
+ public void onStationRDSSupported(boolean bRDSSupported)
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onStationRDSSupported :" + bRDSSupported);
+ }
+ public void onRecordingStopped()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onDisabled :");
+ }
+ public void onFinishActivity()
+ {
+ Log.d(LOGTAG, "mServiceCallbacks.onFinishActivity:");
+ }
+ };
+ /* Radio Vars */
+ final Handler mHandler = new Handler();
+
+ final Runnable mTuneComplete = new Runnable(){
+ public void run(){
+ if((null != mMultiUpdateThread) &&(null != mSync))
+ {
+ synchronized(mSync){
+ mSync.notify();
+ }
+ }
+ if((mTestSelected == SEARCH_TEST) && (mService != null)) {
+ /* On every Tune Complete generate the result for the current
+ Frequency*/
+ Message updateUI = new Message();
+ updateUI.what = STATUS_UPDATE;
+ int freq = FmSharedPreferences.getTunedFrequency();
+ updateUI.obj = (Object)GetFMStatsForFreq(freq);
+ if (updateUI.obj == null)
+ updateUI.what = STATUS_DONE;
+ mUIUpdateHandlerHandler.sendMessage(updateUI);
+ }
+ }
+ };
+
+ private void stopCurTest() {
+ if (mTestRunning) {
+ switch(mTestSelected) {
+ case CUR_FREQ_TEST:
+ break;
+ case SWEEP_TEST:
+ case CUR_MULTI_TEST:
+ if (mMultiUpdateThread != null)
+ mMultiUpdateThread.interrupt();
+ break;
+ case SEARCH_TEST:
+ mHandler.removeCallbacks(mTuneComplete);
+ if (mService != null) {
+ try {
+ Message updateStop = new Message();
+ updateStop.what = STATUS_DONE;
+ mUIUpdateHandlerHandler.sendMessage(updateStop);
+ mService.cancelSearch();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ break;
+ }
+ mTestRunning = false;
+ }
+ }
+ }
diff --git a/fmapp2/src/com/caf/fmradio/FMTransmitterActivity.java b/fmapp2/src/com/caf/fmradio/FMTransmitterActivity.java
new file mode 100644
index 0000000..e83f61f
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FMTransmitterActivity.java
@@ -0,0 +1,1563 @@
+/*
+ * Copyright (c) 2009-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.media.AudioManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView; //import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.text.TextUtils; //import android.app.ProgressDialog;
+import android.util.DisplayMetrics;
+
+import java.lang.ref.WeakReference; //import java.text.DecimalFormat;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+import com.caf.utils.FrequencyPicker;
+import com.caf.utils.FrequencyPickerDialog;
+import android.content.ServiceConnection;
+
+import qcom.fmradio.FmConfig;
+import android.os.ServiceManager;
+
+import com.caf.fmradio.HorizontalNumberPicker.OnScrollFinishListener;
+import com.caf.fmradio.HorizontalNumberPicker.OnValueChangeListener;
+import com.caf.fmradio.HorizontalNumberPicker.Scale;
+
+public class FMTransmitterActivity extends Activity {
+ public static final String LOGTAG = "FMTransmitterActivity";
+
+ /* menu Identifiers */
+ private static final int MENU_SCAN_START = Menu.FIRST + 1;
+ private static final int MENU_SCAN_STOP = Menu.FIRST + 2;
+ private static final int MENU_SETTINGS = Menu.FIRST + 3;
+
+ /* Dialog Identifiers */
+ private static final int DIALOG_PRESET_LIST_AUTO_SET = 1;
+ private static final int DIALOG_PICK_FREQUENCY = 2;
+ private static final int DIALOG_PRESET_OPTIONS = 3;
+ private static final int DIALOG_PROGRESS_PROGRESS = 5;
+ private static final int DIALOG_CMD_FAILED_HDMI_ON = 14;
+ /* Activity Return ResultIdentifiers */
+ private static final int ACTIVITY_RESULT_SETTINGS = 1;
+
+ private static final int MAX_PRESETS = 7;
+ private static IFMTransmitterService mService = null;
+ private static FmSharedPreferences mPrefs;
+
+ /* Button Resources */
+ private ImageView mOnOffButton;
+ private HorizontalNumberPicker mPicker;
+ /* 6 Preset Buttons */
+ private Button[] mPresetButtons = {null, null, null, null, null, null, null};
+ private int[] mPresetFrequencies = {0, 0, 0, 0, 0, 0, 0};
+ // private ImageButton mSearchButton;
+ private ImageView mForwardButton;
+ private ImageView mBackButton;
+
+ /* row in the station info layout */
+ private TextView mTransmitStaticMsgTV;
+ private TextView mTuneStationFrequencyTV;
+
+ /* Bottom row in the station info layout */
+ private TextView mRadioTextTV;
+
+ /* Current Status Indicators */
+ private static boolean mIsSearching = false;
+ private static boolean mSearchingResultStatus = false;
+
+ private boolean mInternalAntennaAvailable = false;
+
+ private Animation mAnimation = null;
+ private ScrollerText mRadioTextScroller = null;
+
+ private static int mTunedFrequency = 0;
+ private int mFrequency;
+ private int mPresetButtonIndex = -1;
+ private String mMetaData = null;
+ private String mPSData = null;
+
+ /* Radio Vars */
+ final Handler mHandler = new Handler();
+ private final Handler enableRadioHandler = new Handler();
+ private final Handler disableRadioHandler = new Handler();
+
+ /* Search Progress Dialog */
+ private ProgressDialog mProgressDialog = null;
+
+ private LoadedDataAndState SavedDataAndState = null;
+
+ private static int mDisplayWidth;
+ private static final int TEXTSIZE_PARAMETER_FOR_NUMBER_PICKER = 20;
+ private static final int FREQUENCY_STEP_SMALL = 50;
+ private static final int FREQUENCY_STEP_MEDIUM = 100;
+ private static final int FREQUENCY_STEP_LARGE = 200;
+ public static boolean mUpdatePickerValue = false;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ mPrefs = new FmSharedPreferences(this);
+ Log.d(LOGTAG, "onCreate - Height : "
+ + getWindowManager().getDefaultDisplay().getHeight()
+ + " - Width : "
+ + getWindowManager().getDefaultDisplay().getWidth());
+
+ mDisplayWidth = getWindowManager().getDefaultDisplay().getWidth();
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
+
+ setContentView(R.layout.fmtransmitter);
+ SavedDataAndState = (LoadedDataAndState)getLastNonConfigurationInstance();
+ mAnimation = AnimationUtils.loadAnimation(this, R.anim.preset_select);
+
+ mPicker = (HorizontalNumberPicker)findViewById(R.id.fm_picker);
+ if(mPicker != null) {
+ mPicker.setTextSize(mDisplayWidth / TEXTSIZE_PARAMETER_FOR_NUMBER_PICKER);
+ mPicker.setDensity(outMetrics.densityDpi);
+ mPicker.setOnValueChangedListener(new OnValueChangeListener() {
+ @Override
+ public void onValueChange(HorizontalNumberPicker picker,
+ int oldVal, int newVal) {
+ // TODO Auto-generated method stub
+ valueToFrequency(newVal);
+ mHandler.post(mRadioChangeFrequency);
+ }
+ });
+ }
+ mOnOffButton = (ImageView)findViewById(R.id.btn_onoff);
+ if(mOnOffButton != null) {
+ mOnOffButton.setOnClickListener(mTurnOnOffClickListener);
+ }
+ mForwardButton = (ImageView)findViewById(R.id.btn_forward);
+ if(mForwardButton != null) {
+ mForwardButton.setOnClickListener(mForwardClickListener);
+ }
+ mBackButton = (ImageView)findViewById(R.id.btn_back);
+ if(mBackButton != null) {
+ mBackButton.setOnClickListener(mBackClickListener);
+ }
+
+ /* Preset Buttons */
+ mPresetButtons[0] = (Button)findViewById(R.id.presets_button_1);
+ mPresetButtons[1] = (Button)findViewById(R.id.presets_button_2);
+ mPresetButtons[2] = (Button)findViewById(R.id.presets_button_3);
+ mPresetButtons[3] = (Button)findViewById(R.id.presets_button_4);
+ mPresetButtons[4] = (Button)findViewById(R.id.presets_button_5);
+ mPresetButtons[5] = (Button)findViewById(R.id.presets_button_6);
+ mPresetButtons[6] = (Button)findViewById(R.id.presets_button_7);
+ for(int nButton = 0; nButton < MAX_PRESETS; nButton++) {
+ if (mPresetButtons[nButton] != null) {
+ mPresetButtons[nButton].setOnClickListener(
+ mPresetButtonClickListener);
+ mPresetButtons[nButton].setOnLongClickListener(
+ mPresetButtonOnLongClickListener);
+ }
+ }
+
+ mTransmitStaticMsgTV = (TextView)findViewById(R.id.transmit_msg_tv);
+ String str = getString(R.string.transmit_msg_string);
+ if(null != mPSData) {
+ str = mPSData.concat("\n").concat(str);
+ }
+ if(mTransmitStaticMsgTV != null) {
+ mTransmitStaticMsgTV.setText(str);
+ }
+
+ mTuneStationFrequencyTV = (TextView)findViewById(R.id.prog_frequency_tv);
+ if(mTuneStationFrequencyTV != null) {
+ mTuneStationFrequencyTV.setOnLongClickListener(
+ mFrequencyViewClickListener);
+ }
+ mRadioTextTV = (TextView)findViewById(R.id.radio_text_tv);
+
+ if((mRadioTextScroller == null) && (mRadioTextTV != null)) {
+ mRadioTextScroller = new ScrollerText(mRadioTextTV);
+ }
+
+ enableRadioOnOffUI(false);
+ //HDMI and FM concurrecny is not supported.
+ if(isHdmiOn()) {
+ showDialog(DIALOG_CMD_FAILED_HDMI_ON);
+ }else {
+ if(false == bindToService(this, osc)) {
+ Log.d(LOGTAG, "onCreate: Failed to Start Service");
+ }else {
+ Log.d(LOGTAG, "onCreate: Start Service completed successfully");
+ }
+ }
+ }
+
+ @Override
+ public void onRestart() {
+ Log.d(LOGTAG, "onRestart");
+ super.onRestart();
+ }
+
+ @Override
+ public void onStop() {
+ Log.d(LOGTAG, "onStop");
+ super.onStop();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Log.d(LOGTAG, "onStart");
+ try {
+ if(mService != null) {
+ mService.registerCallbacks(mServiceCallbacks);
+ }
+ }catch(RemoteException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mRadioTextScroller.stopScroll();
+ SavePreferences();
+ }
+
+ private static final String TX_PREF_LAST_TUNED_FREQUENCY = "last_tx_frequency";
+ private static final String TX_PRESET_FREQUENCY = "tx_preset_freq_";
+ private static final int DEFAULT_NO_FREQUENCY = 98100;
+
+ public void SavePreferences() {
+ Log.d(LOGTAG, "Save preferences");
+ SharedPreferences sp = getPreferences(Context.MODE_PRIVATE);
+ SharedPreferences.Editor ed = sp.edit();
+
+ ed.putInt(TX_PREF_LAST_TUNED_FREQUENCY, mTunedFrequency);
+
+ for(int presetIndex = 0; presetIndex < MAX_PRESETS; presetIndex++) {
+ ed.putInt(TX_PRESET_FREQUENCY + presetIndex,
+ mPresetFrequencies[presetIndex]);
+ }
+ ed.commit();
+ }
+
+ public void LoadPreferences() {
+ SharedPreferences sp = getPreferences(Context.MODE_PRIVATE);
+ mTunedFrequency = sp.getInt(
+ TX_PREF_LAST_TUNED_FREQUENCY, DEFAULT_NO_FREQUENCY);
+ for(int presetIndex = 0; presetIndex < MAX_PRESETS; presetIndex++) {
+ mPresetFrequencies[presetIndex] = sp.getInt(
+ TX_PRESET_FREQUENCY + presetIndex,
+ DEFAULT_NO_FREQUENCY);
+ }
+ }
+
+ protected void setDisplayvalue() {
+ int max = mPrefs.getUpperLimit();
+ int min = mPrefs.getLowerLimit();
+ int step = mPrefs.getFrequencyStepSize();
+
+ if(mPicker == null)
+ return;
+
+ switch(step) {
+ case FREQUENCY_STEP_SMALL:
+ mPicker.setScale(Scale.SCALE_SMALL);
+ break;
+ case FREQUENCY_STEP_MEDIUM:
+ mPicker.setScale(Scale.SCALE_MEDIUM);
+ break;
+ case FREQUENCY_STEP_LARGE:
+ mPicker.setScale(Scale.SCALE_LARGE);
+ break;
+ }
+ int channels = (int)((max - min) / step);
+ String [] displayValues = new String[channels + 1];
+ for(int i = 0; i < displayValues.length; i++) {
+ displayValues[i] = String.valueOf((min + i * step) / 1000.0f);
+ }
+ if(mPicker != null) {
+ mPicker.setDisplayedValues(displayValues, true);
+ mPicker.setWrapSelectorWheel(true);
+ mPicker.invalidate();
+ }
+ }
+ @Override
+ public void onResume() {
+ super.onResume();
+ LoadPreferences();
+ if(mPicker != null) {
+ setDisplayvalue();
+ }
+ mUpdatePickerValue = true;
+ mHandler.post(mUpdateRadioText);
+ updateStationInfoToUI();
+ enableRadioOnOffUI();
+ }
+
+ @Override
+ public void onDestroy() {
+ unbindFromService(this);
+ mService = null;
+ Log.d(LOGTAG, "onDestroy: unbindFromService completed");
+ super.onDestroy();
+ }
+
+ private class LoadedDataAndState {
+ public LoadedDataAndState(){};
+ public boolean onOrOff;
+ }
+
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ final LoadedDataAndState data = new LoadedDataAndState();
+ if(mService != null) {
+ try {
+ data.onOrOff = mService.isFmOn();
+ }catch(RemoteException e) {
+ data.onOrOff = false;
+ e.printStackTrace();
+ }
+ }else {
+ data.onOrOff = false;
+ }
+ return data;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuItem item;
+ boolean radioOn = isFmOn();
+ boolean searchActive = isSearchActive();
+
+ item = menu.add(0, MENU_SCAN_START, 0, R.string.menu_scan_for_preset)
+ .setIcon(R.drawable.ic_btn_search);
+ if(item != null) {
+ item.setVisible(!searchActive && radioOn);
+ }
+ item = menu.add(0, MENU_SCAN_STOP, 0, R.string.menu_scan_stop).setIcon(
+ R.drawable.ic_btn_search);
+ if(item != null) {
+ item.setVisible(searchActive && radioOn);
+ }
+
+ /* Settings can be active */
+ item = menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings).setIcon(
+ android.R.drawable.ic_menu_preferences);
+
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+
+ MenuItem item;
+ boolean radioOn = isFmOn();
+ boolean searchActive = isSearchActive();
+
+ item = menu.findItem(MENU_SCAN_START);
+ if(item != null) {
+ item.setVisible(!searchActive && radioOn);
+ }
+ item = menu.findItem(MENU_SCAN_STOP);
+ if(item != null) {
+ item.setVisible(searchActive && radioOn);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case MENU_SETTINGS:
+ Intent launchPreferencesIntent = new Intent().setClass(this,
+ Settings.class);
+ launchPreferencesIntent.putExtra(Settings.RX_MODE,false);
+ startActivityForResult(launchPreferencesIntent,
+ ACTIVITY_RESULT_SETTINGS);
+
+ return true;
+
+ case MENU_SCAN_START:
+ showDialog(DIALOG_PRESET_LIST_AUTO_SET);
+ return true;
+
+ case MENU_SCAN_STOP:
+ cancelSearch();
+ return true;
+
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private boolean isHdmiOn() {
+ //HDMI and FM concurrecny is not supported.
+ try {
+ String hdmiUserOption = android.provider.Settings.System.getString(
+ getContentResolver(), "HDMI_USEROPTION");
+ }catch(Exception ex){
+ Log.d(LOGTAG,"Get HDMI open failed");
+ }
+ return false;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case DIALOG_PRESET_LIST_AUTO_SET: {
+ return createPresetListAutoSelectWarnDlg(id);
+ }
+ case DIALOG_PICK_FREQUENCY: {
+ FmConfig fmConfig = FmSharedPreferences.getFMConfiguration();
+ return new FrequencyPickerDialog(this, fmConfig, mTunedFrequency,
+ mFrequencyChangeListener);
+ }
+ case DIALOG_PROGRESS_PROGRESS: {
+ return createProgressDialog(id);
+ }
+ case DIALOG_PRESET_OPTIONS: {
+ return createPresetOptionsDlg(id);
+ }
+ default:
+ break;
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ super.onPrepareDialog(id, dialog);
+ switch(id) {
+ case DIALOG_PICK_FREQUENCY: {
+ FmConfig fmConfig = FmSharedPreferences.getFMConfiguration();
+ ((FrequencyPickerDialog) dialog).updateSteps(fmConfig.getChSpacing());
+ ((FrequencyPickerDialog) dialog).updateMinFreq(fmConfig.getLowerLimit());
+ ((FrequencyPickerDialog) dialog).updateMaxFreq(fmConfig.getUpperLimit());
+ ((FrequencyPickerDialog) dialog).UpdateFrequency(mTunedFrequency);
+ break;
+ }
+ case DIALOG_PRESET_OPTIONS: {
+ AlertDialog alertDlg = ((AlertDialog) dialog);
+ if((alertDlg != null) && (mPresetButtonIndex >= 0)
+ && (mPresetButtonIndex <= MAX_PRESETS)) {
+ int frequency = mPresetFrequencies[mPresetButtonIndex];
+ alertDlg.setTitle(PresetStation.getFrequencyString(frequency));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ Log.d(LOGTAG, "onActivityResult : requestCode -> " + requestCode);
+ Log.d(LOGTAG, "onActivityResult : resultCode -> " + resultCode);
+ if(requestCode == ACTIVITY_RESULT_SETTINGS) {
+ if(resultCode == RESULT_OK) {
+ if(data != null) {
+ String action = data.getAction();
+ if(action != null) {
+ if(action.equals(Settings.RESTORE_FACTORY_DEFAULT_ACTION)) {
+ RestoreDefaults();
+ enableRadioOnOffUI();
+ tuneRadio(mTunedFrequency);
+ }
+ }
+ }
+ } //if ACTIVITY_RESULT_SETTINGS
+ }//if (resultCode == RESULT_OK)
+ }
+
+ /**
+ * @return true if a internal antenna is available.
+ *
+ */
+ boolean isInternalAntennaAvailable() {
+ return mInternalAntennaAvailable;
+ }
+
+ private Dialog createPresetOptionsDlg(int id) {
+ if((mPresetButtonIndex >= 0) && (mPresetButtonIndex <= MAX_PRESETS)) {
+ int frequency = mPresetFrequencies[mPresetButtonIndex];
+ AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(this);
+ dlgBuilder.setTitle(PresetStation.getFrequencyString(frequency));
+ ArrayList<String> arrayList = new ArrayList<String>();
+ arrayList.add(getResources().getString(R.string.preset_tune));
+ arrayList.add(getResources().getString(R.string.preset_replace));
+ arrayList.add(getResources().getString(R.string.preset_delete));
+ dlgBuilder.setCancelable(true);
+ dlgBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ mPresetButtonIndex = -1;
+ removeDialog(DIALOG_PRESET_OPTIONS);
+ }
+ });
+ String[] items = new String[arrayList.size()];
+ arrayList.toArray(items);
+ dlgBuilder.setItems(items, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ if((mPresetButtonIndex >= 0) &&
+ (mPresetButtonIndex <= MAX_PRESETS)) {
+ int frequency = mPresetFrequencies[mPresetButtonIndex];
+ switch(item) {
+ case 0: {
+ // Tune to this station
+ mPresetButtonIndex = -1;
+ tuneRadio(frequency);
+ break;
+ }
+ case 1: {
+ // Replace preset Station with currently tuned
+ // station
+ Log.d(LOGTAG, "Replace station - " + frequency
+ + " with " + mTunedFrequency);
+ if(!stationExists(mTunedFrequency)) {
+ mPresetFrequencies[mPresetButtonIndex] = mTunedFrequency;
+ mPresetButtonIndex = -1;
+ setupPresetLayout();
+ SavePreferences();
+ }
+ break;
+ }
+ case 2: {
+ // Delete
+ mPresetFrequencies[mPresetButtonIndex] = 0;
+ mPresetButtonIndex = -1;
+ setupPresetLayout();
+ SavePreferences();
+ break;
+ }
+ default: {
+ // Should not happen
+ mPresetButtonIndex = -1;
+ break;
+ }
+ }// switch item
+ } // if(mPresetButtonStation != null)
+ removeDialog(DIALOG_PRESET_OPTIONS);
+ }// onClick
+ });
+ return dlgBuilder.create();
+ }
+ return null;
+ }
+
+ private Dialog createProgressDialog(int id) {
+ String msgStr = "";
+ String titleStr = "";
+ boolean bSearchActive = false;
+
+ if(isSearchActive()) {
+ msgStr = getString(R.string.msg_weak_searching);
+ titleStr = getString(R.string.msg_searching_title);
+ bSearchActive = true;
+ }
+
+ if(bSearchActive) {
+ mProgressDialog = new ProgressDialog(FMTransmitterActivity.this);
+ if(mProgressDialog != null) {
+ mProgressDialog.setTitle(titleStr);
+ mProgressDialog.setMessage(msgStr);
+ mProgressDialog.setIcon(R.drawable.ic_launcher_fm_tx);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setCanceledOnTouchOutside(false);
+ mProgressDialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ getText(R.string.button_text_stop),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ cancelSearch();
+ }
+ });
+ mProgressDialog.setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ cancelSearch();
+ }
+ });
+ }
+
+ Message msg = new Message();
+ msg.what = TIMEOUT_PROGRESS_DLG;
+ mSearchProgressHandler.sendMessageDelayed(msg, SHOWBUSY_TIMEOUT);
+ }
+ return mProgressDialog;
+ }
+
+ private Dialog createPresetListAutoSelectWarnDlg(int id) {
+ AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(this);
+ dlgBuilder.setIcon(R.drawable.alert_dialog_icon).setTitle(
+ R.string.presetlist_autoselect_title);
+ dlgBuilder.setMessage(getString(R.string.fmtx_autoselect_name));
+
+ dlgBuilder.setPositiveButton(R.string.alert_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ /*
+ * Since the presets will be changed, reset the page
+ * number
+ */
+ initiateSearchList();
+ setupPresetLayout();
+ SavePreferences();
+ removeDialog(DIALOG_PRESET_LIST_AUTO_SET);
+ }
+ });
+
+ dlgBuilder.setNegativeButton(R.string.alert_dialog_cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ removeDialog(DIALOG_PRESET_LIST_AUTO_SET);
+ }
+ });
+ return (dlgBuilder.create());
+ }
+
+ private void RestoreDefaults() {
+ for(int index = 0; index < MAX_PRESETS; index++) {
+ mPresetFrequencies[index] = 0;
+ }
+ mTunedFrequency = FmSharedPreferences.getLowerLimit();
+ SavePreferences();
+ }
+
+ private View.OnLongClickListener mFrequencyViewClickListener = new View.OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ showDialog(DIALOG_PICK_FREQUENCY);
+ return true;
+ }
+ };
+
+ private View.OnClickListener mForwardClickListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ int frequency = FmSharedPreferences
+ .getNextTuneFrequency(mTunedFrequency);
+ Log.d(LOGTAG, "Tune Up: to " + frequency);
+ tuneRadio(frequency);
+ }
+ };
+
+ private View.OnClickListener mBackClickListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ int frequency = FmSharedPreferences
+ .getPrevTuneFrequency(mTunedFrequency);
+ Log.d(LOGTAG, "Tune Down: to " + frequency);
+ tuneRadio(frequency);
+ }
+ };
+
+
+ private View.OnClickListener mPresetButtonClickListener = new View.OnClickListener() {
+ public void onClick(View view) {
+ Integer indexInteger = (Integer) view.getTag();
+ int index = indexInteger.intValue();
+ if((index >= 0) && (index <= MAX_PRESETS)) {
+ Log.d(LOGTAG, "station - " + index);
+ if(mPresetFrequencies[index] != 0) {
+ mTunedFrequency = mPresetFrequencies[index];
+ tuneRadio(mTunedFrequency);
+ view.startAnimation(mAnimation);
+ }
+ }
+ }
+ };
+
+ private boolean stationExists(int frequency) {
+ boolean exists = false;
+ for(int i = 0; i < MAX_PRESETS; i++) {
+ if(mPresetFrequencies[i] == frequency) {
+ exists = true;
+ break;
+ }
+ }
+ if(exists){
+ Toast t = Toast.makeText(this, getString(R.string.station_exists), Toast.LENGTH_SHORT);
+ t.show();
+ }
+ return exists;
+ }
+ private View.OnLongClickListener mPresetButtonOnLongClickListener = new View.OnLongClickListener() {
+ public boolean onLongClick(View view) {
+ Integer indexInteger = (Integer) view.getTag();
+ int index = indexInteger.intValue();
+ if((index >= 0) && (index <= MAX_PRESETS)) {
+ int frequency = mPresetFrequencies[index];
+ mPresetButtonIndex = index;
+ if(frequency > 0) {
+ showDialog(DIALOG_PRESET_OPTIONS);
+ }else if(!stationExists(mTunedFrequency)) {
+ mPresetFrequencies[index] = mTunedFrequency;
+ view.startAnimation(mAnimation);
+ setupPresetLayout();
+ SavePreferences();
+ }
+ }
+ return true;
+ }
+ };
+
+ final FrequencyPickerDialog.OnFrequencySetListener
+ mFrequencyChangeListener =
+ new FrequencyPickerDialog.OnFrequencySetListener() {
+ public void onFrequencySet
+ (
+ FrequencyPicker view, int frequency
+ ) {
+ Log.d(LOGTAG, "mFrequencyChangeListener:"
+ + "onFrequencyChanged to : "
+ + frequency);
+ tuneRadio(frequency);
+ }
+ };
+
+ private View.OnClickListener mTurnOnOffClickListener =
+ new View.OnClickListener() {
+ public void onClick(View v) {
+
+ if(isFmOn()) {
+ enableRadioHandler.removeCallbacks(enableRadioTask);
+ disableRadioHandler.removeCallbacks(disableRadioTask);
+ disableRadioHandler.postDelayed(disableRadioTask, 0);
+ }else {
+ enableRadioHandler.removeCallbacks(enableRadioTask);
+ disableRadioHandler.removeCallbacks(disableRadioTask);
+ enableRadioHandler.postDelayed(enableRadioTask, 0);
+ }
+ setTurnOnOffButtonImage();
+ }
+ };
+
+ private void setTurnOnOffButtonImage() {
+ if(isFmOn() == true) {
+ mOnOffButton.setImageResource(R.drawable.ic_btn_onoff);
+ }else {
+ /* Find a icon to indicate off */
+ mOnOffButton.setImageResource(R.drawable.ic_btn_onoff);
+ }
+ }
+
+ private void enableRadioOnOffButton() {
+ if(mOnOffButton != null) {
+ mOnOffButton.setEnabled(true);
+ mOnOffButton.setClickable(true);
+ }
+ }
+
+ private void disableRadioOnOffButton() {
+ if(mOnOffButton != null) {
+ mOnOffButton.setEnabled(false);
+ mOnOffButton.setClickable(false);
+ }
+ }
+
+ private void setHsPluggedInMsg() {
+ if(mRadioTextTV != null) {
+ mRadioTextTV.setVisibility(View.VISIBLE);
+ mRadioTextTV.setText(getString(R.string.msg_headsetpluggedin));
+ }
+ }
+
+ private Runnable enableRadioTask = new Runnable() {
+ public void run() {
+ enableRadio();
+ }
+ };
+
+ private Runnable disableRadioTask = new Runnable() {
+ public void run() {
+ disableRadio();
+ }
+ };
+
+ private void enableRadio() {
+ mIsSearching = false;
+ disableRadioOnOffButton();
+ if(mService != null) {
+ try {
+ if(mService.isHeadsetPlugged()) {
+ setHsPluggedInMsg();
+ enableRadioOnOffButton();
+ }else if(mService.isCallActive()) {
+ enableRadioOnOffButton();
+ }else if(!mService.fmOn()) {
+ enableRadioOnOffButton();
+ }
+ }catch(RemoteException e) {
+ enableRadioOnOffButton();
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void disableRadio() {
+ enableRadioOnOffUI(false);
+ cancelSearch();
+ if(mService != null) {
+ try {
+ if(!mService.fmOff()) {
+ enableRadioOnOffButton();
+ }
+ }catch(RemoteException e) {
+ enableRadioOnOffButton();
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static void fmConfigure() {
+ if(mService != null) {
+ try {
+ mService.fmReconfigure();
+ }catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void enableRadioOnOffUI() {
+ boolean bEnable = true;
+ //decide enable or disable UI based on
+ //Tx Service status.This is valid only
+ // when search is not in progress. When
+ // search is in progress UI should be active
+ if(false == isSearchActive()) {
+ bEnable = isFmOn();
+ }
+ /* Disable if no antenna/headset is available */
+ if(!readInternalAntennaAvailable()) {
+ Log.d(LOGTAG,"finding internal antenna avialable as false");
+ bEnable = false;
+ }
+ enableRadioOnOffUI(bEnable);
+ }
+
+ private void enableRadioOnOffUI(boolean bEnable) {
+ if(bEnable) {
+ if(mTuneStationFrequencyTV != null) {
+ mTuneStationFrequencyTV.setOnLongClickListener(
+ mFrequencyViewClickListener);
+ }
+ if(mRadioTextScroller != null) {
+ mRadioTextScroller.startScroll();
+ }
+ }else {
+ if(mTuneStationFrequencyTV != null) {
+ mTuneStationFrequencyTV.setOnLongClickListener(null);
+ }
+ if(mRadioTextScroller != null) {
+ mRadioTextScroller.stopScroll();
+ }
+ }
+
+ if(mForwardButton != null) {
+ mForwardButton.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if(mBackButton != null) {
+ mBackButton.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if(mTransmitStaticMsgTV != null) {
+ mTransmitStaticMsgTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if(mTuneStationFrequencyTV != null) {
+ mTuneStationFrequencyTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if(mRadioTextTV != null) {
+ mRadioTextTV.setVisibility(((bEnable == true) ? View.VISIBLE
+ : View.INVISIBLE));
+ }
+ if(mPicker != null) {
+ mPicker.setVisibility(
+ bEnable ? View.VISIBLE : View.INVISIBLE );
+ }
+ try {
+ if(null != mService) {
+ if(mService.isHeadsetPlugged()) {
+ Log.d(LOGTAG,"headset plugged in");
+ if(mRadioTextTV != null) {
+ mRadioTextTV.setVisibility(View.VISIBLE);
+ mRadioTextTV.setText(getString(R.string.msg_headsetpluggedin));
+ }
+ if(mOnOffButton != null) {
+ mOnOffButton.setEnabled(false);
+ }
+ }else if(mService.isCallActive()) {
+ Log.d(LOGTAG,"call active");
+ if(mRadioTextTV != null) {
+ mRadioTextTV.setVisibility(View.VISIBLE);
+ mRadioTextTV.setText(getString(R.string.msg_callactive));
+ }
+ if(mOnOffButton != null) {
+ mOnOffButton.setEnabled(false);
+ }
+ }else {
+ if(mRadioTextTV != null) {
+ mRadioTextTV.setText("");
+ }
+ if(mOnOffButton != null) {
+ mOnOffButton.setEnabled(true);
+ }
+ }
+ }else {
+ Log.d(LOGTAG,"Service null");
+ if(mRadioTextTV != null) {
+ mRadioTextTV.setText("");
+ }
+ if(mOnOffButton != null) {
+ mOnOffButton.setEnabled(true);
+ }
+ }
+ }catch(RemoteException e) {
+ e.printStackTrace();
+ }
+
+ for(int nButton = 0; nButton < MAX_PRESETS; nButton++) {
+ if(mPresetButtons[nButton] != null) {
+ mPresetButtons[nButton].setEnabled(bEnable);
+ }
+ }
+ }
+
+ private void updateSearchProgress() {
+ boolean searchActive = isSearchActive();
+ if(searchActive) {
+ synchronized (this) {
+ if(mProgressDialog == null) {
+ showDialog(DIALOG_PROGRESS_PROGRESS);
+ }else {
+ Message msg = new Message();
+ msg.what = UPDATE_PROGRESS_DLG;
+ mSearchProgressHandler.sendMessage(msg);
+ }
+ }
+ }else {
+ Message msg = new Message();
+ msg.what = END_PROGRESS_DLG;
+ mSearchProgressHandler.sendMessage(msg);
+ }
+ }
+
+ private void resetSearchProgress() {
+ Message msg = new Message();
+ msg.what = END_PROGRESS_DLG;
+ mSearchProgressHandler.sendMessage(msg);
+ }
+
+ private void setupPresetLayout() {
+ for(int buttonIndex = 0; (buttonIndex < MAX_PRESETS); buttonIndex++) {
+ if(mPresetButtons[buttonIndex] != null) {
+ String display = "";
+ int frequency = mPresetFrequencies[buttonIndex];
+ if(frequency != 0) {
+ display = PresetStation.getFrequencyString(frequency);
+ }else {
+ display = this.getString(R.string.add_station);
+ }
+ mPresetButtons[buttonIndex].setText(display);
+ mPresetButtons[buttonIndex].setTag(new Integer(buttonIndex));
+ }
+ }
+ }
+
+ private void updateStationInfoToUI() {
+ mTuneStationFrequencyTV.setText(PresetStation.getFrequencyString(mTunedFrequency));
+ if((mPicker != null) && mUpdatePickerValue) {
+ mPicker.setValue(((mTunedFrequency - mPrefs.getLowerLimit())
+ / mPrefs.getFrequencyStepSize()));
+ }
+ mRadioTextTV.setText("");
+ setupPresetLayout();
+ }
+
+ private boolean isFmOn() {
+ boolean bOn = false;
+ if(mService != null) {
+ try {
+ bOn = mService.isFmOn();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return (bOn);
+ }
+
+ private boolean isSearchActive() {
+ return (mIsSearching);
+ }
+
+ public static int getCurrentTunedFrequency() {
+ return mTunedFrequency;
+ }
+
+ private void cancelSearch() {
+ synchronized (this) {
+ if(mService != null) {
+ try {
+ if(mIsSearching == true) {
+ if(true == mService.cancelSearch()) {
+ }
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ updateSearchProgress();
+ }
+
+ /** get Weakest Stations */
+ private void initiateSearchList() {
+ synchronized (this) {
+ mIsSearching = true;
+ if(mService != null) {
+ try {
+ mSearchingResultStatus = false;
+ mIsSearching = mService.searchWeakStationList(MAX_PRESETS);
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ updateSearchProgress();
+ }
+ }
+ }
+
+ /** get Internal Antenna Available mode Stations */
+ private boolean readInternalAntennaAvailable() {
+ mInternalAntennaAvailable = false;
+ if(mService != null) {
+ try {
+ mInternalAntennaAvailable = mService
+ .isInternalAntennaAvailable();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ Log.e(LOGTAG, "readInternalAntennaAvailable: internalAntenna : "
+ + mInternalAntennaAvailable);
+ return mInternalAntennaAvailable;
+ }
+
+ private static final int UPDATE_PROGRESS_DLG = 1;
+ private static final int END_PROGRESS_DLG = 2;
+ private static final int TIMEOUT_PROGRESS_DLG = 3;
+ private static final int SHOWBUSY_TIMEOUT = 300000;
+ private Handler mSearchProgressHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ if(msg.what == UPDATE_PROGRESS_DLG) {
+ if(mProgressDialog != null) {
+ mTuneStationFrequencyTV.setText(PresetStation.getFrequencyString(mTunedFrequency));
+ String titleStr = getString( R.string.msg_search_title,
+ PresetStation.getFrequencyString(mTunedFrequency));
+
+ mProgressDialog.setTitle(titleStr);
+ }
+ }else if (msg.what == END_PROGRESS_DLG) {
+ mSearchProgressHandler.removeMessages(END_PROGRESS_DLG);
+ mSearchProgressHandler.removeMessages(UPDATE_PROGRESS_DLG);
+ mSearchProgressHandler.removeMessages(TIMEOUT_PROGRESS_DLG);
+ removeDialog(DIALOG_PROGRESS_PROGRESS);
+ mProgressDialog = null;
+ }else if (msg.what == TIMEOUT_PROGRESS_DLG) {
+ cancelSearch();
+ }
+ }
+ };
+
+ private void tuneRadio(int frequency) {
+ if(mService != null) {
+ try {
+ mService.tune(frequency);
+ updateStationInfoToUI();
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void resetFMStationInfoUI() {
+ mRadioTextTV.setText("");
+ mRadioTextScroller.stopScroll();
+ mUpdatePickerValue = true;
+ updateStationInfoToUI();
+ }
+
+
+ final Runnable mUpdateStationInfo = new Runnable() {
+ public void run() {
+ updateSearchProgress();
+ resetFMStationInfoUI();
+ }
+ };
+
+ final Runnable mSearchListComplete = new Runnable() {
+ public void run() {
+ Log.d(LOGTAG, "mSearchListComplete: ");
+ mIsSearching = false;
+
+ if(mService != null) {
+ try {
+ if(mSearchingResultStatus) {
+ int[] searchList = mService.getSearchList();
+ if(searchList != null) {
+ for(int station = 0; (station < searchList.length)
+ && (station < MAX_PRESETS); station++) {
+ Log.d(LOGTAG, "mSearchListComplete: [" + station
+ + "] = " + searchList[station]);
+ mPresetFrequencies[station] = searchList[station];
+ }
+ }
+ }
+ if(!mService.isHeadsetPlugged()) {
+ mService.fmRestart();
+ /* Tune to last tuned frequency */
+ tuneRadio(mTunedFrequency);
+ updateSearchProgress();
+ resetFMStationInfoUI();
+ setupPresetLayout();
+ SavePreferences();
+ }else {
+ updateSearchProgress();
+ enableRadioHandler.removeCallbacks(enableRadioTask);
+ disableRadioHandler.removeCallbacks(disableRadioTask);
+ disableRadioHandler.postDelayed(disableRadioTask, 0);
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+
+
+ final Runnable mUpdateRadioText = new Runnable() {
+ public void run() {
+ String str = "";
+ if((mService != null) && isFmOn()) {
+ try {
+ /* Get Radio Text and update the display */
+ str = mService.getRadioText();
+ if(null != mMetaData) {
+ Log.d(LOGTAG,"meta data is "+mMetaData);
+ str = str.concat(mMetaData);
+ }else {
+ str = str.concat("...");
+ }
+ /* Update only if all the characters are printable */
+ if(TextUtils.isGraphic(str)) {
+ Log.d(LOGTAG, "mUpdateRadioText: Updatable string: ["
+ + str + "]");
+ mRadioTextTV.setText(str);
+ }else if (TextUtils.isEmpty(str)) {
+ mRadioTextTV.setText("");
+ }else {
+ Log.d(LOGTAG, "RDS has non printable stuff");
+ mRadioTextTV.setText("");
+ }
+
+ mRadioTextScroller.startScroll();
+ String szRTStr = getString(R.string.transmit_msg_string);
+ mPSData = mService.getPSData();
+ if(null != mPSData ) {
+ szRTStr = mPSData.concat("\n").concat(szRTStr);
+ }else {
+ Log.d(LOGTAG, "mPSData is NULL");
+ }
+ mTransmitStaticMsgTV.setText(szRTStr);
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ final Runnable mRadioChangeFrequency = new Runnable(){
+ public void run() {
+ mUpdatePickerValue = false;
+ tuneRadio(mFrequency);
+ }
+ };
+
+ protected int valueToFrequency(int value) {
+ mFrequency = mPrefs.getLowerLimit() + value *
+ mPrefs.getFrequencyStepSize();
+ return mFrequency;
+ }
+
+ private void DebugToasts(String str, int duration) {
+ Toast.makeText(this, str, duration).show();
+ }
+
+ /**
+ * This Handler will scroll the text view. On startScroll, the scrolling
+ * starts after SCROLLER_START_DELAY_MS The Text View is scrolled left one
+ * character after every SCROLLER_UPDATE_DELAY_MS When the entire text is
+ * scrolled, the scrolling will restart after SCROLLER_RESTART_DELAY_MS
+ */
+ private static final class ScrollerText extends Handler {
+
+ private static final byte SCROLLER_STOPPED = 0x51;
+ private static final byte SCROLLER_STARTING = 0x52;
+ private static final byte SCROLLER_RUNNING = 0x53;
+
+ private static final int SCROLLER_MSG_START = 0xF1;
+ private static final int SCROLLER_MSG_TICK = 0xF2;
+ private static final int SCROLLER_MSG_RESTART = 0xF3;
+
+ private static final int SCROLLER_START_DELAY_MS = 1000;
+ private static final int SCROLLER_RESTART_DELAY_MS = 3000;
+ private static final int SCROLLER_UPDATE_DELAY_MS = 200;
+
+ private final WeakReference<TextView> mView;
+
+ private byte mStatus = SCROLLER_STOPPED;
+ String mOriginalString;
+ int mStringlength = 0;
+ int mIteration = 0;
+
+ ScrollerText(TextView v) {
+ mView = new WeakReference<TextView>(v);
+ }
+
+ /**
+ * Scrolling Message Handler
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SCROLLER_MSG_START:
+ mStatus = SCROLLER_RUNNING;
+ updateText();
+ break;
+ case SCROLLER_MSG_TICK:
+ updateText();
+ break;
+ case SCROLLER_MSG_RESTART:
+ if(mStatus == SCROLLER_RUNNING) {
+ startScroll();
+ }
+ break;
+ }
+ }
+
+ /**
+ * Moves the text left by one character and posts a delayed message for
+ * next update after SCROLLER_UPDATE_DELAY_MS. If the entire string is
+ * scrolled, then it displays the entire string and waits for
+ * SCROLLER_RESTART_DELAY_MS for scrolling restart
+ */
+ void updateText() {
+ if(mStatus != SCROLLER_RUNNING) {
+ return;
+ }
+
+ removeMessages(SCROLLER_MSG_TICK);
+
+ final TextView textView = mView.get();
+ if(textView != null) {
+ String szStr2 = "";
+ if(mStringlength > 0) {
+ mIteration++;
+ if(mIteration >= mStringlength) {
+ mIteration = 0;
+ sendEmptyMessageDelayed(SCROLLER_MSG_RESTART,
+ SCROLLER_RESTART_DELAY_MS);
+ }else {
+ sendEmptyMessageDelayed(SCROLLER_MSG_TICK,
+ SCROLLER_UPDATE_DELAY_MS);
+ }
+ szStr2 = mOriginalString.substring(mIteration);
+ }
+ textView.setText(szStr2);
+ }
+ }
+
+ /**
+ * Stops the scrolling The textView will be set to the original string.
+ */
+ void stopScroll() {
+ mStatus = SCROLLER_STOPPED;
+ removeMessages(SCROLLER_MSG_TICK);
+ removeMessages(SCROLLER_MSG_RESTART);
+ removeMessages(SCROLLER_MSG_START);
+ resetScroll();
+ }
+
+ /**
+ * Resets the scroll to display the original string.
+ */
+
+ private void resetScroll() {
+ mIteration = 0;
+ final TextView textView = mView.get();
+ if(textView != null) {
+ textView.setText(mOriginalString);
+ }
+ }
+
+ /**
+ * Starts the Scrolling of the TextView after a delay of
+ * SCROLLER_START_DELAY_MS Starts only if Length > 0
+ */
+ void startScroll() {
+ final TextView textView = mView.get();
+ if(textView != null) {
+ mOriginalString = (String) textView.getText();
+ mStringlength = mOriginalString.length();
+ if(mStringlength > 0) {
+ mStatus = SCROLLER_STARTING;
+ sendEmptyMessageDelayed(SCROLLER_MSG_START,
+ SCROLLER_START_DELAY_MS);
+ }
+ }
+ }
+ }
+
+ public static IFMTransmitterService sService = null;
+ private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>();
+
+ public static boolean bindToService(Context context) {
+ Log.e(LOGTAG, "bindToService: Context");
+ return bindToService(context, null);
+ }
+
+ public static boolean bindToService(Context context,
+ ServiceConnection callback) {
+ Log.e(LOGTAG, "bindToService: Context with serviceconnection callback");
+ context.startService(new Intent(context, FMTransmitterService.class));
+ ServiceBinder sb = new ServiceBinder(callback);
+ sConnectionMap.put(context, sb);
+ return context.bindService((new Intent()).setClass(context,
+ FMTransmitterService.class), sb, 0);
+ }
+
+ public static void unbindFromService(Context context) {
+ ServiceBinder sb = (ServiceBinder) sConnectionMap.remove(context);
+ Log.e(LOGTAG, "unbindFromService: Context");
+ if(sb == null) {
+ Log.e(LOGTAG, "Trying to unbind for unknown Context");
+ return;
+ }
+ context.unbindService(sb);
+ if(sConnectionMap.isEmpty()) {
+ // presumably there is nobody interested in the service at this
+ // point,
+ // so don't hang on to the ServiceConnection
+ sService = null;
+ }
+ }
+
+ private static class ServiceBinder implements ServiceConnection {
+ ServiceConnection mCallback;
+
+ ServiceBinder(ServiceConnection callback) {
+ mCallback = callback;
+ }
+
+ public void onServiceConnected(ComponentName className,
+ android.os.IBinder service) {
+ sService = IFMTransmitterService.Stub.asInterface(service);
+ if(mCallback != null) {
+ Log.e(LOGTAG, "onServiceConnected: mCallback");
+ mCallback.onServiceConnected(className, service);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ if(mCallback != null) {
+ mCallback.onServiceDisconnected(className);
+ }
+ sService = null;
+ }
+ }
+
+ private ServiceConnection osc = new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ mService = IFMTransmitterService.Stub.asInterface(obj);
+ Log.e(LOGTAG, "ServiceConnection: onServiceConnected: ");
+ if(mService != null) {
+ try {
+ mService.registerCallbacks(mServiceCallbacks);
+ if(false == mService.isHeadsetPlugged()) {
+ Log.e(LOGTAG, "return for isHeadsetPlugged is false");
+ if(SavedDataAndState == null) {
+ enableRadioHandler.removeCallbacks(enableRadioTask);
+ disableRadioHandler.removeCallbacks(disableRadioTask);
+ enableRadioHandler.postDelayed(enableRadioTask, 0);
+ }else if (SavedDataAndState.onOrOff) {
+ enableRadioOnOffUI(true);
+ }else {
+ enableRadioOnOffUI(false);
+ }
+ }else {
+ enableRadioOnOffUI(false);
+ }
+ }catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return;
+ }else {
+ Log.e(LOGTAG, "IFMTransmitterService onServiceConnected failed");
+ }
+ // Service is dead or not playing anything. If we got here as part
+ // of a "play this file" Intent, exit. Otherwise go to the Music
+ // app start screen.
+ if(getIntent().getData() == null) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(FMTransmitterActivity.this,
+ FMTransmitterActivity.class);
+ startActivity(intent);
+ }
+ finish();
+ }
+
+ public void onServiceDisconnected(ComponentName classname) {
+ }
+ };
+
+ private IFMTransmitterServiceCallbacks.Stub mServiceCallbacks = new IFMTransmitterServiceCallbacks.Stub() {
+
+ public void onDisabled() throws RemoteException {
+ mHandler.post(mRadioStateUpdated);
+ }
+
+ public void onRadioReset() throws RemoteException {
+ mHandler.post(mRadioReset);
+ }
+
+ public void onEnabled(boolean status) throws RemoteException {
+ mHandler.post(mRadioStateUpdated);
+ }
+
+ public void onRadioTextChanged() throws RemoteException {
+ }
+
+ public void onSearchListComplete(boolean status) throws RemoteException {
+ mIsSearching = false;
+ mSearchingResultStatus = status;
+ mHandler.post(mSearchListComplete);
+ }
+
+ public void onTuneStatusChanged(int frequency) throws RemoteException {
+ mTunedFrequency = frequency;
+ Log.d(LOGTAG, "onTuneStatusChanged: Frequency : " + mTunedFrequency);
+ FmSharedPreferences.setTunedFrequency(mTunedFrequency);
+ SavePreferences();
+ mHandler.post(mUpdateStationInfo);
+ }
+
+ public void onReconfigured() throws RemoteException {
+ RestoreDefaults();
+ }
+
+ public void onMetaDataChanged(String metaStr) throws RemoteException {
+ Log.d(LOGTAG,"meta data is "+metaStr);
+ mMetaData = new String (metaStr);
+ mHandler.post(mUpdateRadioText);
+ }
+
+ public void onPSInfoSent(String psStr ) throws RemoteException {
+ Log.d(LOGTAG,"PS String data is "+psStr);
+ mPSData = new String (psStr);
+ mHandler.post(mUpdateRadioText);
+ }
+ };
+
+ final Runnable mRadioStateUpdated = new Runnable() {
+ public void run() {
+ enableRadioOnOffButton();
+ /* Update UI to FM On State */
+ if(isFmOn()) {
+ enableRadioOnOffUI(true);
+ /* Tune to the last tuned frequency */
+ mUpdatePickerValue = true;
+ LoadPreferences();
+ tuneRadio(mTunedFrequency);
+ }else {
+ /* Save the existing frequency */
+ FmSharedPreferences.setTunedFrequency(mTunedFrequency);
+ SavePreferences();
+ removeDialog(DIALOG_PRESET_LIST_AUTO_SET);
+ enableRadioOnOffUI(false);
+ }
+
+ }
+ };
+
+ final Runnable mRadioReset = new Runnable() {
+ public void run() {
+ /* Save the existing frequency */
+ resetSearchProgress();
+ FmSharedPreferences.setTunedFrequency(mTunedFrequency);
+ SavePreferences();
+ enableRadioOnOffUI(false);
+ }
+ };
+}
diff --git a/fmapp2/src/com/caf/fmradio/FMTransmitterConfigReceiver.java b/fmapp2/src/com/caf/fmradio/FMTransmitterConfigReceiver.java
new file mode 100644
index 0000000..4f15188
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FMTransmitterConfigReceiver.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2011-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.content.pm.PackageManager;
+import android.content.Context;
+import android.content.ComponentName;
+import android.util.Log;
+import android.os.SystemProperties;
+import java.io.FileReader;
+import java.lang.String;
+
+
+public class FMTransmitterConfigReceiver extends BroadcastReceiver {
+
+ private static FileReader socinfo_fd;
+ private static char[] socinfo = new char[20];
+ private static String build_id = "1";
+
+ private static final String TAG = "FMFolderConfigReceiver";
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.d(TAG, "Received intent: " + action);
+ if((action != null) && action.equals("android.intent.action.BOOT_COMPLETED")) {
+ Log.d(TAG, "boot complete intent received");
+ boolean isFmTransmitterSupported = SystemProperties.getBoolean("ro.fm.transmitter",true);
+
+ if ("msm7630_surf".equals(SystemProperties.get("ro.board.platform"))) {
+ Log.d(TAG,"this is msm7630_surf");
+ try {
+ socinfo_fd = new FileReader("/sys/devices/system/soc/soc0/build_id");
+ socinfo_fd.read(socinfo,0,20);
+ socinfo_fd.close();
+ } catch(Exception e) {
+ Log.e(TAG,"Exception in FileReader");
+ }
+ Log.d(TAG, "socinfo=" +socinfo);
+ build_id = new String(socinfo,17,1);
+ Log.d(TAG, "build_id=" +build_id);
+ }
+ if ((!isFmTransmitterSupported) || (build_id.equals("0"))) {
+ PackageManager pManager = context.getPackageManager();
+ if (pManager != null) {
+ Log.d(TAG, "disableing the FM Transmitter");
+ ComponentName fmTransmitter = new ComponentName("com.caf.fmradio", "com.caf.fmradio.FMTransmitterActivity");
+ pManager.setComponentEnabledSetting(fmTransmitter, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+ }
+ }
+ }
+}
diff --git a/fmapp2/src/com/caf/fmradio/FMTransmitterService.java b/fmapp2/src/com/caf/fmradio/FMTransmitterService.java
new file mode 100644
index 0000000..a720868
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FMTransmitterService.java
@@ -0,0 +1,1221 @@
+/*
+ * Copyright (c) 2009-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import java.lang.ref.WeakReference;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import qcom.fmradio.FmReceiver;
+import qcom.fmradio.FmTransmitter;
+import qcom.fmradio.FmRxEvCallbacksAdaptor;
+import qcom.fmradio.FmTransmitterCallbacksAdaptor;
+import qcom.fmradio.FmRxRdsData;
+import qcom.fmradio.FmConfig;
+import com.caf.utils.A2dpDeviceStatus;
+import android.media.IRemoteControlDisplay;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.graphics.Bitmap;
+import android.media.IAudioService;
+import android.media.MediaMetadataRetriever;
+
+
+/**
+ * Provides "background" FM Radio (that uses the hardware) capabilities,
+ * allowing the user to switch between activities without stopping playback.
+ */
+public class FMTransmitterService extends Service
+{
+ private static final int FMTRANSMITTERSERVICE_STATUS = 102;
+ private static final int FM_TX_PROGRAM_TYPE = 0;
+ private static final int FM_TX_PROGRAM_ID = 0x1234;
+ private static final int FM_TX_PS_REPEAT_COUNT = 1;
+
+ private static final String FMRADIO_DEVICE_FD_STRING = "/dev/radio0";
+ private static final String LOGTAG = "FMTxService";//FMRadio.LOGTAG;
+ private static final String QFM_STRING ="QFMRADIO";
+
+ private static FmReceiver mReceiver;
+ private static FmTransmitter mTransmitter;
+ private int mTunedFrequency = 0;
+
+ private static FmSharedPreferences mPrefs;
+ private IFMTransmitterServiceCallbacks mCallbacks;
+ private WakeLock mWakeLock;
+ private int mServiceStartId = -1;
+ private boolean mServiceInUse = false;
+ private boolean mMuted = false;
+ private boolean mResumeAfterCall = false;
+
+ private boolean mFMOn = false;
+ private int mFMSearchStations = 0;
+
+ private FmRxRdsData mFMRxRDSData=null;
+ final Handler mHandler = new Handler();
+ private BroadcastReceiver mHeadsetReceiver = null;
+ boolean mHeadsetPlugged = false;
+ // Track A2dp Device status changes
+ private A2dpDeviceStatus mA2dpDeviceState = null;
+ // interval after which we stop the service when idle
+ private static final int IDLE_DELAY = 60000;
+
+ private static String RText = " ";
+ private IAudioService mAudioService;
+ private AudioManager mAudioManager;
+ private Metadata mMetadata;
+ RdsDisplay mRds;
+
+ public FMTransmitterService() {
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mCallbacks = null;
+ mPrefs = new FmSharedPreferences(this);
+
+ PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
+ mWakeLock.setReferenceCounted(false);
+
+ // If the service was idle, but got killed before it stopped itself, the
+ // system will relaunch it. Make sure it gets stopped again in that case.
+
+ TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+ //register for A2DP utility interface
+ mA2dpDeviceState = new A2dpDeviceStatus(getApplicationContext());
+ Message msg = mDelayedStopHandler.obtainMessage();
+ mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+ mAudioManager = new AudioManager(getApplicationContext());
+ mMetadata = new Metadata();
+ registerHeadsetListener();
+ mRds = new RdsDisplay();
+ mAudioManager.registerRemoteControlDisplay(mRds);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(LOGTAG, "onDestroy");
+ if (isFmOn())
+ {
+ Log.e(LOGTAG, "Service being destroyed while still playing.");
+ }
+
+ // make sure there aren't any other messages coming
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ mAudioManager.unregisterRemoteControlDisplay(mRds);
+
+ /* Unregister the headset Broadcase receiver */
+ if (mHeadsetReceiver != null) {
+ unregisterReceiver(mHeadsetReceiver);
+ mHeadsetReceiver = null;
+ }
+ /* Since the service is closing, disable the receiver */
+ fmOff();
+
+ TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ tmgr.listen(mPhoneStateListener, 0);
+
+ mWakeLock.release();
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ mServiceInUse = true;
+ /* Application/UI is attached, so get out of lower power mode */
+ setLowPowerMode(false);
+ Log.d(LOGTAG, "onBind");
+ return mBinder;
+ }
+
+ @Override
+ public void onRebind(Intent intent) {
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ mServiceInUse = true;
+ /* Application/UI is attached, so get out of lower power mode */
+ setLowPowerMode(false);
+ Log.d(LOGTAG, "onRebind");
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ Log.d(LOGTAG, "onStart");
+ mServiceStartId = startId;
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+
+ // make sure the service will shut down on its own if it was
+ // just started but not bound to and nothing is playing
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ Message msg = mDelayedStopHandler.obtainMessage();
+ mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mServiceInUse = false;
+ Log.d(LOGTAG, "onUnbind");
+
+ /* Application/UI is not attached, so go into lower power mode */
+ unregisterCallbacks();
+ setLowPowerMode(true);
+ if (isFmOn())
+ {
+ // something is currently playing, or will be playing once
+ // an in-progress call ends, so don't stop the service now.
+ return true;
+ }
+
+ stopSelf(mServiceStartId);
+ return true;
+ }
+
+ /* Handle Phone Call + FM Concurrency */
+ private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ Log.d(LOGTAG, "onCallStateChanged: State - " + state );
+ Log.d(LOGTAG, "onCallStateChanged: incomingNumber - " + incomingNumber );
+ fmActionOnCallState(state );
+ }
+
+ // NEED TO CHECK ACTION TO BE TAKEN ON DATA ACTIVITY
+ };
+ private void fmActionOnCallState( int state ) {
+ //if Call Status is non IDLE we need to Mute FM as well stop recording if
+ //any. Similarly once call is ended FM should be unmuted.
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if((TelephonyManager.CALL_STATE_OFFHOOK == state)||
+ (TelephonyManager.CALL_STATE_RINGING == state)) {
+ if (state == TelephonyManager.CALL_STATE_RINGING) {
+ int ringvolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
+ if (ringvolume == 0) {
+ return;
+ }
+ }
+ if( mFMOn == true) {
+ Log.d(LOGTAG, "posting for call state change");
+ mHandler.post(mChangeFMTxState);
+ mResumeAfterCall = true;
+ }
+ }
+ else if (state == TelephonyManager.CALL_STATE_IDLE) {
+ // start playing again
+ if (mResumeAfterCall)
+ {
+ Log.d(LOGTAG, "posting for call state change");
+ mHandler.post(mChangeFMTxState);
+ mResumeAfterCall = false;
+ }
+ }//idle
+ }
+
+ private Handler mDelayedStopHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ // Check again to make sure nothing is playing right now
+ if (isFmOn() || mServiceInUse)
+ {
+ return;
+ }
+ Log.d(LOGTAG, "mDelayedStopHandler: stopSelf");
+ stopSelf(mServiceStartId);
+ }
+ };
+
+ /* Show the FM Notification */
+ public void startNotification() {
+ RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
+ views.setImageViewResource(R.id.icon, R.drawable.ic_status_fm_tx);
+ if (isFmOn())
+ {
+ views.setTextViewText(R.id.frequency, getTunedFrequencyString());
+ } else
+ {
+ views.setTextViewText(R.id.frequency, "");
+ }
+
+ Notification status = new Notification();
+ status.contentView = views;
+ status.flags |= Notification.FLAG_ONGOING_EVENT;
+ status.icon = R.drawable.ic_status_fm_tx;
+ status.contentIntent = PendingIntent.getActivity(this, 0,
+ new Intent("com.caf.fmradio.FMTRANSMITTER_ACTIVITY"), 0);
+ startForeground(FMTRANSMITTERSERVICE_STATUS, status);
+ //NotificationManager nm = (NotificationManager)
+ // getSystemService(Context.NOTIFICATION_SERVICE);
+ //nm.notify(FMTRANSMITTERSERVICE_STATUS, status);
+ //setForeground(true);
+ mFMOn = true;
+ }
+
+ private void stop() {
+ gotoIdleState();
+ mFMOn = false;
+ }
+
+ private void gotoIdleState() {
+ mDelayedStopHandler.removeCallbacksAndMessages(null);
+ Message msg = mDelayedStopHandler.obtainMessage();
+ mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
+ //NotificationManager nm =
+ //(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ //nm.cancel(FMTRANSMITTERSERVICE_STATUS);
+ //setForeground(false);
+ stopForeground(true);
+ }
+
+ /*
+ * By making this a static class with a WeakReference to the Service, we
+ * ensure that the Service can be GCd even when the system process still
+ * has a remote reference to the stub.
+ */
+ static class ServiceStub extends IFMTransmitterService.Stub
+ {
+ WeakReference<FMTransmitterService> mService;
+
+ ServiceStub(FMTransmitterService service)
+ {
+ mService = new WeakReference<FMTransmitterService>(service);
+ }
+
+ public boolean fmOn() throws RemoteException
+ {
+ return(mService.get().fmOn());
+ }
+
+ public boolean fmOff() throws RemoteException
+ {
+ return(mService.get().fmOff());
+ }
+ public boolean fmRadioReset() throws RemoteException
+ {
+ return(mService.get().fmRadioReset());
+ }
+ public boolean fmRestart() throws RemoteException
+ {
+ return(mService.get().fmRestart());
+ }
+
+ public boolean isFmOn()
+ {
+ return(mService.get().isFmOn());
+ }
+ public boolean fmReconfigure()
+ {
+ return(mService.get().fmReconfigure());
+ }
+
+ public void registerCallbacks(IFMTransmitterServiceCallbacks cb)
+ throws RemoteException {
+ mService.get().registerCallbacks(cb);
+ }
+
+ public boolean searchWeakStationList(int numStations)
+ throws RemoteException {
+ return(mService.get().searchWeakStationList(numStations));
+ }
+
+ public void unregisterCallbacks() throws RemoteException
+ {
+ mService.get().unregisterCallbacks();
+ }
+
+ public boolean tune(int frequency)
+ {
+ return(mService.get().tune(frequency));
+ }
+
+ public boolean cancelSearch()
+ {
+ return(mService.get().cancelSearch());
+ }
+
+ public String getRadioText()
+ {
+ return(mService.get().getRadioText());
+ }
+
+ public int[] getSearchList()
+ {
+ return(mService.get().getSearchList());
+ }
+
+ public boolean isInternalAntennaAvailable()
+ {
+ return(mService.get().isInternalAntennaAvailable());
+ }
+ public boolean isHeadsetPlugged()
+ {
+ return(mService.get().isHeadsetPlugged());
+ }
+ public boolean isCallActive()
+ {
+ return(mService.get().isCallActive());
+ }
+ public String getPSData()
+ {
+ return(mService.get().getPSData());
+ }
+ }
+
+ private final IBinder mBinder = new ServiceStub(this);
+ /*
+ * Turn ON FM: Powers up FM hardware, and initializes the FM module
+ * .
+ * @return true if fm Enable api was invoked successfully, false if the api failed.
+ */
+ private boolean fmOn() {
+ boolean bStatus=false;
+
+ Log.d(LOGTAG, "fmOn");
+ mWakeLock.acquire(10*1000);
+ if (mTransmitter == null) {
+ try {
+ mTransmitter = new FmTransmitter(FMRADIO_DEVICE_FD_STRING, transmitCallbacks);
+ Log.d(LOGTAG, "new transmitter created");
+ } catch (InstantiationException e) {
+ throw new RuntimeException("FmTx service not available!");
+ }
+ }
+ if (mTransmitter != null)
+ {
+ if (isFmOn())
+ {
+ /* FM Is already on,*/
+ bStatus = true;
+ try {
+ if(mCallbacks != null) {
+ mCallbacks.onEnabled(true);
+ }
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ Log.d(LOGTAG, "mTransmitter is enabled");
+ }
+ else
+ {
+ // This sets up the FM radio device
+ FmConfig config = FmSharedPreferences.getFMConfiguration();
+ Log.d(LOGTAG, "fmOn: RadioBand :"+ config.getRadioBand());
+ Log.d(LOGTAG, "fmOn: Emphasis :"+ config.getEmphasis());
+ Log.d(LOGTAG, "fmOn: ChSpacing :"+ config.getChSpacing());
+ Log.d(LOGTAG, "fmOn: RdsStd :"+ config.getRdsStd());
+ Log.d(LOGTAG, "fmOn: LowerLimit :"+ config.getLowerLimit());
+ Log.d(LOGTAG, "fmOn: UpperLimit :"+ config.getUpperLimit());
+
+ boolean bFmRxEnabled = false;
+
+ if (!mA2dpDeviceState.isDeviceAvailable()) {
+ bStatus = mTransmitter.enable(config);
+ }
+ if( false == bStatus ) {
+ Log.e(LOGTAG,"FM Enable failed");
+ return bStatus;
+ }
+ bStatus = mTransmitter.setTxPowerLevel(FmTransmitter.FM_TX_PWR_LEVEL_7);
+
+ if( false == bStatus ) {
+ Log.e(LOGTAG,"FM setPowerLevel failed");
+ return bStatus;
+ }
+
+ Log.e(LOGTAG, "FMTx is on: Requesting to start FM TX");
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_FM_TX,
+ AudioSystem.DEVICE_STATE_AVAILABLE, "");
+ }
+
+ if(true == bStatus )
+ {
+ bStatus = mTransmitter.setRdsOn();
+ if( true != bStatus ) {
+ Log.d(LOGTAG, "FMTx setRdsOn failed");
+ } else {
+ if(false == mTransmitter.getInternalAntenna()) {
+ Log.d(LOGTAG, "Setting internal antenna explicitly");
+ mTransmitter.setInternalAntenna(true);
+ }
+ startNotification();
+ }
+ }
+ else
+ {
+ stop();
+ }
+ }
+ return(bStatus);
+ }
+
+ /*
+ * Turn OFF FM Operations: This disables all the current FM operations .
+ */
+ private void fmOperationsOff() {
+
+ Log.d(LOGTAG, "fmOperationsOff" );
+
+ Log.e(LOGTAG, "FMTx is off: Requesting to stop FM Tx");
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_FM_TX,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, "");
+ }
+ /*
+ * Turn OFF FM: Disable the FM Host and hardware .
+ * .
+ * @return true if fm Disable api was invoked successfully, false if the api failed.
+ */
+ private boolean fmOff() {
+ boolean bStatus=false;
+ Log.d(LOGTAG, "fmOff");
+
+ fmOperationsOff();
+
+ // This will disable the FM radio device
+ if (mTransmitter != null)
+ {
+ bStatus = mTransmitter.disable();
+ mTransmitter = null;
+ }
+ /* Disable Receiver */
+ if (mReceiver != null)
+ {
+ bStatus = mReceiver.disable();
+ mReceiver = null;
+ }
+ RText = " ";
+ stop();
+ return(bStatus);
+ }
+ /*
+ * Turn OFF FM: Disable the FM Host when hardware resets asynchronously .
+ * .
+ * @return true if fm Reset api was invoked successfully, false if the api failed .
+ */
+ private boolean fmRadioReset() {
+ boolean bStatus=false;
+ Log.d(LOGTAG, "fmRadioReset");
+ fmOperationsOff();
+
+ // This will disable the FM radio device
+ if (mTransmitter != null)
+ {
+ bStatus = mTransmitter.reset();
+ mTransmitter = null;
+ }
+ /* Disable Receiver */
+ if (mReceiver != null)
+ {
+ bStatus = mReceiver.reset();
+ mReceiver = null;
+ }
+ stop();
+ return(bStatus);
+ }
+
+ /*
+ * Restart FM Transmitter: Disables FM receiver mode or transmitter is already active
+ * and Powers up FM hardware, and initializes the FM module
+ *
+ * @return true if fm Enable api was invoked successfully, false if the api failed.
+ */
+
+ private boolean fmRestart() {
+ boolean bStatus=false;
+ Log.d(LOGTAG, "fmRestart");
+
+ /* First Disable Transmitter, if enabled */
+ if (mTransmitter != null)
+ {
+ bStatus = mTransmitter.disable();
+ mTransmitter = null;
+ mFMOn = false;
+ }
+
+ /* Disable Receiver */
+ if (mReceiver != null)
+ {
+ bStatus = mReceiver.disable();
+ mReceiver = null;
+ }
+ try {
+ Thread.sleep(100);//sleep needed for SoC to switch mode
+ }
+ catch ( Exception ex ) {
+ Log.d( LOGTAG, "RunningThread InterruptedException");
+ }
+ bStatus = fmOn();
+ return(bStatus);
+ }
+
+ /* Returns whether FM hardware is ON.
+ *
+ * @return true if FM was tuned, searching. (at the end of
+ * the search FM goes back to tuned).
+ *
+ */
+ public boolean isFmOn() {
+ return mFMOn;
+ }
+
+ /*
+ * ReConfigure the FM Setup parameters
+ * - Band
+ * - Channel Spacing (50/100/200 KHz)
+ * - Emphasis (50/75)
+ * - Frequency limits
+ * - RDS/RBDS standard
+ *
+ * @return true if configure api was invoked successfully, false if the api failed.
+ */
+ public boolean fmReconfigure() {
+ boolean bStatus=false;
+ Log.d(LOGTAG, "fmReconfigure");
+ if (mTransmitter != null)
+ {
+ // This sets up the FM radio device
+ FmConfig config = FmSharedPreferences.getFMConfiguration();
+ Log.d(LOGTAG, "RadioBand :"+ config.getRadioBand());
+ Log.d(LOGTAG, "Emphasis :"+ config.getEmphasis());
+ Log.d(LOGTAG, "ChSpacing :"+ config.getChSpacing());
+ Log.d(LOGTAG, "RdsStd :"+ config.getRdsStd());
+ Log.d(LOGTAG, "LowerLimit :"+ config.getLowerLimit());
+ Log.d(LOGTAG, "UpperLimit :"+ config.getUpperLimit());
+ bStatus = mTransmitter.configure(config);
+ }
+ if (mCallbacks != null)
+ {
+ try
+ {
+ mCallbacks.onReconfigured();
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return(bStatus);
+ }
+
+ /*
+ * Register UI/Activity Callbacks
+ */
+ public void registerCallbacks(IFMTransmitterServiceCallbacks cb)
+ {
+ mCallbacks = cb;
+ }
+
+ /*
+ * unRegister UI/Activity Callbacks
+ */
+ public void unregisterCallbacks()
+ {
+ mCallbacks=null;
+ }
+
+ /* Tunes to the specified frequency
+ *
+ * @return true if Tune command was invoked successfully, false if not muted.
+ * Note: Callback FmRxEvRadioTuneStatus will be called when the tune
+ * is complete
+ */
+ public boolean tune(int frequency) {
+ boolean bCommandSent=false;
+ double doubleFrequency = frequency/1000.00;
+
+ Log.d(LOGTAG, "tune: " + doubleFrequency);
+ if (mTransmitter != null)
+ {
+ mTransmitter.setStation(frequency);
+ mTunedFrequency = frequency;
+ bCommandSent = true;
+ }
+ return bCommandSent;
+ }
+
+ /* Search for the weakest 12 FM Stations in the current band.
+ *
+ * It searches in the forward direction relative to the current tuned station.
+ * int numStations: maximum number of stations to search.
+ *
+ * @return true if Search command was invoked successfully, false if not muted.
+ * Note: 1. Callback FmRxEvSearchListComplete will be called when the Search
+ * is complete
+ * 2. Callback FmRxEvRadioTuneStatus will also be called when tuned to
+ * the previously tuned station.
+ */
+ public boolean searchWeakStationList(int numStations)
+ {
+
+ boolean bStatus=false;
+ FmConfig config = FmSharedPreferences.getFMConfiguration();
+
+ if(null != mTransmitter) {
+ mTransmitter.disable();
+ mTransmitter = null;
+ mFMOn = false;
+ }
+ if(null != mReceiver) {
+ mReceiver.disable();
+ mReceiver = null;
+ }
+ try {
+ Thread.sleep(100);//SoC needs a delay to switch mode
+ } catch (Exception ex) {
+ Log.d( LOGTAG, "RunningThread InterruptedException");
+ }
+
+
+ if(null == mReceiver) {
+ try {
+ mReceiver = new FmReceiver(FMRADIO_DEVICE_FD_STRING, fmCallbacks);
+ }
+ catch (InstantiationException e){
+ throw new RuntimeException("FmTx service not available!");
+ }
+ if (mReceiver.getFMState() == mReceiver.FMState_Turned_Off) {
+ bStatus = mReceiver.enable(config);
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (Exception ex) {
+ Log.d( LOGTAG, "RunningThread InterruptedException");
+ }
+ bStatus = mReceiver.enable(config);
+ }
+ if (!bStatus) {
+ Log.e( LOGTAG, "Search for weak station failed");
+ return false;
+ }
+ }
+
+ bStatus = mReceiver.setStation(config.getLowerLimit());
+
+ Log.d(LOGTAG, "mReceiver.setStation: bStatus: " + bStatus);
+ bStatus = mReceiver.searchStationList( FmReceiver.FM_RX_SRCHLIST_MODE_WEAKEST,
+ FmReceiver.FM_RX_SEARCHDIR_UP,
+ numStations,
+ 0);
+
+ mFMSearchStations = 0;//numStations;
+ if(bStatus == false)
+ {
+ try
+ {
+ if (mCallbacks != null)
+ {
+ mCallbacks.onSearchListComplete(false);
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return bStatus;
+ }
+
+ /* Cancel any ongoing Search (Seek/Scan/SearchStationList).
+ *
+ * @return true if Search command was invoked successfully, false if not muted.
+ * Note: 1. Callback FmRxEvSearchComplete will be called when the Search
+ * is complete/cancelled.
+ * 2. Callback FmRxEvRadioTuneStatus will also be called when tuned to a station
+ * at the end of the Search or if the seach was cancelled.
+ */
+ public boolean cancelSearch()
+ {
+ boolean bStatus=false;
+ if (mReceiver != null)
+ {
+ bStatus = mReceiver.cancelSearch();
+ Log.d(LOGTAG, "mReceiver.cancelSearch: bStatus: " + bStatus);
+ try
+ {
+ if (mCallbacks != null)
+ {
+ mCallbacks.onSearchListComplete(false);
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+ return bStatus;
+ }
+
+ /* Retrieves the basic String to be displayed on UI
+ * Other than this static string the RDS String will be
+ * queried by Tx Activity to update on UI
+ */
+ public String getRadioText() {
+ String str = "Radio Text: Transmitting ";
+ Log.d(LOGTAG, "Radio Text: [" + str + "]");
+ return str;
+ }
+
+ /* Retrieves the station list from the SearchStationlist.
+ *
+ * @return Array of integers that represents the station frequencies.
+ * Note: 1. This is a synchronous call that should typically called when
+ * Callback onSearchListComplete.
+ */
+ public int[] getSearchList()
+ {
+ int[] frequencyList = null;
+ if (mReceiver != null)
+ {
+ Log.d(LOGTAG, "getSearchList: ");
+ frequencyList = mReceiver.getStationList();
+ }
+ return frequencyList;
+ }
+ /* Set the FM Power Mode on the FM hardware SoC.
+ * Typically used when UI/Activity is in the background, so the Host is interrupted less often.
+ *
+ * boolean bLowPower: true: Enable Low Power mode on FM hardware.
+ * false: Disable Low Power mode on FM hardware. (Put into normal power mode)
+ * @return true if set power mode api was invoked successfully, false if the api failed.
+ */
+ public boolean setLowPowerMode(boolean bLowPower)
+ {
+ boolean bCommandSent=false;
+ if (mTransmitter != null)
+ {
+ Log.d(LOGTAG, "setLowPowerMode: " + bLowPower);
+ if(bLowPower)
+ {
+ bCommandSent = mTransmitter.setPowerMode(FmTransmitter.FM_TX_LOW_POWER_MODE);
+ }
+ else
+ {
+ bCommandSent = mTransmitter.setPowerMode(FmTransmitter.FM_TX_NORMAL_POWER_MODE);
+ }
+ }
+ return bCommandSent;
+ }
+ /** Determines if an internal Antenna is available.
+ *
+ * @return true if internal antenna is available, false if
+ * internal antenna is not available.
+ */
+ public boolean isInternalAntennaAvailable()
+ {
+ boolean bAvailable = false;
+ /* Update this when the API is available */
+
+ if(null != mTransmitter ) {
+ bAvailable = mTransmitter.getInternalAntenna();
+ Log.d(LOGTAG, "internalAntennaAvailable: " + bAvailable);
+ } else if( null != mReceiver ) {
+ bAvailable = mReceiver.getInternalAntenna();
+ Log.d(LOGTAG, "internalAntennaAvailable: " + bAvailable);
+ }
+ return bAvailable;
+ }
+
+
+ private FmTransmitterCallbacksAdaptor transmitCallbacks = new FmTransmitterCallbacksAdaptor() {
+ public void FmTxEvRDSGroupsAvailable() {
+ // Do nothing
+ }
+
+ public void FmTxEvRDSGroupsComplete() {
+ // Do nothing
+ }
+ public void FmTxEvContRDSGroupsComplete() {
+ }
+
+ public void FmTxEvTuneStatusChange(int freq) {
+
+ Log.d(LOGTAG, "onTuneStatusChange\n");
+ if (mCallbacks != null)
+ {
+ try
+ {
+ mCallbacks.onTuneStatusChanged(freq);
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ /* Update the frequency in the StatusBar's Notification */
+ startNotification();
+
+ String s = getPSData();
+ if( true == mTransmitter.startPSInfo(
+ s, FM_TX_PROGRAM_TYPE, FM_TX_PROGRAM_ID, FM_TX_PS_REPEAT_COUNT ) ) {
+ if (mCallbacks != null)
+ {
+ try
+ {
+ mCallbacks.onPSInfoSent(s);
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (mTransmitter != null ){
+ mTransmitter.startRTInfo(RText, FM_TX_PROGRAM_TYPE, FM_TX_PROGRAM_ID );
+ }
+
+ try {
+ if (mCallbacks != null ) {
+ mCallbacks.onMetaDataChanged(RText);
+ } else {
+ Log.d(LOGTAG, "callback is not there");
+ }
+ } catch (RemoteException ex){
+ ex.printStackTrace();
+ }
+
+ }
+
+ public void FmTxEvRadioDisabled() {
+ Log.d(LOGTAG, "onRadioDisabled");
+ mFMOn = false;
+ if((mServiceInUse) && (mCallbacks != null) ) {
+ try {
+ mCallbacks.onDisabled();
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ public void FmTxEvRadioEnabled() {
+ mFMOn = true;
+ if((mServiceInUse) && (mCallbacks != null) ) {
+ try {
+ mCallbacks.onEnabled(true);
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ public void FmTxEvRadioReset() {
+ if(isFmOn()) {
+ // Received Radio Disable event unexpectedly
+ Log.d(LOGTAG, "FM is ON, reset FM");
+ fmRadioReset();
+ try {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if((mServiceInUse) && (mCallbacks != null)) {
+ mCallbacks.onRadioReset();
+ }
+ }
+ catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ /* Receiver callbacks back from the FM Stack */
+ FmRxEvCallbacksAdaptor fmCallbacks = new FmRxEvCallbacksAdaptor()
+ {
+ public void FmRxEvEnableReceiver()
+ {
+ Log.d(LOGTAG, "FmRxEvEnableReceiver");
+ }
+ public void FmRxEvDisableReceiver()
+ {
+ Log.d(LOGTAG, "FmRxEvDisableReceiver");
+ }
+ public void FmRxEvRadioReset()
+ {
+ Log.d(LOGTAG, "FmRxEvRadioReset");
+ if(isFmOn()) {
+ // Received Radio Reset event
+ Log.d(LOGTAG, "FM is ON, reset FM");
+ fmRadioReset();
+ try
+ {
+ /* Notify the UI/Activity, only if the service is "bound"
+ by an activity and if Callbacks are registered
+ */
+ if((mServiceInUse) && (mCallbacks != null) )
+ {
+ mCallbacks.onRadioReset();
+ }
+ }
+ catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void FmRxEvRadioTuneStatus(int frequency)
+ {
+ Log.d(LOGTAG, "FmRxEvRadioTuneStatus: Tuned Frequency: " +frequency);
+ }
+
+ public void FmRxEvSearchListComplete()
+ {
+ Log.d(LOGTAG, "FmRxEvSearchListComplete");
+ try
+ {
+ if (mCallbacks != null) {
+ mCallbacks.onSearchListComplete(true);
+ } else if(mReceiver != null) {
+ mReceiver.disable();
+ }
+ } catch (RemoteException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ };
+
+
+ /*
+ * Read the Tuned Frequency from the FM module.
+ */
+ private String getTunedFrequencyString() {
+ double frequency = mTunedFrequency / 1000.0;
+ String frequencyString = getString(R.string.stat_notif_tx_frequency, (""+frequency));
+ return frequencyString;
+ }
+ /**
+ * Registers an intent to listen for ACTION_HEADSET_PLUG
+ * notifications. This intent is called to know if the headset
+ * was plugged in/out
+ */
+ public void registerHeadsetListener() {
+ if (mHeadsetReceiver == null) {
+ mHeadsetReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
+ Log.d(LOGTAG, "ACTION_HEADSET_PLUG Intent received");
+ // Listen for ACTION_HEADSET_PLUG broadcasts.
+ Log.d(LOGTAG, "mTransmitter: ACTION_HEADSET_PLUG");
+ Log.d(LOGTAG, "==> intent: " + intent);
+ Log.d(LOGTAG, " state: " + intent.getIntExtra("state", 0));
+ Log.d(LOGTAG, " name: " + intent.getStringExtra("name"));
+ mHeadsetPlugged = (intent.getIntExtra("state", 0) == 1);
+ mHandler.post(mChangeFMTxState);
+
+ } else if (mA2dpDeviceState.isA2dpStateChange(action)) {
+ if (mA2dpDeviceState.isConnected(intent)){
+ Log.d(LOGTAG, " A2DP connected");
+ mHeadsetPlugged = true;
+ if( mFMOn == true)
+ {
+ mHandler.post(mChangeFMTxState);
+ }
+ } else if( !mA2dpDeviceState.isConnected(intent)) {
+ Log.d(LOGTAG, "A2DP disconnected");
+ mHeadsetPlugged = false;
+ if( mFMOn == false) // when FM Tx App open, DISC BT
+ {
+ Log.d(LOGTAG, "posting for a2dp state change");
+ mHandler.post(mChangeFMTxState);
+ }
+ }
+ } else if (action.equals("HDMI_CONNECTED")) {
+ if( mFMOn == true) {
+ Log.d(LOGTAG, "posting for hdmi state change");
+ mHandler.post(mChangeFMTxState);
+ }
+ }
+ }
+ };
+ IntentFilter iFilter = new IntentFilter();
+ iFilter.addAction(Intent.ACTION_HEADSET_PLUG);
+ iFilter.addAction(mA2dpDeviceState.getActionSinkStateChangedString());
+ iFilter.addAction(mA2dpDeviceState.getActionPlayStateChangedString());
+ iFilter.addAction("HDMI_CONNECTED");
+ iFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ registerReceiver(mHeadsetReceiver, iFilter);
+ }
+ }
+ final Runnable mChangeFMTxState = new Runnable() {
+ public void run() {
+ boolean bStatus = false;
+
+ Log.d(LOGTAG, "Enter change FM Tx State");
+ /* Update the UI based on the state change of the headset/antenna*/
+ if(mHeadsetPlugged) {
+ bStatus = cancelSearch();
+ if(bStatus == false)
+ Log.e(LOGTAG, "Error in cancelling the search");
+ if(isFmOn()) {
+ Log.d(LOGTAG, "disable called from headset handler");
+ bStatus = fmOff();
+ if(mServiceInUse && (mCallbacks != null) && (bStatus == true)) {
+ try {
+ mCallbacks.onDisabled();
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ } else if(bStatus == false) {
+ Log.e(LOGTAG, "Error in turning off the FM TX ");
+ }
+ } else if(mReceiver != null) {
+ bStatus = mReceiver.disable();
+ if(bStatus == true)
+ mReceiver = null;
+ else
+ Log.e(LOGTAG, "Error in disabling the FM RX");
+ }
+ }else {
+ if(!isFmOn()) {
+ bStatus = fmOn();
+ if(mServiceInUse && (mCallbacks != null) && (bStatus == true)) {
+ try {
+ mCallbacks.onEnabled(true);
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ } else if(bStatus == false) {
+ Log.e(LOGTAG, "Error in enabling the FM TX");
+ }
+ }
+ }
+ }
+ };
+ private class RdsDisplay extends IRemoteControlDisplay.Stub {
+ RdsDisplay() {
+ }
+
+ @Override
+ public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
+ long currentPosMs, float speed) {
+ }
+
+ @Override
+ public void setMetadata(int generationId, Bundle metadata) {
+ updateMetadata(metadata);
+ }
+
+ @Override
+ public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
+ }
+
+ @Override
+ public void setArtwork(int generationId, Bitmap bitmap) {
+ }
+
+ @Override
+ public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
+ }
+
+ @Override
+ public void setCurrentClientId(int clientGeneration, PendingIntent clientMediaIntent,
+ boolean clearing) {
+ }
+ }
+
+ class Metadata {
+ private String artist;
+ private String trackTitle;
+ private String albumTitle;
+
+ public Metadata() {
+ artist = null;
+ trackTitle = null;
+ albumTitle = null;
+ }
+
+ public String toString() {
+ return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" +
+ albumTitle + "]";
+ }
+ }
+
+ private String getMdString(Bundle data, int id) {
+ return data.getString(Integer.toString(id));
+ }
+
+ private void updateMetadata(Bundle data) {
+ String oldMetadata = mMetadata.toString();
+ mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
+ mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE);
+ mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM);
+ Log.v(LOGTAG, "mMetadata=" + mMetadata.toString());
+ if (mTransmitter != null ){
+ RText = mMetadata.albumTitle +":" + mMetadata.trackTitle +":"+ mMetadata.artist;
+ Log.d(LOGTAG,"RT string size is "+RText.length());
+ mTransmitter.startRTInfo(RText, FM_TX_PROGRAM_TYPE, FM_TX_PROGRAM_ID );
+ }
+
+ try {
+ if (mCallbacks != null ) {
+ mCallbacks.onMetaDataChanged(RText);
+ } else {
+ Log.d(LOGTAG, "callback is not there");
+ }
+ } catch (RemoteException ex){
+ ex.printStackTrace();
+ }
+ }
+ public boolean isHeadsetPlugged() {
+ if (mA2dpDeviceState.isDeviceAvailable())
+ mHeadsetPlugged = true;
+ return mHeadsetPlugged;
+ }
+ public boolean isCallActive() {
+ TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ if( TelephonyManager.CALL_STATE_IDLE !=tmgr.getCallState() )
+ return true;
+ return false;
+ }
+ public String getPSData(){
+ return QFM_STRING;
+ }
+}
diff --git a/fmapp2/src/com/caf/fmradio/FmSharedPreferences.java b/fmapp2/src/com/caf/fmradio/FmSharedPreferences.java
new file mode 100644
index 0000000..db215e8
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FmSharedPreferences.java
@@ -0,0 +1,1114 @@
+/*
+ * Copyright (c) 2009-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import qcom.fmradio.FmReceiver;
+import qcom.fmradio.FmConfig;
+import android.util.Log;
+
+public class FmSharedPreferences
+{
+ public static final int REGIONAL_BAND_NORTH_AMERICA = 0;
+ public static final int REGIONAL_BAND_EUROPE = 1;
+ public static final int REGIONAL_BAND_JAPAN = 2;
+ public static final int REGIONAL_BAND_JAPAN_WIDE = 3;
+ public static final int REGIONAL_BAND_AUSTRALIA = 4;
+ public static final int REGIONAL_BAND_AUSTRIA = 5;
+ public static final int REGIONAL_BAND_BELGIUM = 6;
+ public static final int REGIONAL_BAND_BRAZIL = 7;
+ public static final int REGIONAL_BAND_CHINA = 8;
+ public static final int REGIONAL_BAND_CZECH = 9;
+ public static final int REGIONAL_BAND_DENMARK = 10;
+ public static final int REGIONAL_BAND_FINLAND = 11;
+ public static final int REGIONAL_BAND_FRANCE = 12;
+ public static final int REGIONAL_BAND_GERMANY = 13;
+ public static final int REGIONAL_BAND_GREECE = 14;
+ public static final int REGIONAL_BAND_HONGKONG = 15;
+ public static final int REGIONAL_BAND_INDIA = 16;
+ public static final int REGIONAL_BAND_IRELAND = 17;
+ public static final int REGIONAL_BAND_ITALY = 18;
+ public static final int REGIONAL_BAND_KOREA = 19;
+ public static final int REGIONAL_BAND_MEXICO = 20;
+ public static final int REGIONAL_BAND_NETHERLANDS = 21;
+ public static final int REGIONAL_BAND_NEWZEALAND = 22;
+ public static final int REGIONAL_BAND_NORWAY = 23;
+ public static final int REGIONAL_BAND_POLAND = 24;
+ public static final int REGIONAL_BAND_PORTUGAL = 25;
+ public static final int REGIONAL_BAND_RUSSIA = 26;
+ public static final int REGIONAL_BAND_SINGAPORE = 27;
+ public static final int REGIONAL_BAND_SLOVAKIA = 28;
+ public static final int REGIONAL_BAND_SPAIN = 29;
+ public static final int REGIONAL_BAND_SWITZERLAND = 30;
+ public static final int REGIONAL_BAND_SWEDEN = 31;
+ public static final int REGIONAL_BAND_TAIWAN = 32;
+ public static final int REGIONAL_BAND_TURKEY = 33;
+ public static final int REGIONAL_BAND_UNITEDKINGDOM = 34;
+ public static final int REGIONAL_BAND_UNITED_STATES = 35;
+
+ public static final int RECORD_DUR_INDEX_0_VAL = 5;
+ public static final int RECORD_DUR_INDEX_1_VAL = 15;
+ public static final int RECORD_DUR_INDEX_2_VAL = 30;
+ public static final int RECORD_DUR_INDEX_3_VAL = -1;
+
+ private static final String LOGTAG = "FmSharedPreferences";
+
+ private static final String SHARED_PREFS = "fmradio_prefs";
+ private static final String LIST_NUM = "list_number";
+ private static final String LIST_NAME = "list_name";
+ private static final String STATION_NAME = "station_name";
+ private static final String STATION_FREQUENCY = "station_freq";
+ private static final String STATION_ID = "station_id";
+ private static final String STATION_PTY = "station_pty";
+ private static final String STATION_RDS = "station_rds";
+ private static final String STATION_NUM = "preset_number";
+
+ private static final String FMCONFIG_COUNTRY = "fmconfig_country";
+ //private static final String FMCONFIG_BAND = "fmconfig_band";
+ //private static final String FMCONFIG_MIN = "fmconfig_min";
+ //private static final String FMCONFIG_MAX = "fmconfig_max";
+ //private static final String FMCONFIG_STEP = "fmconfig_step";
+ //private static final String FMCONFIG_EMPH = "fmconfig_emphasis";
+ //private static final String FMCONFIG_RDSSTD = "fmconfig_rdsstd";
+ /* Storage key String */
+ private static final String LAST_LIST_INDEX = "last_list_index";
+ public static final int MAX_NUM_TAG_TYPES = 64;
+ public static final int NUM_TAG_CATEGORY = 8;
+ public static final String []TAG_CATEGORIES = { "DUMMY", "ITEM", "INFO", "PROGRAMME",
+ "INTERACTIVITY", "RFU", "PRIVATE_CLASSES",
+ "DESCRIPTOR" };
+ public static final int [][]TAG_CATEGORY_RANGE = { {0, 0}, {1, 11}, {12, 30},
+ {31, 40}, {41, 53}, {54, 55},
+ {56, 58}, {59, 63} };
+ public static final String []TAG_NAMES = { "DUMMY", "ITEM.TITLE", "ITEM.ALBUM", "ITEM.TRACKNUM",
+ "ITEM.ARTIST", "ITEM.COMPOSITION", "ITEM.MOVEMENT",
+ "ITEM.CONDUCTOR", "ITEM.COMPOSER", "ITEM.BAND", "ITEM.COMMENT",
+ "ITEM.GENERE", "INFO.NEWS", "INFO.NEWS_LOCAL", "INFO.STOCK",
+ "INFO.SPORT", "INFO.LOTTERY", "INFO.HOROSCOPE",
+ "INFO.DAILY_DIVERSION", "INFO.HEALTH", "INFO.EVENT",
+ "INFO.SCENE", "INFO.CINEMA", "INFO.TV", "INFO.DATE_TIME",
+ "INFO.WEATHER", "INFO.TRAFFIC", "INFO.ALARM", "INFO.ADS",
+ "INFO.URL", "INFO.OTHER", "PROGRAMME.STATIONNAME_SHORT",
+ "PROGRAMME.NOW", "PROGRAMME.NEXT", "PROGRAMME.PART",
+ "PROGRAMME.HOST", "PROGRAMME.EDITORIAL_STAFF",
+ "PROGRAMME.FREQUENCY", "PROGRAMME.HOMEPAGE",
+ "PROGRAMME.SUBCHANNEL", "PHONE.HOTLINE", "PHONE.STUDIO",
+ "PHONE.OTHER", "SMS.STUDIO", "SMS.OTHER", "EMAIL.HOTLINE",
+ "EMAIL.STUDIO", "EMAIL.OTHER", "MMS.OTHER", "CHAT",
+ "CHAT.CENTRE", "VOTE.QUESTION", "VOTE.CENTRE", "RFU.1",
+ "RFU.2", "PRIVATE.1", "PRIVATE.2", "PRIVATE.3",
+ "PLACE", "APPOINTMENT", "IDENTIFIER", "PURCHASE", "GET_DATA" };
+
+ private static final String PREF_LAST_TUNED_FREQUENCY = "last_frequency";
+ private static final String LAST_RECORD_DURATION = "last_record_duration";
+ private static String LAST_AF_JUMP_VALUE = "last_af_jump_value";
+ private static final String AUDIO_OUTPUT_MODE = "audio_output_mode";
+
+ private static Map<String, String> mNameMap = new HashMap<String, String>();
+ private static List<PresetList> mListOfPlists = new ArrayList<PresetList>();
+ public static Set[] tagList = new TreeSet[FmSharedPreferences.MAX_NUM_TAG_TYPES];
+ public static int num_tags = 0;
+ private static FmConfig mFMConfiguration;
+
+ private static final String DEFAULT_NO_NAME = "";
+ public static final int DEFAULT_NO_FREQUENCY = 98100;
+ private static final int DEFAULT_NO_PTY = 0;
+ private static final int DEFAULT_NO_STATIONID = 0;
+ private static final int DEFAULT_NO_RDSSUP = 0;
+ private static CharSequence[] mListEntries;
+ private static CharSequence[] mListValues;
+ private static int mListIndex;
+ private Context mContext;
+ private static int mTunedFrequency = 98100;
+ private static int mFrequencyBand_Stepsize = 200;
+
+ private static int mCountry = 0;
+ /* true = Stereo and false = "force Mono" even if Station is transmitting a
+ * Stereo signal
+ */
+ private static boolean mAudioOutputMode = true;
+ private static boolean mAFAutoSwitch = true;
+ private static int mRecordDuration = 0;
+
+ FmSharedPreferences(Context context){
+ mContext = context.getApplicationContext();
+ mFMConfiguration = new FmConfig();
+ Load();
+ }
+
+ public static void removeStation(int listIndex, int stationIndex){
+ if (listIndex < getNumList())
+ {
+ mListOfPlists.get(listIndex).removeStation(stationIndex);
+ }
+ }
+ public static void removeStation(int listIndex, PresetStation station){
+ if (listIndex < getNumList())
+ {
+ mListOfPlists.get(listIndex).removeStation(station);
+ }
+ }
+
+ public static void setListName(int listIndex, String name){
+ if (listIndex < getNumList())
+ {
+ mListOfPlists.get(listIndex).setName(name);
+ }
+ }
+
+ public static void setStationName(int listIndex, int stationIndex, String name){
+ if (listIndex < getNumList())
+ {
+ mListOfPlists.get(listIndex).setStationName(stationIndex, name);
+ }
+ }
+
+ public static String getListName(int listIndex){
+ String name = "";
+ addListIfEmpty(listIndex);
+ if (listIndex < getNumList())
+ {
+ name= mListOfPlists.get(listIndex).getName();
+ }
+ return name;
+ }
+
+ public static String getStationName(int listIndex, int stationIndex){
+ String name = "";
+ if (listIndex < getNumList())
+ {
+ name = mListOfPlists.get(listIndex).getStationName(stationIndex);
+ }
+ return name;
+ }
+
+ public static double getStationFrequency(int listIndex, int stationIndex){
+ double frequency = 0;
+ if (listIndex < getNumList())
+ {
+ frequency = mListOfPlists.get(listIndex).getStationFrequency(stationIndex);
+ }
+ return frequency;
+ }
+
+ public static PresetList getStationList(int listIndex){
+ if (listIndex < getNumList())
+ {
+ return mListOfPlists.get(listIndex);
+ }
+ return null;
+ }
+
+ public static PresetStation getselectedStation(){
+ int listIndex = getCurrentListIndex();
+ PresetStation station = null;
+ if (listIndex < getNumList())
+ {
+ station = mListOfPlists.get(listIndex).getSelectedStation();
+ }
+ return station;
+ }
+
+ public static PresetStation getStationInList(int index){
+ int listIndex = getCurrentListIndex();
+ PresetStation station = null;
+ if (listIndex < getNumList())
+ {
+ station = mListOfPlists.get(listIndex).getStationFromIndex(index);
+ }
+ return station;
+ }
+ public static PresetStation getStationFromFrequency(int frequency){
+ int listIndex = getCurrentListIndex();
+ PresetStation station = null;
+ if(listIndex < getNumList())
+ {
+ station = mListOfPlists.get(listIndex).getStationFromFrequency(frequency);
+ }
+ return station;
+ }
+
+ public static PresetStation selectNextStation(){
+ int listIndex = getCurrentListIndex();
+ PresetStation station = null;
+ if (listIndex < getNumList())
+ {
+ station = mListOfPlists.get(listIndex).selectNextStation();
+ }
+ return station;
+ }
+
+ public static PresetStation selectPrevStation(){
+ int listIndex = getCurrentListIndex();
+ PresetStation station = null;
+ if (listIndex < getNumList())
+ {
+ station = mListOfPlists.get(listIndex).selectPrevStation();
+ }
+ return station;
+ }
+
+ public static void selectStation(PresetStation station){
+ int listIndex = getCurrentListIndex();
+ if (listIndex < getNumList())
+ {
+ mListOfPlists.get(listIndex).selectStation(station);
+ }
+ }
+
+ public static int getNumList(){
+ return mListOfPlists.size();
+ }
+
+ public static int getCurrentListIndex(){
+ return mListIndex;
+ }
+
+ public static void setListIndex(int index){
+ mListIndex = index;
+ }
+
+ public static Map<String, String> getNameMap(){
+ return mNameMap;
+ }
+
+ private static void addListIfEmpty(int listIndex){
+ if ((listIndex < 1) && (getNumList() == 0))
+ {
+ createPresetList("FM");
+ }
+ }
+
+ public static void addStation(String name, int freq, int listIndex){
+ /* If no lists exists and a new station is added, add a new Preset List
+ * if "listIndex" requested was "0"
+ */
+ addListIfEmpty(listIndex);
+ if (getNumList() > listIndex)
+ {
+ mListOfPlists.get(listIndex).addStation(name, freq);
+ }
+ }
+
+ /** Add "station" into the Preset List indexed by "listIndex" */
+ public static void addStation(int listIndex, PresetStation station){
+ /* If no lists exists and a new station is added, add a new Preset List
+ * if "listIndex" requested was "0"
+ */
+ addListIfEmpty(listIndex);
+ if (getNumList() > listIndex)
+ {
+ mListOfPlists.get(listIndex).addStation(station);
+ }
+ }
+ public static void addTags(int index, String s) {
+ if ((index >= 0) && (index <FmSharedPreferences.MAX_NUM_TAG_TYPES)) {
+ if(tagList[index] == null) {
+ tagList[index] = new TreeSet<String>();
+ }
+ if (tagList[index].add(s))
+ num_tags++;
+ }
+ }
+ public static void clearTags() {
+ for(int i = 0; i <FmSharedPreferences.MAX_NUM_TAG_TYPES; i++) {
+ if(tagList[i] != null) {
+ tagList[i].clear();
+ Log.d(LOGTAG, "cleared tags of type" + i);
+ }
+ }
+ num_tags = 0;
+ }
+
+ /** Does "station" already exist in the Preset List indexed by "listIndex" */
+ public static boolean sameStationExists(int listIndex, PresetStation station){
+ boolean exists = false;
+ if (getNumList() > listIndex)
+ {
+ exists = mListOfPlists.get(listIndex).sameStationExists(station);
+ }
+ return exists;
+ }
+
+ /** Does "station" already exist in the current Preset List*/
+ public static boolean sameStationExists( PresetStation station){
+ int listIndex = getCurrentListIndex();
+ boolean exists = false;
+ if (getNumList() > listIndex)
+ {
+ exists = mListOfPlists.get(listIndex).sameStationExists(station);
+ }
+ return exists;
+ }
+
+ /** Does "station" already exist in the current Preset List*/
+ public static int getListStationCount( ){
+ int listIndex = getCurrentListIndex();
+ int numStations = 0;
+ if (getNumList() > listIndex)
+ {
+ numStations = mListOfPlists.get(listIndex).getStationCount();
+ }
+ return numStations;
+ }
+
+ public static void renamePresetList(String newName, int listIndex){
+ PresetList curList = mListOfPlists.get(listIndex);
+ if (curList != null)
+ {
+ String oldListName = curList.getName();
+ curList.setName(newName);
+ String index = mNameMap.get(oldListName);
+ mNameMap.remove(oldListName);
+ mNameMap.put((String) newName, index);
+ repopulateEntryValueLists();
+ }
+ }
+
+ /* Returns the index of the list just created */
+ public static int createPresetList(String name) {
+ int numLists = mListOfPlists.size();
+ mListOfPlists.add(new PresetList(name));
+ String index = String.valueOf(numLists);
+ mNameMap.put(name, index);
+ repopulateEntryValueLists();
+ return numLists;
+ }
+
+
+ public static void createFirstPresetList(String name) {
+ mListIndex = 0;
+ createPresetList(name);
+ }
+
+ public static CharSequence[] repopulateEntryValueLists() {
+ ListIterator<PresetList> presetIter;
+ presetIter = mListOfPlists.listIterator();
+ int numLists = mListOfPlists.size();
+
+ mListEntries = new CharSequence[numLists];
+ mListValues = new CharSequence[numLists];
+ for (int i = 0; i < numLists; i++)
+ {
+ PresetList temp = presetIter.next();
+ mListEntries[i] = temp.getName();
+ mListValues[i] = temp.getName();
+ }
+ return mListEntries;
+ }
+
+ public static List<PresetList> getPresetLists() {
+ return mListOfPlists;
+ }
+
+
+ public void Load(){
+ Log.d(LOGTAG, "Load preferences ");
+ if(mContext == null)
+ {
+ return;
+ }
+ SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFS, Context.MODE_PRIVATE);
+ mTunedFrequency = sp.getInt(PREF_LAST_TUNED_FREQUENCY, DEFAULT_NO_FREQUENCY);
+ mRecordDuration = sp.getInt(LAST_RECORD_DURATION, RECORD_DUR_INDEX_0_VAL);
+ mAFAutoSwitch = sp.getBoolean(LAST_AF_JUMP_VALUE, true);
+ mAudioOutputMode = sp.getBoolean(AUDIO_OUTPUT_MODE, true);
+ /* Reset the Lists before reading the preferences */
+ mListOfPlists.clear();
+
+ int num_lists = sp.getInt(LIST_NUM, 1);
+ if (mListOfPlists.size() == 0) {
+
+ for (int listIter = 0; listIter < num_lists; listIter++) {
+ String listName = sp.getString(LIST_NAME + listIter, "FM - " + (listIter+1));
+ int numStations = sp.getInt(STATION_NUM + listIter, 1);
+ if (listIter == 0) {
+ createFirstPresetList(listName);
+ } else {
+ createPresetList(listName);
+ }
+
+ PresetList curList = mListOfPlists.get(listIter);
+ for (int stationIter = 0; stationIter < numStations; stationIter++) {
+ String stationName = sp.getString(STATION_NAME + listIter + "x" + stationIter,
+ DEFAULT_NO_NAME);
+ int stationFreq = sp.getInt(STATION_FREQUENCY + listIter + "x" + stationIter,
+ DEFAULT_NO_FREQUENCY);
+ PresetStation station = curList.addStation(stationName, stationFreq);
+
+ int stationId = sp.getInt(STATION_ID + listIter + "x" + stationIter,
+ DEFAULT_NO_STATIONID);
+ station.setPI(stationId);
+
+ int pty = sp.getInt(STATION_PTY + listIter + "x" + stationIter, DEFAULT_NO_PTY);
+ station.setPty(pty);
+
+ int rdsSupported = sp.getInt(STATION_RDS + listIter + "x" + stationIter,
+ DEFAULT_NO_RDSSUP);
+ if (rdsSupported != 0) {
+ station.setRDSSupported(true);
+ } else {
+ station.setRDSSupported(false);
+ }
+ }
+ }
+ }
+ /* Load Configuration */
+ setCountry(sp.getInt(FMCONFIG_COUNTRY, 0));
+ /* Last list the user was navigating */
+ mListIndex = sp.getInt(LAST_LIST_INDEX, 0);
+ if(mListIndex >= num_lists)
+ {
+ mListIndex=0;
+ }
+ }
+
+ public void Save() {
+ if(mContext == null)
+ {
+ return;
+ }
+ Log.d(LOGTAG, "Save preferences ");
+
+ int numLists = mListOfPlists.size();
+ SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFS, Context.MODE_PRIVATE);
+ SharedPreferences.Editor ed = sp.edit();
+
+ ed.putInt(PREF_LAST_TUNED_FREQUENCY, mTunedFrequency);
+
+ ed.putInt(LIST_NUM, numLists);
+ /* Last list the user was navigating */
+ ed.putInt(LAST_LIST_INDEX, mListIndex);
+
+ for (int listIter = 0; listIter < numLists; listIter++)
+ {
+ PresetList curList = mListOfPlists.get(listIter);
+ ed.putString(LIST_NAME + listIter, curList.getName());
+ int numStations = curList.getStationCount();
+ ed.putInt(STATION_NUM + listIter, numStations);
+ int numStation = 0;
+ for (int stationIter = 0; stationIter < numStations; stationIter++)
+ {
+ PresetStation station = curList.getStationFromIndex(stationIter);
+ if (station != null)
+ {
+ ed.putString(STATION_NAME + listIter + "x" + numStation,
+ station.getName());
+ ed.putInt(STATION_FREQUENCY + listIter + "x" + numStation,
+ station.getFrequency());
+ ed.putInt(STATION_ID + listIter + "x" + numStation,
+ station.getPI());
+ ed.putInt(STATION_PTY + listIter + "x" + numStation,
+ station.getPty());
+ ed.putInt(STATION_RDS + listIter + "x" + numStation,
+ (station.getRDSSupported() == true? 1:0));
+ numStation ++;
+ }
+ }
+ }
+
+ /* Save Configuration */
+ ed.putInt(FMCONFIG_COUNTRY, mCountry);
+ ed.putInt(LAST_RECORD_DURATION, mRecordDuration);
+ ed.putBoolean(LAST_AF_JUMP_VALUE, mAFAutoSwitch);
+ ed.commit();
+ }
+
+ public static void SetDefaults() {
+ mListIndex = 0;
+ mListOfPlists.clear();
+ setCountry(REGIONAL_BAND_NORTH_AMERICA);
+ setRadioBand(0);
+ setChSpacing(0);
+ setEmphasis(0);
+ setRdsStd(0);
+ mFMConfiguration.setLowerLimit(87500);
+ mFMConfiguration.setUpperLimit(107900);
+ }
+
+ public static void removeStationList(int listIndex) {
+ mListIndex = listIndex;
+ PresetList toRemove = mListOfPlists.get(mListIndex);
+
+ mNameMap.remove(toRemove.getName());
+ mListOfPlists.remove(mListIndex);
+ int numLists = mListOfPlists.size();
+
+ /* Remove for others */
+ for (int i = mListIndex; i < numLists; i++)
+ {
+ PresetList curList = mListOfPlists.get(i);
+ if (curList!=null)
+ {
+ String listName = curList.getName();
+ /* Removals */
+ mNameMap.remove(listName);
+ mNameMap.put(listName, String.valueOf(i));
+ }
+ }
+ mListIndex = 0;
+ repopulateEntryValueLists();
+ }
+
+ public static void setTunedFrequency(int frequency) {
+ mTunedFrequency = frequency;
+ }
+
+ public static int getTunedFrequency() {
+ return mTunedFrequency;
+ }
+
+ public static int getNextTuneFrequency(int frequency) {
+ int nextFrequency = (frequency + mFrequencyBand_Stepsize);
+ if (nextFrequency > getUpperLimit())
+ {
+ nextFrequency = getLowerLimit();
+ }
+ return nextFrequency;
+ }
+
+ public static int getNextTuneFrequency() {
+ int nextFrequency = (mTunedFrequency + mFrequencyBand_Stepsize);
+ if (nextFrequency > getUpperLimit())
+ {
+ nextFrequency = getLowerLimit();
+ }
+ return nextFrequency;
+ }
+
+ public static int getPrevTuneFrequency(int frequency) {
+ int prevFrequency = (frequency - mFrequencyBand_Stepsize);
+ if (prevFrequency < getLowerLimit())
+ {
+ prevFrequency = getUpperLimit();
+ }
+ return prevFrequency;
+ }
+
+ public static int getPrevTuneFrequency() {
+ int prevFrequency = (mTunedFrequency - mFrequencyBand_Stepsize);
+ if (prevFrequency < getLowerLimit())
+ {
+ prevFrequency = getUpperLimit();
+ }
+ return prevFrequency;
+ }
+
+ /**
+ * @param mFMConfiguration the mFMConfiguration to set
+ */
+ public static void setFMConfiguration(FmConfig mFMConfig) {
+ FmSharedPreferences.mFMConfiguration = mFMConfig;
+ }
+
+ /**
+ * @return the mFMConfiguration
+ */
+ public static FmConfig getFMConfiguration() {
+ return mFMConfiguration;
+ }
+
+ public static void setRadioBand(int band)
+ {
+ switch (band)
+ {
+ case FmReceiver.FM_JAPAN_WIDE_BAND:
+ {
+ mFrequencyBand_Stepsize = 50;
+ mFMConfiguration.setLowerLimit(76000);
+ mFMConfiguration.setUpperLimit(108000);
+ break;
+ }
+ case FmReceiver.FM_JAPAN_STANDARD_BAND:
+ {
+ mFrequencyBand_Stepsize = 100;
+ mFMConfiguration.setLowerLimit(76000);
+ mFMConfiguration.setUpperLimit(90000);
+ break;
+ }
+ case FmReceiver.FM_USER_DEFINED_BAND:
+ {
+ break;
+ }
+ case FmReceiver.FM_US_BAND:
+ case FmReceiver.FM_EU_BAND:
+ default:
+ {
+ band = FmReceiver.FM_US_BAND;
+ mFMConfiguration.setLowerLimit(87500);
+ mFMConfiguration.setUpperLimit(107900);
+ mFrequencyBand_Stepsize = 100;
+ }
+ }
+ mFMConfiguration.setRadioBand(band);
+ }
+
+ public static int getRadioBand()
+ {
+ return mFMConfiguration.getRadioBand();
+ }
+
+ public static void setChSpacing(int spacing)
+ {
+ if((spacing>=FmReceiver.FM_CHSPACE_200_KHZ)
+ && (spacing<=FmReceiver.FM_CHSPACE_50_KHZ))
+ {
+ mFrequencyBand_Stepsize = 200;
+ switch (spacing)
+ {
+ case FmReceiver.FM_CHSPACE_100_KHZ:
+ {
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case FmReceiver.FM_CHSPACE_50_KHZ:
+ {
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+ }
+ mFMConfiguration.setChSpacing(spacing);
+ }
+ }
+
+ public static int getChSpacing()
+ {
+ return mFMConfiguration.getChSpacing();
+ }
+
+ public static void setRdsStd(int std)
+ {
+ if((std>=FmReceiver.FM_RDS_STD_RBDS)
+ && (std<=FmReceiver.FM_RDS_STD_NONE))
+ {
+ mFMConfiguration.setRdsStd(std);
+ }
+ }
+
+ public static int getRdsStd()
+ {
+ return mFMConfiguration.getRdsStd();
+ }
+
+ /* North America */
+ public static boolean isRDSStd()
+ {
+ return(FmReceiver.FM_RDS_STD_RDS == mFMConfiguration.getRdsStd());
+ }
+
+ public static boolean isRBDSStd()
+ {
+ return(FmReceiver.FM_RDS_STD_RBDS == mFMConfiguration.getRdsStd());
+ }
+
+ public static void setEmphasis(int emph)
+ {
+ if((emph>=FmReceiver.FM_DE_EMP75)
+ && (emph<=FmReceiver.FM_DE_EMP50))
+ {
+ mFMConfiguration.setEmphasis(emph);
+ }
+ }
+
+ public static int getEmphasis()
+ {
+ return mFMConfiguration.getEmphasis();
+ }
+
+ public static int getUpperLimit()
+ {
+ return mFMConfiguration.getUpperLimit();
+ }
+
+ public static int getLowerLimit()
+ {
+ return mFMConfiguration.getLowerLimit();
+ }
+ public static int getFrequencyStepSize() {
+ return mFrequencyBand_Stepsize;
+ }
+ public static void setLowerLimit(int lowLimit){
+ mFMConfiguration.setLowerLimit(lowLimit);
+ }
+
+ public static void setUpperLimit(int upLimit){
+ mFMConfiguration.setUpperLimit(upLimit);
+ }
+
+ public static void setCountry(int nCountryCode){
+
+ // Default: 87500 TO 10800 IN 100 KHZ STEPS
+ mFMConfiguration.setRadioBand(FmReceiver.FM_USER_DEFINED_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_100_KHZ);
+ mFMConfiguration.setEmphasis(FmReceiver.FM_DE_EMP50);
+ mFMConfiguration.setRdsStd(FmReceiver.FM_RDS_STD_RDS);
+ mFMConfiguration.setLowerLimit(87500);
+ mFMConfiguration.setUpperLimit(108000);
+
+ switch(nCountryCode)
+ {
+ case REGIONAL_BAND_NORTH_AMERICA:
+ {
+ /*NORTH_AMERICA : 87500 TO 108000 IN 200 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_US_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_200_KHZ);
+ mFMConfiguration.setEmphasis(FmReceiver.FM_DE_EMP75);
+ mFMConfiguration.setRdsStd(FmReceiver.FM_RDS_STD_RBDS);
+ mFMConfiguration.setLowerLimit(87500);
+ mFMConfiguration.setUpperLimit(107900);
+ mFrequencyBand_Stepsize = 200;
+ break;
+ }
+ case REGIONAL_BAND_EUROPE:
+ {
+ /*EUROPE/Default : 87500 TO 10800 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+
+ case REGIONAL_BAND_JAPAN:
+ {
+ /*JAPAN : 76000 TO 90000 IN 100 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_JAPAN_STANDARD_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_100_KHZ);
+ mFMConfiguration.setEmphasis(FmReceiver.FM_DE_EMP75);
+ mFMConfiguration.setLowerLimit(76000);
+ mFMConfiguration.setUpperLimit(90000);
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_JAPAN_WIDE:
+ {
+ /*JAPAN_WB : 90000 TO 108000 IN 50 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_JAPAN_WIDE_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_50_KHZ);
+ mFMConfiguration.setEmphasis(FmReceiver.FM_DE_EMP75);
+ mFMConfiguration.setLowerLimit(90000);
+ mFMConfiguration.setUpperLimit(108000);
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+
+ /* Country specific */
+ case REGIONAL_BAND_AUSTRALIA:
+ {
+ /*AUSTRALIA : 87700 TO 108000 IN 100 KHZ STEPS*/
+ mFMConfiguration.setLowerLimit(87700);
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_AUSTRIA:
+ {
+ /*AUSTRIA : 87500 TO 108000 IN 50 KHZ STEPS*/
+ mFMConfiguration.setEmphasis(FmReceiver.FM_DE_EMP50);
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+ case REGIONAL_BAND_BELGIUM:
+ {
+ /*BELGIUM : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_BRAZIL:
+ {
+ /*BRAZIL : 87500 TO 108000 IN 200 KHZ STEP*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_US_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_200_KHZ);
+ mFMConfiguration.setLowerLimit(87500);
+ mFMConfiguration.setUpperLimit(107900);
+ mFrequencyBand_Stepsize = 200;
+ break;
+ }
+ case REGIONAL_BAND_CHINA:
+ {
+ /*CHINA : 87000 TO 108000 IN 100 KHZ STEPS*/
+ mFMConfiguration.setLowerLimit(87000);
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_CZECH:
+ {
+ /*CZECH : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_DENMARK:
+ {
+ /*DENMARK : 87500 TO 108000 IN 50 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_EU_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_50_KHZ);
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+ case REGIONAL_BAND_FINLAND:
+ {
+ /*FINLAND : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_FRANCE:
+ {
+ /* FRANCE : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_GERMANY:
+ /*GERMANY : 87500 TO 108000 IN 50 KHZ STEPS*/
+ case REGIONAL_BAND_GREECE:
+ /*GREECE : 87500 TO 108000 IN 50 KHZ STEPS*/
+ {
+ mFMConfiguration.setRadioBand(FmReceiver.FM_EU_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_50_KHZ);
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+ case REGIONAL_BAND_HONGKONG:
+ {
+ /*HONG KONG : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_INDIA:
+ {
+ /*INDIA : 91000 TO 106400 IN 100 KHZ STEPS*/
+ mFMConfiguration.setLowerLimit(91000);
+ mFMConfiguration.setUpperLimit(106400);
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_IRELAND:
+ {
+ /*IRELAND : 87500 TO 108000 IN 50 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_EU_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_50_KHZ);
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+ case REGIONAL_BAND_ITALY:
+ {
+ /*ITALY : 87500 TO 108000 IN 50 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_EU_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_50_KHZ);
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+ case REGIONAL_BAND_KOREA:
+ {
+ /*KOREA : 87500 TO 108000 IN 200 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_US_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_200_KHZ);
+ mFMConfiguration.setUpperLimit(107900);
+ mFrequencyBand_Stepsize = 200;
+ break;
+ }
+ case REGIONAL_BAND_MEXICO:
+ {
+ /*MEXICO : 88100 TO 107900 IN 200 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_US_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_200_KHZ);
+ mFMConfiguration.setEmphasis(FmReceiver.FM_DE_EMP75);
+ mFMConfiguration.setRdsStd(FmReceiver.FM_RDS_STD_RBDS);
+ mFMConfiguration.setLowerLimit(88100);
+ mFMConfiguration.setUpperLimit(107900);
+ mFrequencyBand_Stepsize = 200;
+ break;
+ }
+ case REGIONAL_BAND_NETHERLANDS:
+ {
+ /*NETHERLANDS : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_NEWZEALAND:
+ {
+ /*NEW ZEALAND : 88000 TO 107000 IN 100 KHZ STEPS*/
+ mFMConfiguration.setLowerLimit(88000);
+ mFMConfiguration.setUpperLimit(107000);
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_NORWAY:
+ {
+ /*NORWAY : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_POLAND:
+ {
+ /*POLAND : 88000 TO 108000 IN 50 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_EU_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_50_KHZ);
+ mFMConfiguration.setLowerLimit(88000);
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+ case REGIONAL_BAND_PORTUGAL:
+ {
+ /*PORTUGAL : 87500 TO 108000 IN 50 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_EU_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_50_KHZ);
+ mFrequencyBand_Stepsize = 50;
+ break;
+ }
+ case REGIONAL_BAND_RUSSIA:
+ {
+ /*RUSSIA : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_SINGAPORE:
+ {
+ /*SINGAPORE : 88000 TO 108000 IN 100 KHZ STEPS*/
+ mFMConfiguration.setLowerLimit(88000);
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_SLOVAKIA:
+ {
+ /*SLOVAKIA : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_SPAIN:
+ {
+ /*SPAIN : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_SWITZERLAND:
+ {
+ /*SWITZERLAND : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_SWEDEN:
+ {
+ /*SWEDEN : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_TAIWAN:
+ {
+ /*TAIWAN : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_TURKEY:
+ {
+ /*TURKEY : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_UNITEDKINGDOM:
+ {
+ /*UNITED KINGDOM : 87500 TO 108000 IN 100 KHZ STEPS*/
+ mFrequencyBand_Stepsize = 100;
+ break;
+ }
+ case REGIONAL_BAND_UNITED_STATES:
+ {
+ /*UNITED STATES : 88100 TO 107900 IN 200 KHZ STEPS*/
+ mFMConfiguration.setRadioBand(FmReceiver.FM_US_BAND);
+ mFMConfiguration.setChSpacing(FmReceiver.FM_CHSPACE_200_KHZ);
+ mFMConfiguration.setEmphasis(FmReceiver.FM_DE_EMP75);
+ mFMConfiguration.setRdsStd(FmReceiver.FM_RDS_STD_RBDS);
+ mFMConfiguration.setLowerLimit(88100);
+ mFMConfiguration.setUpperLimit(107900);
+ mFrequencyBand_Stepsize = 200;
+ break;
+ }
+ default:
+ {
+ Log.d(LOGTAG, "Invalid: countryCode: "+nCountryCode);
+ nCountryCode=0;
+ }
+ }
+ mCountry = nCountryCode;
+ Log.d(LOGTAG, "=====================================================");
+ Log.d(LOGTAG, "Country :"+nCountryCode);
+ Log.d(LOGTAG, "RadioBand :"+ mFMConfiguration.getRadioBand());
+ Log.d(LOGTAG, "Emphasis :"+ mFMConfiguration.getEmphasis());
+ Log.d(LOGTAG, "ChSpacing :"+ mFMConfiguration.getChSpacing());
+ Log.d(LOGTAG, "RdsStd :"+ mFMConfiguration.getRdsStd());
+ Log.d(LOGTAG, "LowerLimit :"+ mFMConfiguration.getLowerLimit());
+ Log.d(LOGTAG, "UpperLimit :"+ mFMConfiguration.getUpperLimit());
+ Log.d(LOGTAG, "=====================================================");
+ }
+
+
+ public static int getCountry() {
+ return mCountry;
+ }
+
+
+ public static void setAudioOutputMode(boolean bStereo) {
+ mAudioOutputMode = bStereo;
+ }
+
+ public static boolean getAudioOutputMode() {
+ return mAudioOutputMode;
+ }
+
+ public static void setRecordDuration(int durationIndex) {
+
+ Log.d(LOGTAG, "setRecordDuration "+durationIndex);
+ switch( durationIndex ) {
+ case 0: mRecordDuration = RECORD_DUR_INDEX_0_VAL; break;
+ case 1: mRecordDuration = RECORD_DUR_INDEX_1_VAL; break;
+ case 2: mRecordDuration = RECORD_DUR_INDEX_2_VAL; break;
+ case 3: mRecordDuration = RECORD_DUR_INDEX_3_VAL; break;
+ default:
+ {
+ Log.d(LOGTAG, "Invalid: durationIndex "+durationIndex);
+ }
+
+ }
+ return;
+ }
+
+ public static int getRecordDuration() {
+ return mRecordDuration;
+ }
+
+ public static void setAutoAFSwitch(boolean bAFAutoSwitch) {
+ mAFAutoSwitch = bAFAutoSwitch;
+ }
+
+ public static boolean getAutoAFSwitch() {
+ return mAFAutoSwitch;
+ }
+}
diff --git a/fmapp2/src/com/caf/fmradio/FmTags.java b/fmapp2/src/com/caf/fmradio/FmTags.java
new file mode 100644
index 0000000..f3bbdbe
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/FmTags.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2011-2013, 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 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import java.util.*;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.Spinner;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.ListView;
+
+
+public class FmTags extends Activity {
+ private ListView la;
+ private ArrayAdapter<String> adapter;
+ private final Handler mHandler = new Handler();
+ private Iterator itr;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.fmtags);
+ la = (ListView)findViewById(R.id.TagListView);
+ }
+ @Override
+ public void onStart() {
+ super.onStart();
+ }
+ @Override
+ public void onResume() {
+ super.onResume();
+ mHandler.post(mDisplayTagList);
+ }
+ final Runnable mDisplayTagList = new Runnable() {
+ public void run() {
+ String[] tags;
+ int l = 0;
+
+ tags = new String[FmSharedPreferences.num_tags];
+ for(int i = 0; i < FmSharedPreferences.MAX_NUM_TAG_TYPES; i++) {
+ if (FmSharedPreferences.tagList[i] != null) {
+ itr = FmSharedPreferences.tagList[i].iterator();
+ while(itr.hasNext())
+ tags[l++] = ((FmSharedPreferences.TAG_NAMES[i]) + "\n" +
+ "\t" + (String)itr.next());
+ }
+ }
+ adapter = new ArrayAdapter(la.getContext(), android.R.layout.simple_list_item_1, tags);
+ la.setAdapter(adapter);
+ }
+ };
+}
+
+
diff --git a/fmapp2/src/com/caf/fmradio/HorizontalNumberPicker.java b/fmapp2/src/com/caf/fmradio/HorizontalNumberPicker.java
new file mode 100644
index 0000000..8a89a85
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/HorizontalNumberPicker.java
@@ -0,0 +1,2223 @@
+/*
+ * Copyright (C) 2012-2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.caf.fmradio;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.Widget;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.method.NumberKeyListener;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.LayoutInflater.Filter;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.Scroller;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * A widget that enables the user to select a number form a predefined range.
+ * The widget presents an input filed and up and down buttons for selecting the
+ * current value. Pressing/long pressing the up and down buttons increments and
+ * decrements the current value respectively. Touching the input filed shows a
+ * scroll wheel, tapping on which while shown and not moving allows direct edit
+ * of the current value. Sliding motions up or down hide the buttons and the
+ * input filed, show the scroll wheel, and rotate the latter. Flinging is also
+ * supported. The widget enables mapping from positions to strings such that
+ * instead the position index the corresponding string is displayed.
+ * <p>
+ * For an example of using this widget, see {@link android.widget.TimePicker}.
+ * </p>
+ */
+@Widget
+public class HorizontalNumberPicker extends LinearLayout {
+
+ /**
+ * The default update interval during long press.
+ */
+ private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;
+
+ /**
+ * The index of the middle selector item.
+ */
+// private static final int SELECTOR_MIDDLE_ITEM_INDEX = 10;
+
+ /**
+ * The coefficient by which to adjust (divide) the max fling velocity.
+ */
+ private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;
+
+ /**
+ * The the duration for adjusting the selector wheel.
+ */
+ private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
+
+ /**
+ * The duration of scrolling to the next/previous value while changing the
+ * current value by one, i.e. increment or decrement.
+ */
+ private static final int CHANGE_CURRENT_BY_ONE_SCROLL_DURATION = 300;
+
+ /**
+ * The the delay for showing the input controls after a single tap on the
+ * input text.
+ */
+ private static final int SHOW_INPUT_CONTROLS_DELAY_MILLIS = ViewConfiguration
+ .getDoubleTapTimeout();
+
+ /**
+ * The strength of fading in the top and bottom while drawing the selector.
+ */
+ private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;
+
+ /**
+ * The default unscaled height of the selection divider.
+ */
+ private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;
+
+ /**
+ * In this state the selector wheel is not shown.
+ */
+ private static final int SELECTOR_WHEEL_STATE_NONE = 0;
+
+ /**
+ * In this state the selector wheel is small.
+ */
+ private static final int SELECTOR_WHEEL_STATE_SMALL = 1;
+
+ /**
+ * In this state the selector wheel is large.
+ */
+ private static final int SELECTOR_WHEEL_STATE_LARGE = 2;
+
+ /**
+ * The alpha of the selector wheel when it is bright.
+ */
+ private static final int SELECTOR_WHEEL_BRIGHT_ALPHA = 255;
+
+ /**
+ * The alpha of the selector wheel when it is dimmed.
+ */
+ private static final int SELECTOR_WHEEL_DIM_ALPHA = 60;
+
+ /**
+ * The alpha for the increment/decrement button when it is transparent.
+ */
+ private static final int BUTTON_ALPHA_TRANSPARENT = 0;
+
+ /**
+ * The alpha for the increment/decrement button when it is opaque.
+ */
+ private static final int BUTTON_ALPHA_OPAQUE = 1;
+
+ /**
+ * The property for setting the selector paint.
+ */
+ private static final String PROPERTY_SELECTOR_PAINT_ALPHA = "selectorPaintAlpha";
+
+ /**
+ * The property for setting the increment/decrement button alpha.
+ */
+ private static final String PROPERTY_BUTTON_ALPHA = "alpha";
+
+ /**
+ * The numbers accepted by the input text's {@link Filter}
+ */
+ private static final char[] DIGIT_CHARACTERS = new char[] { '0', '1', '2',
+ '3', '4', '5', '6', '7', '8', '9' };
+
+ /**
+ * Constant for unspecified size.
+ */
+ private static final int SIZE_UNSPECIFIED = -1;
+
+ /**
+ * Use a custom NumberPicker formatting callback to use two-digit minutes
+ * strings like "01". Keeping a static formatter etc. is the most efficient
+ * way to do this; it avoids creating temporary objects on every call to
+ * format().
+ *
+ * @hide
+ */
+ public static final HorizontalNumberPicker.Formatter TWO_DIGIT_FORMATTER = new HorizontalNumberPicker.Formatter() {
+ final StringBuilder mBuilder = new StringBuilder();
+
+ final java.util.Formatter mFmt = new java.util.Formatter(mBuilder,
+ java.util.Locale.US);
+
+ final Object[] mArgs = new Object[1];
+
+ public String format(int value) {
+ mArgs[0] = value;
+ mBuilder.delete(0, mBuilder.length());
+ mFmt.format("%02d", mArgs);
+ return mFmt.toString();
+ }
+ };
+
+ private static final String TAG = "HorizontalNumberPicker";
+
+ /**
+ * The increment button.
+ */
+ // private final ImageButton mIncrementButton;
+
+ /**
+ * The decrement button.
+ */
+ // private final ImageButton mDecrementButton;
+
+ /**
+ * The text for showing the current value.
+ */
+// private final EditText mInputText;
+
+ /**
+ * The min height of this widget.
+ */
+ private final int mMinHeight = 0;
+
+ /**
+ * The max height of this widget.
+ */
+ private int mMaxHeight;
+
+ /**
+ * The max width of this widget.
+ */
+ private final int mMinWidth = 0;
+
+ /**
+ * The max width of this widget.
+ */
+ private int mMaxWidth;
+
+ /**
+ * Flag whether to compute the max width.
+ */
+ private final boolean mComputeMaxWidth;
+
+ /**
+ * The height of the text.
+ */
+ private int mTextSize = 20;
+ /**
+ * The height of the gap between text elements if the selector wheel.
+ */
+ private int mSelectorTextGapHeight;
+
+ /**
+ * The width of the gap between text elements if the selector wheel.
+ */
+ private int mSelectorTextGapWidth;
+
+ /**
+ * The values to be displayed instead the indices.
+ */
+ private String[] mDisplayedValues;
+
+ /**
+ * Lower value of the range of numbers allowed for the NumberPicker
+ */
+ private int mMinValue;
+
+ /**
+ * Upper value of the range of numbers allowed for the NumberPicker
+ */
+ private int mMaxValue;
+
+ /**
+ * Current value of this NumberPicker
+ */
+ private int mValue;
+
+ /**
+ * Listener to be notified upon current value change.
+ */
+ private OnValueChangeListener mOnValueChangeListener;
+
+ /**
+ * Listener to be notified upon scroll state change.
+ */
+ private OnScrollListener mOnScrollListener;
+
+ /**
+ * Formatter for for displaying the current value.
+ */
+ private Formatter mFormatter;
+
+ /**
+ * The speed for updating the value form long press.
+ */
+ private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL;
+
+ /**
+ * Cache for the string representation of selector indices.
+ */
+ private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<String>();
+
+ /**
+ * The selector indices whose value are show by the selector.
+ */
+ private final int[] SELECTOR_INDICES_MEDIUM = new int[] {
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE
+ };
+ private final int[] SELECTOR_INDICES_SMALL = new int[] {
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE,
+ };
+ private final int[] SELECTOR_INDICES_LARGE = new int[] {
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE,
+ };
+ private int[] mSelectorIndices = SELECTOR_INDICES_MEDIUM;
+ private int mSelectorMiddleItemIndex = mSelectorIndices.length / 2;
+ /**
+ * The offset to middle of selector.
+ */
+ private static final int SELECTOR_OFFSET_ZERO = 0;
+ /**
+ * The colors alpha of selector text.
+ */
+ private static final int SELECTOR_TEXT_ALPHA_TRANSPARENT_NONE = 255;
+
+ private static float mDensity = 1.0f;
+ private static final float LDPI = 0.75f;
+ private static final float MDPI = 1.0f;
+ private static final float HDPI = 1.5f;
+ private static final float XHDPI = 2.0f;
+
+ private float mScaleWidth = 2;
+ private float mScaleLengthShort = 10;
+ private float mScaleLengthLong = 20;
+ private float mGapBetweenNumAndScale = 18;
+ private float mHdpiPositionAdjust = 18;
+
+ public enum Scale {
+ SCALE_SMALL,
+ SCALE_MEDIUM,
+ SCALE_LARGE
+ };
+ /**
+ * The {@link Paint} for drawing the selector.
+ */
+ private final Paint mSelectorWheelPaint;
+
+ /**
+ * The height of a selector element (text + gap).
+ */
+ private int mSelectorElementHeight;
+
+ /**
+ * The width of a selector element (text + gap).
+ */
+ private int mSelectorElementWidth;
+
+ /**
+ * The initial offset of the scroll selector.
+ */
+ private int mInitialScrollOffset = Integer.MIN_VALUE;
+
+ /**
+ * The initial offset for horizontal scroll selector .
+ */
+ private static final int INIT_SCROLL_OFFSET_HORIZONTAL = 0;
+
+ /**
+ * The initial offset for vertical scroll selector .
+ */
+ private static final int INIT_SCROLL_OFFSET_VERTICAL = 0;
+ /**
+ * The current offset of the scroll selector.
+ */
+ private int mCurrentScrollOffset;
+
+ /**
+ * The {@link Scroller} responsible for flinging the selector.
+ */
+ private final Scroller mFlingScroller;
+
+ /**
+ * The {@link Scroller} responsible for adjusting the selector.
+ */
+ private final Scroller mAdjustScroller;
+
+ /**
+ * The previous Y coordinate while scrolling the selector.
+ */
+ private int mPreviousScrollerY;
+
+ /**
+ * The previous Y coordinate while scrolling the selector.
+ */
+ private int mPreviousScrollerX;
+
+ /**
+ * Handle to the reusable command for setting the input text selection.
+ */
+ private SetSelectionCommand mSetSelectionCommand;
+
+ /**
+ * Handle to the reusable command for adjusting the scroller.
+ */
+ private AdjustScrollerCommand mAdjustScrollerCommand;
+
+ /**
+ * Handle to the reusable command for changing the current value from long
+ * press by one.
+ */
+ private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;
+
+ /**
+ * {@link Animator} for showing the up/down arrows.
+ */
+// private final AnimatorSet mShowInputControlsAnimator;
+
+ /**
+ * {@link Animator} for dimming the selector wheel.
+ */
+// private final Animator mDimSelectorWheelAnimator;
+
+ /**
+ * The Y position of the last down event.
+ */
+ private float mLastDownEventY;
+ /**
+ * The X position of the last down event.
+ */
+ private float mLastDownEventX;
+
+ /**
+ * The Y position of the last motion event.
+ */
+ private float mLastMotionEventY;
+
+ /**
+ * The X position of the last motion event.
+ */
+ private float mLastMotionEventX;
+
+ /**
+ * Flag if to begin edit on next up event.
+ */
+ private boolean mBeginEditOnUpEvent;
+
+ /**
+ * Flag if to adjust the selector wheel on next up event.
+ */
+ private boolean mAdjustScrollerOnUpEvent;
+
+ /**
+ * The state of the selector wheel.
+ */
+ private int mSelectorWheelState;
+
+ /**
+ * Determines speed during touch scrolling.
+ */
+ private VelocityTracker mVelocityTracker;
+
+ /**
+ * @see ViewConfiguration#getScaledTouchSlop()
+ */
+ private int mTouchSlop;
+
+ /**
+ * @see ViewConfiguration#getScaledMinimumFlingVelocity()
+ */
+ private int mMinimumFlingVelocity;
+
+ /**
+ * @see ViewConfiguration#getScaledMaximumFlingVelocity()
+ */
+ private int mMaximumFlingVelocity;
+
+ /**
+ * Flag whether the selector should wrap around.
+ */
+ private boolean mWrapSelectorWheel;
+
+ /**
+ * The back ground color used to optimize scroller fading.
+ */
+ private final int mSolidColor;
+
+ /**
+ * Flag indicating if this widget supports flinging.
+ */
+ private final boolean mFlingable;
+
+ /**
+ * Divider for showing item to be selected while scrolling
+ */
+// private final Drawable mSelectionDivider;
+
+ /**
+ * The height of the selection divider.
+ */
+// private final int mSelectionDividerHeight;
+
+ /**
+ * Reusable {@link Rect} instance.
+ */
+ private final Rect mTempRect = new Rect();
+
+ /**
+ * The current scroll state of the number picker.
+ */
+ private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ /**
+ * The duration of the animation for showing the input controls.
+ */
+ private final long mShowInputControlsAnimimationDuration;
+
+ /**
+ * Flag whether the scoll wheel and the fading edges have been initialized.
+ */
+ private boolean mScrollWheelAndFadingEdgesInitialized;
+
+ private boolean mHorizontal = true;
+
+ /**
+ * Interface to listen for changes of the current value.
+ */
+ public interface OnValueChangeListener {
+
+ /**
+ * Called upon a change of the current value.
+ *
+ * @param picker
+ * The NumberPicker associated with this listener.
+ * @param oldVal
+ * The previous value.
+ * @param newVal
+ * The new value.
+ */
+ void onValueChange(HorizontalNumberPicker picker, int oldVal, int newVal);
+ }
+
+
+ private OnScrollFinishListener mOnScrollFinishListener;
+
+ public interface OnScrollFinishListener{
+ public void onScrollFinish(int value);
+ }
+ /**
+ * Interface to listen for the picker scroll state.
+ */
+ public interface OnScrollListener {
+
+ /**
+ * The view is not scrolling.
+ */
+ public static int SCROLL_STATE_IDLE = 0;
+
+ /**
+ * The user is scrolling using touch, and their finger is still on the
+ * screen.
+ */
+ public static int SCROLL_STATE_TOUCH_SCROLL = 1;
+
+ /**
+ * The user had previously been scrolling using touch and performed a
+ * fling.
+ */
+ public static int SCROLL_STATE_FLING = 2;
+
+ /**
+ * Callback invoked while the number picker scroll state has changed.
+ *
+ * @param view
+ * The view whose scroll state is being reported.
+ * @param scrollState
+ * The current scroll state. One of
+ * {@link #SCROLL_STATE_IDLE},
+ * {@link #SCROLL_STATE_TOUCH_SCROLL} or
+ * {@link #SCROLL_STATE_IDLE}.
+ */
+ public void onScrollStateChange(HorizontalNumberPicker view,
+ int scrollState);
+ }
+
+ /**
+ * Interface used to format current value into a string for presentation.
+ */
+ public interface Formatter {
+
+ /**
+ * Formats a string representation of the current value.
+ *
+ * @param value
+ * The currently selected value.
+ * @return A formatted string representation.
+ */
+ public String format(int value);
+ }
+
+ /**
+ * Create a new number picker.
+ *
+ * @param context
+ * The application environment.
+ */
+ public HorizontalNumberPicker(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Create a new number picker.
+ *
+ * @param context
+ * The application environment.
+ * @param attrs
+ * A collection of attributes.
+ */
+ public HorizontalNumberPicker(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.numberPickerStyle);
+ }
+
+ /**
+ * Create a new number picker
+ *
+ * @param context
+ * the application environment.
+ * @param attrs
+ * a collection of attributes.
+ * @param defStyle
+ * The default style to apply to this view.
+ */
+ public HorizontalNumberPicker(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+
+ // process style attributes
+ TypedArray attributesArray = context.obtainStyledAttributes(attrs,
+ R.styleable.NumberPicker, defStyle, 0);
+ mSolidColor = attributesArray.getColor(
+ R.styleable.NumberPicker_solidColor, 0);
+ //mFlingable = attributesArray.getBoolean(
+ // R.styleable.NumberPicker_flingable, true);
+ mFlingable = true;
+
+ mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
+ attributesArray.recycle();
+
+ mShowInputControlsAnimimationDuration = getResources().getInteger(
+ R.integer.config_longAnimTime);
+
+ // By default Linearlayout that we extend is not drawn. This is
+ // its draw() method is not called but dispatchDraw() is called
+ // directly (see ViewGroup.drawChild()). However, this class uses
+ // the fading edge effect implemented by View and we need our
+ // draw() method to be called. Therefore, we declare we will draw.
+ setWillNotDraw(false);
+ setSelectorWheelState(SELECTOR_WHEEL_STATE_NONE);
+
+
+ // initialize constants
+ mTouchSlop = ViewConfiguration.getTapTimeout();
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
+ / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
+// mTextSize = (int) mInputText.getTextSize();
+ // create the selector wheel paint
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setTextAlign(Align.CENTER);
+ paint.setTextSize(mTextSize);
+ paint.setColor(Color.WHITE);
+ mSelectorWheelPaint = paint;
+
+
+ // create the fling and adjust scrollers
+ mFlingScroller = new Scroller(getContext(), null, true);
+ mAdjustScroller = new Scroller(getContext(),
+ new DecelerateInterpolator(2.5f));
+// updateInputTextView();
+
+ updateIncrementAndDecrementButtonsVisibilityState();
+
+ if (mFlingable) {
+ if (isInEditMode()) {
+ setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
+ } else {
+ // Start with shown selector wheel and hidden controls. When
+ // made
+ // visible hide the selector and fade-in the controls to suggest
+ // fling interaction.
+ setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right,
+ int bottom) {
+ final int msrdWdth = getMeasuredWidth();
+ final int msrdHght = getMeasuredHeight();
+
+ if (!mScrollWheelAndFadingEdgesInitialized) {
+ mScrollWheelAndFadingEdgesInitialized = true;
+ // need to do all this when we know our size
+ initializeSelectorWheel();
+ initializeFadingEdges();
+ }
+ setVerticalFadingEdgeEnabled(false);
+ }
+ public void setTextSize(int textSize){
+ if(textSize > 0 ){
+ mTextSize = textSize;
+ mSelectorWheelPaint.setTextSize(textSize);
+ }
+ }
+ public void setDensity(int density){
+ switch(density){
+ case DisplayMetrics.DENSITY_LOW :
+ mDensity = LDPI;
+ break;
+ case DisplayMetrics.DENSITY_MEDIUM:
+ mDensity = MDPI;
+ break;
+ case DisplayMetrics.DENSITY_HIGH:
+ mDensity = HDPI;
+ break;
+ case DisplayMetrics.DENSITY_XHIGH:
+ mDensity = XHDPI;
+ break;
+ default:
+ mDensity = MDPI;
+ break;
+ }
+ }
+
+ public void setScale(Scale scale){
+ switch(scale){
+ case SCALE_SMALL:
+ mSelectorIndices = SELECTOR_INDICES_SMALL;
+ break;
+ case SCALE_MEDIUM:
+ mSelectorIndices = SELECTOR_INDICES_MEDIUM;
+ break;
+ case SCALE_LARGE:
+ mSelectorIndices = SELECTOR_INDICES_LARGE;
+ break;
+ default :
+ break;
+ }
+
+ mSelectorMiddleItemIndex = mSelectorIndices.length / 2;
+ initializeSelectorWheel();
+// invalidate();
+ }
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec,
+ mMaxWidth);
+ final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec,
+ mMaxHeight);
+ super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
+ // Flag if we are measured with width or height less than the respective
+ // min.
+ final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth,
+ getMeasuredWidth(), widthMeasureSpec);
+ final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight,
+ getMeasuredHeight(), heightMeasureSpec);
+ setMeasuredDimension(widthSize, heightSize);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (!isEnabled() || !mFlingable) {
+ return false;
+ }
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mLastMotionEventY = mLastDownEventY = event.getY();
+ mLastMotionEventX = mLastDownEventX = event.getX();
+ removeAllCallbacks();
+ mBeginEditOnUpEvent = false;
+ mAdjustScrollerOnUpEvent = true;
+ if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
+ mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
+ boolean scrollersFinished = mFlingScroller.isFinished()
+ && mAdjustScroller.isFinished();
+ if (!scrollersFinished) {
+ mFlingScroller.forceFinished(true);
+ mAdjustScroller.forceFinished(true);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ }
+ mBeginEditOnUpEvent = scrollersFinished;
+ mAdjustScrollerOnUpEvent = true;
+ return true;
+ }
+
+ mAdjustScrollerOnUpEvent = false;
+ setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
+// hideInputControls();
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ float currentMoveY = event.getY();
+ float currentMoveX = event.getX();
+ int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
+ int deltaDownX = (int) Math.abs(currentMoveX - mLastDownEventX);
+ if(mHorizontal){
+ if (mLastDownEventX > mTouchSlop) {
+ mBeginEditOnUpEvent = false;
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ return true;
+ }
+ }else{
+ if (deltaDownY > mTouchSlop) {
+ mBeginEditOnUpEvent = false;
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ return true;
+ }
+ }
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!isEnabled()) {
+ return false;
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ int action = ev.getActionMasked();
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ if (!mHorizontal) {
+ float currentMoveY = ev.getY();
+ if (mBeginEditOnUpEvent
+ || mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ int deltaDownY = (int) Math.abs(currentMoveY
+ - mLastDownEventY);
+ if (deltaDownY > mTouchSlop) {
+ mBeginEditOnUpEvent = false;
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ }
+ }
+ int deltaMoveY = (int) (currentMoveY - mLastMotionEventY);
+ scrollBy(0, deltaMoveY);
+ invalidate();
+ mLastMotionEventY = currentMoveY;
+ } else {
+ float currentMoveX = ev.getX();
+ if (mBeginEditOnUpEvent
+ || mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ int deltaDownX = (int) Math.abs(currentMoveX
+ - mLastDownEventX);
+ if (deltaDownX > mTouchSlop) {
+ mBeginEditOnUpEvent = false;
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ }
+ }
+ int deltaMoveX = (int) (currentMoveX - mLastMotionEventX);
+ scrollBy(deltaMoveX, 0);
+ invalidate();
+ mLastMotionEventX = currentMoveX;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mBeginEditOnUpEvent) {
+ setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
+ return true;
+ }
+// VelocityTracker velocityTracker = mVelocityTracker;
+// velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
+// int initialVelocity=0;
+// if(!mHorizontal){
+// initialVelocity = (int) velocityTracker.getYVelocity();
+// }else{
+// initialVelocity = (int)velocityTracker.getXVelocity();
+// }
+// if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
+// // fling after up
+// fling(initialVelocity);
+// onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+// } else {
+ if (mAdjustScrollerOnUpEvent) {
+ if (mFlingScroller.isFinished()
+ && mAdjustScroller.isFinished()) {
+ postAdjustScrollerCommand(0);
+ }
+ } else {
+ postAdjustScrollerCommand(SHOW_INPUT_CONTROLS_DELAY_MILLIS);
+ }
+// }
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
+ removeAllCallbacks();
+ forceCompleteChangeCurrentByOneViaScroll();
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ removeAllCallbacks();
+ break;
+ }
+ return super.dispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_ENTER) {
+ removeAllCallbacks();
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_CANCEL
+ || action == MotionEvent.ACTION_UP) {
+ removeAllCallbacks();
+ }
+ return super.dispatchTrackballEvent(event);
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
+ return;
+ }
+ Scroller scroller = mFlingScroller;
+ if (scroller.isFinished()) {
+ scroller = mAdjustScroller;
+ if (scroller.isFinished()) {
+ return;
+ }
+ }
+ scroller.computeScrollOffset();
+
+ if (mHorizontal) {
+ int currentScrollerX = scroller.getCurrX();
+ if (mPreviousScrollerX == 0) {
+ mPreviousScrollerX = scroller.getStartX();
+
+ }
+
+ scrollBy(currentScrollerX - mPreviousScrollerX, 0);
+ mPreviousScrollerX = currentScrollerX;
+
+ } else {
+ int currentScrollerY = scroller.getCurrY();
+ if (mPreviousScrollerY == 0) {
+ mPreviousScrollerY = scroller.getStartY();
+ }
+ scrollBy(0, currentScrollerY - mPreviousScrollerY);
+ mPreviousScrollerY = currentScrollerY;
+ }
+
+ if (scroller.isFinished()) {
+ onScrollerFinished(scroller);
+ } else {
+ invalidate();
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ }
+
+ @Override
+ public void scrollBy(int x, int y) {
+ if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
+ return;
+ }
+ int[] selectorIndices = mSelectorIndices;
+ if (!mHorizontal) {
+ if (!mWrapSelectorWheel && y > 0
+ && selectorIndices[mSelectorMiddleItemIndex] <= mMinValue) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ return;
+ }
+ if (!mWrapSelectorWheel && y < 0
+ && selectorIndices[mSelectorMiddleItemIndex] >= mMaxValue) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ return;
+ }
+ mCurrentScrollOffset += y;
+ while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
+ mCurrentScrollOffset -= mSelectorElementHeight;
+ decrementSelectorIndices(selectorIndices);
+ changeCurrent(selectorIndices[mSelectorMiddleItemIndex]);
+ if (!mWrapSelectorWheel
+ && selectorIndices[mSelectorMiddleItemIndex] <= mMinValue) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ }
+ }
+ while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
+ mCurrentScrollOffset += mSelectorElementHeight;
+ incrementSelectorIndices(selectorIndices);
+ changeCurrent(selectorIndices[mSelectorMiddleItemIndex]);
+ if (!mWrapSelectorWheel
+ && selectorIndices[mSelectorMiddleItemIndex] >= mMaxValue) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ }
+ }
+ } else {
+ if (!mWrapSelectorWheel && x > 0
+ && selectorIndices[mSelectorMiddleItemIndex] <= mMinValue) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ return;
+ }
+ if (!mWrapSelectorWheel && x < 0
+ && selectorIndices[mSelectorMiddleItemIndex] >= mMaxValue) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ return;
+ }
+ mCurrentScrollOffset += x;
+ while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapWidth) {
+ mCurrentScrollOffset -= mSelectorElementWidth;
+ decrementSelectorIndices(selectorIndices);
+ changeCurrent(selectorIndices[mSelectorMiddleItemIndex]);
+ if (!mWrapSelectorWheel
+ && selectorIndices[mSelectorMiddleItemIndex] <= mMinValue) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ }
+ }
+ while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapWidth) {
+ mCurrentScrollOffset += mSelectorElementWidth;
+ incrementSelectorIndices(selectorIndices);
+ changeCurrent(selectorIndices[mSelectorMiddleItemIndex]);
+ if (!mWrapSelectorWheel
+ && selectorIndices[mSelectorMiddleItemIndex] >= mMaxValue) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public int getSolidColor() {
+ return mSolidColor;
+ }
+
+ /**
+ * Sets the listener to be notified on change of the current value.
+ *
+ * @param onValueChangedListener
+ * The listener.
+ */
+ public void setOnValueChangedListener(
+ OnValueChangeListener onValueChangedListener) {
+ mOnValueChangeListener = onValueChangedListener;
+ }
+
+ /**
+ * Set listener to be notified for scroll state changes.
+ *
+ * @param onScrollListener
+ * The listener.
+ */
+ public void setOnScrollListener(OnScrollListener onScrollListener) {
+ mOnScrollListener = onScrollListener;
+ }
+ public void setOnScrollFinishedListener(OnScrollFinishListener onScrollFinishListener){
+ mOnScrollFinishListener = onScrollFinishListener;
+ }
+ /**
+ * Set the formatter to be used for formatting the current value.
+ * <p>
+ * Note: If you have provided alternative values for the values this
+ * formatter is never invoked.
+ * </p>
+ *
+ * @param formatter
+ * The formatter object. If formatter is <code>null</code>,
+ * {@link String#valueOf(int)} will be used.
+ *
+ * @see #setDisplayedValues(String[])
+ */
+ public void setFormatter(Formatter formatter) {
+ if (formatter == mFormatter) {
+ return;
+ }
+ mFormatter = formatter;
+ initializeSelectorWheelIndices();
+// updateInputTextView();
+ }
+
+ /**
+ * Set the current value for the number picker.
+ * <p>
+ * If the argument is less than the {@link NumberPicker#getMinValue()} and
+ * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
+ * current value is set to the {@link NumberPicker#getMinValue()} value.
+ * </p>
+ * <p>
+ * If the argument is less than the {@link NumberPicker#getMinValue()} and
+ * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
+ * current value is set to the {@link NumberPicker#getMaxValue()} value.
+ * </p>
+ * <p>
+ * If the argument is less than the {@link NumberPicker#getMaxValue()} and
+ * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
+ * current value is set to the {@link NumberPicker#getMaxValue()} value.
+ * </p>
+ * <p>
+ * If the argument is less than the {@link NumberPicker#getMaxValue()} and
+ * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
+ * current value is set to the {@link NumberPicker#getMinValue()} value.
+ * </p>
+ *
+ * @param value
+ * The current value.
+ * @see #setWrapSelectorWheel(boolean)
+ * @see #setMinValue(int)
+ * @see #setMaxValue(int)
+ */
+ public void setValue(int value) {
+ if (mValue == value) {
+ return;
+ }
+ if (value < mMinValue) {
+ value = mWrapSelectorWheel ? mMaxValue : mMinValue;
+ }
+ if (value > mMaxValue) {
+ value = mWrapSelectorWheel ? mMinValue : mMaxValue;
+ }
+ mValue = value;
+ initializeSelectorWheelIndices();
+ updateInputTextView();
+ updateIncrementAndDecrementButtonsVisibilityState();
+ invalidate();
+ }
+
+ /**
+ * Computes the max width if no such specified as an attribute.
+ */
+ private void tryComputeMaxWidth() {
+ if (!mComputeMaxWidth) {
+ return;
+ }
+ int maxTextWidth = 0;
+ if (mDisplayedValues == null) {
+ float maxDigitWidth = 0;
+ for (int i = 0; i <= 9; i++) {
+ final float digitWidth = mSelectorWheelPaint.measureText(String
+ .valueOf(i));
+ if (digitWidth > maxDigitWidth) {
+ maxDigitWidth = digitWidth;
+ }
+ }
+ int numberOfDigits = 0;
+ int current = mMaxValue;
+ while (current > 0) {
+ numberOfDigits++;
+ current = current / 10;
+ }
+ maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
+ } else {
+ final int valueCount = mDisplayedValues.length;
+ for (int i = 0; i < valueCount; i++) {
+ final float textWidth = mSelectorWheelPaint
+ .measureText(mDisplayedValues[i]);
+ if (textWidth > maxTextWidth) {
+ maxTextWidth = (int) textWidth;
+ }
+ }
+ }
+// maxTextWidth += mInputText.getPaddingLeft()
+// + mInputText.getPaddingRight();
+ if (mMaxWidth != maxTextWidth) {
+ if (maxTextWidth > mMinWidth) {
+ mMaxWidth = maxTextWidth;
+ } else {
+ mMaxWidth = mMinWidth;
+ }
+ invalidate();
+ }
+ }
+
+ /**
+ * Gets whether the selector wheel wraps when reaching the min/max value.
+ *
+ * @return True if the selector wheel wraps.
+ *
+ * @see #getMinValue()
+ * @see #getMaxValue()
+ */
+ public boolean getWrapSelectorWheel() {
+ return mWrapSelectorWheel;
+ }
+
+ /**
+ * Sets whether the selector wheel shown during flinging/scrolling should
+ * wrap around the {@link NumberPicker#getMinValue()} and
+ * {@link NumberPicker#getMaxValue()} values.
+ * <p>
+ * By default if the range (max - min) is more than five (the number of
+ * items shown on the selector wheel) the selector wheel wrapping is
+ * enabled.
+ * </p>
+ *
+ * @param wrapSelectorWheel
+ * Whether to wrap.
+ */
+ public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
+ if (wrapSelectorWheel
+ && (mMaxValue - mMinValue) < mSelectorIndices.length) {
+ throw new IllegalStateException(
+ "Range less than selector items count.");
+ }
+ if (wrapSelectorWheel != mWrapSelectorWheel) {
+ mWrapSelectorWheel = wrapSelectorWheel;
+ updateIncrementAndDecrementButtonsVisibilityState();
+ }
+ }
+
+ /**
+ * Sets the speed at which the numbers be incremented and decremented when
+ * the up and down buttons are long pressed respectively.
+ * <p>
+ * The default value is 300 ms.
+ * </p>
+ *
+ * @param intervalMillis
+ * The speed (in milliseconds) at which the numbers will be
+ * incremented and decremented.
+ */
+ public void setOnLongPressUpdateInterval(long intervalMillis) {
+ mLongPressUpdateInterval = intervalMillis;
+ }
+
+ /**
+ * Returns the value of the picker.
+ *
+ * @return The value.
+ */
+ public int getValue() {
+ return mValue;
+ }
+
+ /**
+ * Returns the min value of the picker.
+ *
+ * @return The min value
+ */
+ public int getMinValue() {
+ return mMinValue;
+ }
+
+ /**
+ * Sets the min value of the picker.
+ *
+ * @param minValue
+ * The min value.
+ */
+ public void setMinValue(int minValue) {
+ if (mMinValue == minValue) {
+ return;
+ }
+ if (minValue < 0) {
+ throw new IllegalArgumentException("minValue must be >= 0");
+ }
+ mMinValue = minValue;
+ if (mMinValue > mValue) {
+ mValue = mMinValue;
+ }
+ boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
+ setWrapSelectorWheel(wrapSelectorWheel);
+ initializeSelectorWheelIndices();
+ updateInputTextView();
+ tryComputeMaxWidth();
+ }
+
+ /**
+ * Returns the max value of the picker.
+ *
+ * @return The max value.
+ */
+ public int getMaxValue() {
+ return mMaxValue;
+ }
+
+ /**
+ * Sets the max value of the picker.
+ *
+ * @param maxValue
+ * The max value.
+ */
+ public void setMaxValue(int maxValue) {
+ if (mMaxValue == maxValue) {
+ return;
+ }
+ if (maxValue < 0) {
+ throw new IllegalArgumentException("maxValue must be >= 0");
+ }
+ mMaxValue = maxValue;
+ if (mMaxValue < mValue) {
+ mValue = mMaxValue;
+ }
+ boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
+ setWrapSelectorWheel(wrapSelectorWheel);
+ initializeSelectorWheelIndices();
+ tryComputeMaxWidth();
+ }
+
+ /**
+ * Gets the values to be displayed instead of string values.
+ *
+ * @return The displayed values.
+ */
+ public String[] getDisplayedValues() {
+ return mDisplayedValues;
+ }
+
+ /**
+ * Sets the values to be displayed.
+ *
+ * @param displayedValues
+ * The displayed values.
+ */
+ public void setDisplayedValues(String[] displayedValues) {
+ if (mDisplayedValues == displayedValues) {
+ return;
+ }
+ mDisplayedValues = displayedValues;
+ if (mDisplayedValues != null) {
+ // Allow text entry rather than strictly numeric entry.
+// mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT
+// | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ } else {
+// mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
+ }
+// updateInputTextView();
+ initializeSelectorWheelIndices();
+ tryComputeMaxWidth();
+ }
+ /**
+ * Sets the values to be displayed.If autoCalMinMax passed true, will calculate
+ * and set min value and max value.
+ *
+ * @param displayedValues
+ * The displayed values.
+ * @param autoCalMinMax
+ * Whether auto calculate and set the min value and max value.
+ */
+ public void setDisplayedValues(String[] displayeValues , boolean autoCalculateMinMax) {
+ if(autoCalculateMinMax){
+ mMinValue = 0;
+ mMaxValue = displayeValues.length - 1;
+ }
+ setDisplayedValues(displayeValues);
+ }
+
+ @Override
+ protected float getTopFadingEdgeStrength() {
+ return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
+ }
+
+ @Override
+ protected float getBottomFadingEdgeStrength() {
+ return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ // make sure we show the controls only the very
+ // first time the user sees this widget
+ if (mFlingable && !isInEditMode()) {
+ // animate a bit slower the very first time
+ showInputControls(mShowInputControlsAnimimationDuration * 2);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeAllCallbacks();
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ // There is a good reason for doing this. See comments in draw().
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // Dispatch draw to our children only if we are not currently running
+ // the animation for simultaneously dimming the scroll wheel and
+ // showing in the buttons. This class takes advantage of the View
+ // implementation of fading edges effect to draw the selector wheel.
+ // However, in View.draw(), the fading is applied after all the children
+ // have been drawn and we do not want this fading to be applied to the
+ // buttons. Therefore, we draw our children after we have completed
+ // drawing ourselves.
+ super.draw(canvas);
+
+// Draw our children if we are not showing the selector wheel of fading
+// it out
+// if (mShowInputControlsAnimator.isRunning()
+// || mSelectorWheelState != SELECTOR_WHEEL_STATE_LARGE) {
+// long drawTime = getDrawingTime();
+// for (int i = 0, count = getChildCount(); i < count; i++) {
+// View child = getChildAt(i);
+// if (!child.isShown()) {
+// continue;
+// }
+// drawChild(canvas, getChildAt(i), drawTime);
+// }
+// }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
+ return;
+ }
+
+ float x = 0.0f;
+ float y = 0.0f;
+ if (!mHorizontal) {
+ x = (mRight - mLeft) / 2;
+ y = mCurrentScrollOffset;
+ } else {
+ x = mCurrentScrollOffset;
+ y = (mBottom - mTop) / 2 + mTextSize/2;
+ if(Math.abs(mDensity - 1.5f) < 0.001f){
+ y += mHdpiPositionAdjust;
+ }
+ }
+
+
+ final int restoreCount = canvas.save();
+
+ if (mSelectorWheelState == SELECTOR_WHEEL_STATE_SMALL) {
+ Rect clipBounds = canvas.getClipBounds();
+ clipBounds.inset(0, mSelectorElementHeight);
+ canvas.clipRect(clipBounds);
+ }
+
+ // draw the selector wheel
+ int[] selectorIndices = mSelectorIndices;
+ for (int i = 0; i < selectorIndices.length; i++) {
+ int selectorIndex = selectorIndices[i];
+ String scrollSelectorValue = mSelectorIndexToStringCache
+ .get(selectorIndex);
+ if(i - mSelectorMiddleItemIndex > 0 ){
+ mSelectorWheelPaint.setColor(Color.WHITE);
+ mSelectorWheelPaint.setAlpha((2*mSelectorMiddleItemIndex - i) * SELECTOR_TEXT_ALPHA_TRANSPARENT_NONE / mSelectorMiddleItemIndex);
+ }else if(i - mSelectorMiddleItemIndex < 0 ){
+ mSelectorWheelPaint.setColor(Color.WHITE);
+ mSelectorWheelPaint.setAlpha(i * SELECTOR_TEXT_ALPHA_TRANSPARENT_NONE / mSelectorMiddleItemIndex);
+ }else{
+ mSelectorWheelPaint.setColor(Color.RED);
+ mSelectorWheelPaint.setAlpha(SELECTOR_TEXT_ALPHA_TRANSPARENT_NONE);
+ }
+ float fNumber = Float.valueOf(scrollSelectorValue).floatValue();
+
+ boolean bShowNumber = false;
+ float fWidthOfScale = mScaleWidth ;
+ float fGapBetweenNumAndScale = mGapBetweenNumAndScale * mDensity;
+ float fScaleLength = mScaleLengthShort * mDensity;
+ //every 0.5MHz show number.
+ if((int)(fNumber * 100)%50 == 0 ){
+ if(!(selectorIndex == getMaxValue())){
+ bShowNumber = true;
+ fScaleLength = mScaleLengthLong * mDensity;
+ }
+ }
+ if(bShowNumber){
+ canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
+ }
+ canvas.drawRect(x, y+fGapBetweenNumAndScale, x+fWidthOfScale, y + fGapBetweenNumAndScale + fScaleLength , mSelectorWheelPaint);
+
+// }
+ if (mHorizontal) {
+ x += mSelectorElementWidth;
+ } else {
+ y += mSelectorElementHeight;
+ }
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+
+ @Override
+ public void sendAccessibilityEvent(int eventType) {
+ // Do not send accessibility events - we want the user to
+ // perceive this widget as several controls rather as a whole.
+ }
+
+ /**
+ * Makes a measure spec that tries greedily to use the max value.
+ *
+ * @param measureSpec
+ * The measure spec.
+ * @param maxSize
+ * The max value for the size.
+ * @return A measure spec greedily imposing the max size.
+ */
+ private int makeMeasureSpec(int measureSpec, int maxSize) {
+ if (maxSize == SIZE_UNSPECIFIED) {
+ return measureSpec;
+ }
+ final int size = MeasureSpec.getSize(measureSpec);
+ final int mode = MeasureSpec.getMode(measureSpec);
+ switch (mode) {
+ case MeasureSpec.EXACTLY:
+ return measureSpec;
+ case MeasureSpec.AT_MOST:
+ return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize),
+ MeasureSpec.EXACTLY);
+ case MeasureSpec.UNSPECIFIED:
+ return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
+ default:
+ throw new IllegalArgumentException("Unknown measure mode: " + mode);
+ }
+ }
+
+ /**
+ * Utility to reconcile a desired size and state, with constraints imposed
+ * by a MeasureSpec. Tries to respect the min size, unless a different size
+ * is imposed by the constraints.
+ *
+ * @param minSize
+ * The minimal desired size.
+ * @param measuredSize
+ * The currently measured size.
+ * @param measureSpec
+ * The current measure spec.
+ * @return The resolved size and state.
+ */
+ private int resolveSizeAndStateRespectingMinSize(int minSize,
+ int measuredSize, int measureSpec) {
+ if (minSize != SIZE_UNSPECIFIED) {
+ final int desiredWidth = Math.max(minSize, measuredSize);
+ return resolveSizeAndState(desiredWidth, measureSpec, 0);
+ } else {
+ return measuredSize;
+ }
+ }
+
+ /**
+ * Resets the selector indices and clear the cached string representation of
+ * these indices.
+ */
+ private void initializeSelectorWheelIndices() {
+ mSelectorIndexToStringCache.clear();
+ int[] selectorIdices = mSelectorIndices;
+ int current = getValue();
+ for (int i = 0; i < mSelectorIndices.length; i++) {
+ int selectorIndex = current + (i - mSelectorMiddleItemIndex);
+ if (mWrapSelectorWheel) {
+ selectorIndex = getWrappedSelectorIndex(selectorIndex);
+ }
+ mSelectorIndices[i] = selectorIndex;
+ ensureCachedScrollSelectorValue(mSelectorIndices[i]);
+ }
+ }
+
+ /**
+ * Sets the current value of this NumberPicker, and sets mPrevious to the
+ * previous value. If current is greater than mEnd less than mStart, the
+ * value of mCurrent is wrapped around. Subclasses can override this to
+ * change the wrapping behavior
+ *
+ * @param current
+ * the new value of the NumberPicker
+ */
+ private void changeCurrent(int current) {
+ if (mValue == current) {
+ return;
+ }
+ // Wrap around the values if we go past the start or end
+ if (mWrapSelectorWheel) {
+ current = getWrappedSelectorIndex(current);
+ }
+ int previous = mValue;
+ setValue(current);
+ notifyChange(previous, current);
+ }
+
+ /**
+ * Changes the current value by one which is increment or decrement based on
+ * the passes argument.
+ *
+ * @param increment
+ * True to increment, false to decrement.
+ */
+ private void changeCurrentByOne(boolean increment) {
+ if (mFlingable) {
+ mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
+ mPreviousScrollerY = 0;
+ mPreviousScrollerX = 0;
+ forceCompleteChangeCurrentByOneViaScroll();
+ if (increment) {
+ if (mHorizontal) {
+ mFlingScroller.startScroll(0, 0, -mSelectorElementHeight,
+ 0, CHANGE_CURRENT_BY_ONE_SCROLL_DURATION);
+ } else {
+ mFlingScroller.startScroll(0, 0, 0,
+ -mSelectorElementHeight,
+ CHANGE_CURRENT_BY_ONE_SCROLL_DURATION);
+ }
+
+ } else {
+ if (mHorizontal) {
+ mFlingScroller.startScroll(0, 0, mSelectorElementHeight, 0,
+ CHANGE_CURRENT_BY_ONE_SCROLL_DURATION);
+ } else {
+ mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight,
+ CHANGE_CURRENT_BY_ONE_SCROLL_DURATION);
+ }
+
+ }
+ invalidate();
+ } else {
+ if (increment) {
+ changeCurrent(mValue + 1);
+ } else {
+ changeCurrent(mValue - 1);
+ }
+ }
+ }
+
+ /**
+ * Ensures that if we are in the process of changing the current value by
+ * one via scrolling the scroller gets to its final state and the value is
+ * updated.
+ */
+ private void forceCompleteChangeCurrentByOneViaScroll() {
+ Scroller scroller = mFlingScroller;
+ if (!scroller.isFinished()) {
+ if (mHorizontal) {
+ final int xBeforeAbort = scroller.getCurrX();
+ scroller.abortAnimation();
+ final int xDelta = scroller.getCurrX() - xBeforeAbort;
+ scrollBy(xDelta, 0);
+ } else {
+ final int yBeforeAbort = scroller.getCurrY();
+ scroller.abortAnimation();
+ final int yDelta = scroller.getCurrY() - yBeforeAbort;
+ scrollBy(0, yDelta);
+ }
+ }
+ }
+
+ /**
+ * Sets the <code>alpha</code> of the {@link Paint} for drawing the selector
+ * wheel.
+ */
+ @SuppressWarnings("unused")
+ // Called via reflection
+ private void setSelectorPaintAlpha(int alpha) {
+ mSelectorWheelPaint.setAlpha(alpha);
+ invalidate();
+ }
+
+ /**
+ * @return If the <code>event</code> is in the visible <code>view</code>.
+ */
+ private boolean isEventInVisibleViewHitRect(MotionEvent event, View view) {
+ if (view.getVisibility() == VISIBLE) {
+ view.getHitRect(mTempRect);
+ return mTempRect.contains((int) event.getX(), (int) event.getY());
+ }
+ return false;
+ }
+
+ /**
+ * Sets the <code>selectorWheelState</code>.
+ */
+ private void setSelectorWheelState(int selectorWheelState) {
+ mSelectorWheelState = selectorWheelState;
+ if (selectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
+ mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
+ }
+
+ if (mFlingable && selectorWheelState == SELECTOR_WHEEL_STATE_LARGE
+ && AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityManager.getInstance(mContext).interrupt();
+ String text = mContext
+ .getString(R.string.number_picker_increment_scroll_action);
+ }
+ }
+
+ private void initializeSelectorWheel() {
+ initializeSelectorWheelIndices();
+ int[] selectorIndices = mSelectorIndices;
+ int totalTextHeight = selectorIndices.length * mTextSize;
+ int totalTextWidth = (selectorIndices.length - 1) * 2;
+ // set it horizontal
+ float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
+ float totalTextGapWidth = (mRight - mLeft) - totalTextWidth;
+
+ float textGapCount = selectorIndices.length - 1;
+ if (mHorizontal) {
+ mSelectorTextGapWidth = (int) (totalTextGapWidth / textGapCount);
+ Log.d(TAG,"mSelectorTextGapWidth :" + mSelectorTextGapWidth);
+ mSelectorElementWidth = 2 + mSelectorTextGapWidth;
+ mInitialScrollOffset = INIT_SCROLL_OFFSET_HORIZONTAL;
+ } else {
+ mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
+ mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
+ mInitialScrollOffset = INIT_SCROLL_OFFSET_VERTICAL;
+ }
+ mCurrentScrollOffset = mInitialScrollOffset;
+
+ }
+
+ private void initializeFadingEdges() {
+ setVerticalFadingEdgeEnabled(true);
+ setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
+ }
+
+ /**
+ * Callback invoked upon completion of a given <code>scroller</code>.
+ */
+ private void onScrollerFinished(Scroller scroller) {
+
+ if(mOnScrollFinishListener != null){
+ mOnScrollFinishListener.onScrollFinish(mValue);
+ }
+ if (scroller == mFlingScroller) {
+ if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
+ postAdjustScrollerCommand(0);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ } else {
+// updateInputTextView();
+// fadeSelectorWheel(mShowInputControlsAnimimationDuration);
+ }
+ } else {
+// updateInputTextView();
+// showInputControls(mShowInputControlsAnimimationDuration);
+ }
+ }
+
+ /**
+ * Handles transition to a given <code>scrollState</code>
+ */
+ private void onScrollStateChange(int scrollState) {
+ if (mScrollState == scrollState) {
+ return;
+ }
+ mScrollState = scrollState;
+ if (mOnScrollListener != null) {
+
+ mOnScrollListener.onScrollStateChange(this, scrollState);
+ }
+ }
+
+ /**
+ * Flings the selector with the given <code>velocityY</code>.
+ */
+ private void fling(int velocity) {
+ mPreviousScrollerY = 0;
+ mPreviousScrollerX = 0;
+ int velocityY = velocity;
+ int velocityX = velocity;
+ if (mHorizontal) {
+ if (velocityX > 0) {
+ mFlingScroller.fling(0, 0, velocityX, 0, 0, Integer.MAX_VALUE,
+ 0, 0);
+ } else {
+ mFlingScroller.fling(Integer.MAX_VALUE, 0, velocityX, 0, 0,
+ Integer.MAX_VALUE, 0, 0);
+ }
+ } else {
+ if (velocityY > 0) {
+ mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0,
+ Integer.MAX_VALUE);
+ } else {
+ mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0,
+ 0, Integer.MAX_VALUE);
+ }
+ }
+
+ invalidate();
+ }
+
+ /**
+ * Hides the input controls which is the up/down arrows and the text field.
+ */
+ private void hideInputControls() {
+ // mShowInputControlsAnimator.cancel();
+ // mIncrementButton.setVisibility(INVISIBLE);
+ // mDecrementButton.setVisibility(INVISIBLE);
+// mInputText.setVisibility(INVISIBLE);
+ }
+
+ /**
+ * Show the input controls by making them visible and animating the alpha
+ * property up/down arrows.
+ *
+ * @param animationDuration
+ * The duration of the animation.
+ */
+ private void showInputControls(long animationDuration) {
+// updateIncrementAndDecrementButtonsVisibilityState();
+// mInputText.setVisibility(VISIBLE);
+// mShowInputControlsAnimator.setDuration(animationDuration);
+// mShowInputControlsAnimator.start();
+ }
+
+ /**
+ * Fade the selector wheel via an animation.
+ *
+ * @param animationDuration
+ * The duration of the animation.
+ */
+ // mark this 1
+ private void fadeSelectorWheel(long animationDuration) {
+// mInputText.setVisibility(VISIBLE);
+// mDimSelectorWheelAnimator.setDuration(animationDuration);
+// mDimSelectorWheelAnimator.start();
+ }
+
+ /**
+ * Updates the visibility state of the increment and decrement buttons.
+ */
+ private void updateIncrementAndDecrementButtonsVisibilityState() {
+ if (mWrapSelectorWheel || mValue < mMaxValue) {
+ // mIncrementButton.setVisibility(VISIBLE);
+ } else {
+ // mIncrementButton.setVisibility(INVISIBLE);
+ }
+ if (mWrapSelectorWheel || mValue > mMinValue) {
+ // mDecrementButton.setVisibility(VISIBLE);
+ } else {
+ // mDecrementButton.setVisibility(INVISIBLE);
+ }
+ }
+
+ /**
+ * @return The wrapped index <code>selectorIndex</code> value.
+ */
+ private int getWrappedSelectorIndex(int selectorIndex) {
+ if (selectorIndex > mMaxValue) {
+ return mMinValue + (selectorIndex - mMaxValue)
+ % (mMaxValue - mMinValue) - 1;
+ } else if (selectorIndex < mMinValue) {
+ return mMaxValue - (mMinValue - selectorIndex)
+ % (mMaxValue - mMinValue) + 1;
+ }
+ return selectorIndex;
+ }
+
+ /**
+ * Increments the <code>selectorIndices</code> whose string representations
+ * will be displayed in the selector.
+ */
+ private void incrementSelectorIndices(int[] selectorIndices) {
+ for (int i = 0; i < selectorIndices.length - 1; i++) {
+ selectorIndices[i] = selectorIndices[i + 1];
+ }
+ int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
+ if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
+ nextScrollSelectorIndex = mMinValue;
+ }
+ selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
+ ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
+ }
+
+ /**
+ * Decrements the <code>selectorIndices</code> whose string representations
+ * will be displayed in the selector.
+ */
+ private void decrementSelectorIndices(int[] selectorIndices) {
+ for (int i = selectorIndices.length - 1; i > 0; i--) {
+ selectorIndices[i] = selectorIndices[i - 1];
+ }
+ int nextScrollSelectorIndex = selectorIndices[1] - 1;
+ if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
+ nextScrollSelectorIndex = mMaxValue;
+ }
+ selectorIndices[0] = nextScrollSelectorIndex;
+ ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
+ }
+
+ /**
+ * Ensures we have a cached string representation of the given <code>
+ * selectorIndex</code>
+ * to avoid multiple instantiations of the same string.
+ */
+ private void ensureCachedScrollSelectorValue(int selectorIndex) {
+ SparseArray<String> cache = mSelectorIndexToStringCache;
+ String scrollSelectorValue = cache.get(selectorIndex);
+ if (scrollSelectorValue != null) {
+ return;
+ }
+ if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
+ scrollSelectorValue = "";
+ } else {
+ if (mDisplayedValues != null) {
+ int displayedValueIndex = selectorIndex - mMinValue;
+ scrollSelectorValue = mDisplayedValues[displayedValueIndex];
+ } else {
+ scrollSelectorValue = formatNumber(selectorIndex);
+ }
+ }
+ cache.put(selectorIndex, scrollSelectorValue);
+ }
+
+ private String formatNumber(int value) {
+ return (mFormatter != null) ? mFormatter.format(value) : String
+ .valueOf(value);
+ }
+
+ private void validateInputTextView(View v) {
+ String str = String.valueOf(((TextView) v).getText());
+ if (TextUtils.isEmpty(str)) {
+ // Restore to the old value as we don't allow empty values
+ updateInputTextView();
+ } else {
+ // Check the new value and ensure it's in range
+ int current = getSelectedPos(str.toString());
+ changeCurrent(current);
+ }
+ }
+
+ /**
+ * Updates the view of this NumberPicker. If displayValues were specified in
+ * the string corresponding to the index specified by the current value will
+ * be returned. Otherwise, the formatter specified in {@link #setFormatter}
+ * will be used to format the number.
+ */
+ private void updateInputTextView() {
+ /*
+ * If we don't have displayed values then use the current number else
+ * find the correct value in the displayed values for the current
+ * number.
+ */
+ // mark this 2
+ if (mDisplayedValues == null) {
+// mInputText.setText(TWO_DIGIT_FORMATTER.format(mValue));
+ } else {
+// mInputText.setText(mDisplayedValues[mValue - mMinValue]);
+ }
+// mInputText.setSelection(mInputText.getText().length());
+
+ if (mFlingable
+ && AccessibilityManager.getInstance(mContext).isEnabled()) {
+// String text = mContext.getString(
+// R.string.number_picker_increment_scroll_mode, mInputText
+// .getText());
+// mInputText.setContentDescription(text);
+ }
+ }
+
+ /**
+ * Notifies the listener, if registered, of a change of the value of this
+ * NumberPicker.
+ */
+ private void notifyChange(int previous, int current) {
+ if (mOnValueChangeListener != null) {
+ mOnValueChangeListener.onValueChange(this, previous, mValue);
+ }
+ }
+
+ /**
+ * Posts a command for changing the current value by one.
+ *
+ * @param increment
+ * Whether to increment or decrement the value.
+ */
+ private void postChangeCurrentByOneFromLongPress(boolean increment) {
+// mInputText.clearFocus();
+ removeAllCallbacks();
+ if (mChangeCurrentByOneFromLongPressCommand == null) {
+ mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
+ }
+ mChangeCurrentByOneFromLongPressCommand.setIncrement(increment);
+ post(mChangeCurrentByOneFromLongPressCommand);
+ }
+
+ /**
+ * Removes all pending callback from the message queue.
+ */
+ private void removeAllCallbacks() {
+ if (mChangeCurrentByOneFromLongPressCommand != null) {
+ removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
+ }
+ if (mAdjustScrollerCommand != null) {
+ removeCallbacks(mAdjustScrollerCommand);
+ }
+ if (mSetSelectionCommand != null) {
+ removeCallbacks(mSetSelectionCommand);
+ }
+ }
+
+ /**
+ * @return The selected index given its displayed <code>value</code>.
+ */
+ private int getSelectedPos(String value) {
+ if (mDisplayedValues == null) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ // Ignore as if it's not a number we don't care
+ }
+ } else {
+ for (int i = 0; i < mDisplayedValues.length; i++) {
+ // Don't force the user to type in jan when ja will do
+ value = value.toLowerCase();
+ if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
+ return mMinValue + i;
+ }
+ }
+
+ /*
+ * The user might have typed in a number into the month field i.e.
+ * 10 instead of OCT so support that too.
+ */
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+
+ // Ignore as if it's not a number we don't care
+ }
+ }
+ return mMinValue;
+ }
+
+ /**
+ * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
+ * </code> to
+ * <code>selectionEnd</code>.
+ */
+ private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
+ if (mSetSelectionCommand == null) {
+ mSetSelectionCommand = new SetSelectionCommand();
+ } else {
+ removeCallbacks(mSetSelectionCommand);
+ }
+ mSetSelectionCommand.mSelectionStart = selectionStart;
+ mSetSelectionCommand.mSelectionEnd = selectionEnd;
+ post(mSetSelectionCommand);
+ }
+
+ /**
+ * Posts an {@link AdjustScrollerCommand} within the given <code>
+ * delayMillis</code>
+ * .
+ */
+ private void postAdjustScrollerCommand(int delayMillis) {
+ if (mAdjustScrollerCommand == null) {
+ mAdjustScrollerCommand = new AdjustScrollerCommand();
+ } else {
+ removeCallbacks(mAdjustScrollerCommand);
+ }
+ postDelayed(mAdjustScrollerCommand, delayMillis);
+ }
+
+ /**
+ * Filter for accepting only valid indices or prefixes of the string
+ * representation of valid indices.
+ */
+ class InputTextFilter extends NumberKeyListener {
+
+ // XXX This doesn't allow for range limits when controlled by a
+ // soft input method!
+ public int getInputType() {
+ return InputType.TYPE_CLASS_TEXT;
+ }
+
+ @Override
+ protected char[] getAcceptedChars() {
+ return DIGIT_CHARACTERS;
+ }
+
+ @Override
+ public CharSequence filter(CharSequence source, int start, int end,
+ Spanned dest, int dstart, int dend) {
+ if (mDisplayedValues == null) {
+ CharSequence filtered = super.filter(source, start, end, dest,
+ dstart, dend);
+ if (filtered == null) {
+ filtered = source.subSequence(start, end);
+ }
+
+ String result = String.valueOf(dest.subSequence(0, dstart))
+ + filtered + dest.subSequence(dend, dest.length());
+
+ if ("".equals(result)) {
+ return result;
+ }
+ int val = getSelectedPos(result);
+
+ /*
+ * Ensure the user can't type in a value greater than the max
+ * allowed. We have to allow less than min as the user might
+ * want to delete some numbers and then type a new number.
+ */
+ if (val > mMaxValue) {
+ return "";
+ } else {
+ return filtered;
+ }
+ } else {
+ CharSequence filtered = String.valueOf(source.subSequence(
+ start, end));
+ if (TextUtils.isEmpty(filtered)) {
+ return "";
+ }
+ String result = String.valueOf(dest.subSequence(0, dstart))
+ + filtered + dest.subSequence(dend, dest.length());
+ String str = String.valueOf(result).toLowerCase();
+ for (String val : mDisplayedValues) {
+ String valLowerCase = val.toLowerCase();
+ if (valLowerCase.startsWith(str)) {
+ postSetSelectionCommand(result.length(), val.length());
+ return val.subSequence(dstart, val.length());
+ }
+ }
+ return "";
+ }
+ }
+ }
+
+ /**
+ * Command for setting the input text selection.
+ */
+ class SetSelectionCommand implements Runnable {
+ private int mSelectionStart;
+
+ private int mSelectionEnd;
+
+ public void run() {
+// mInputText.setSelection(mSelectionStart, mSelectionEnd);
+ }
+ }
+
+ /**
+ * Command for adjusting the scroller to show in its center the closest of
+ * the displayed items.
+ */
+ class AdjustScrollerCommand implements Runnable {
+ public void run() {
+ mPreviousScrollerY = 0;
+ mPreviousScrollerX = 0;
+ if (mInitialScrollOffset == mCurrentScrollOffset) {
+ return;
+ }
+ if (mHorizontal) {
+ // adjust to the closest value
+ int deltaX = mInitialScrollOffset - mCurrentScrollOffset;
+ mAdjustScroller.startScroll(0, 0, deltaX, 0,
+ SELECTOR_ADJUSTMENT_DURATION_MILLIS);
+ } else {
+ // adjust to the closest value
+ int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
+ if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
+ deltaY += (deltaY > 0) ? -mSelectorElementHeight
+ : mSelectorElementHeight;
+ }
+ mAdjustScroller.startScroll(0, 0, 0, deltaY,
+ SELECTOR_ADJUSTMENT_DURATION_MILLIS);
+ }
+
+ invalidate();
+ }
+ }
+
+ /**
+ * Command for changing the current value from a long press by one.
+ */
+ class ChangeCurrentByOneFromLongPressCommand implements Runnable {
+ private boolean mIncrement;
+
+ private void setIncrement(boolean increment) {
+ mIncrement = increment;
+ }
+
+ public void run() {
+ changeCurrentByOne(mIncrement);
+ postDelayed(this, mLongPressUpdateInterval);
+ }
+ }
+}
diff --git a/fmapp2/src/com/caf/fmradio/IFMRadioService.aidl b/fmapp2/src/com/caf/fmradio/IFMRadioService.aidl
new file mode 100644
index 0000000..7358149
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/IFMRadioService.aidl
@@ -0,0 +1,57 @@
+package com.caf.fmradio;
+
+import com.caf.fmradio.IFMRadioServiceCallbacks;
+
+interface IFMRadioService
+{
+ boolean fmOn();
+ boolean fmOff();
+ boolean fmRadioReset();
+ boolean isFmOn();
+ boolean isAnalogModeEnabled();
+ boolean isFmRecordingOn();
+ boolean isSpeakerEnabled();
+ boolean fmReconfigure();
+ void registerCallbacks(IFMRadioServiceCallbacks cb);
+ void unregisterCallbacks();
+ boolean mute();
+ boolean routeAudio(int device);
+ boolean unMute();
+ boolean isMuted();
+ boolean startRecording();
+ void stopRecording();
+ boolean tune(int frequency);
+ boolean seek(boolean up);
+ void enableSpeaker(boolean speakerOn);
+ boolean scan(int pty);
+ boolean seekPI(int piCode);
+ boolean searchStrongStationList(int numStations);
+ int[] getSearchList();
+ boolean cancelSearch();
+ String getProgramService();
+ String getRadioText();
+ int getProgramType();
+ int getProgramID();
+ boolean setLowPowerMode(boolean bLowPower);
+ int getPowerMode();
+ boolean enableAutoAF(boolean bEnable);
+ boolean enableStereo(boolean bEnable);
+ boolean isAntennaAvailable();
+ boolean isWiredHeadsetAvailable();
+ boolean isCallActive();
+ int getRssi();
+ int getIoC();
+ int getMpxDcc();
+ int getIntDet();
+ int getSINR();
+ void setHiLoInj(int inj);
+ void delayedStop(long nDuration, int nType);
+ void cancelDelayedStop (int nType);
+ void requestFocus();
+ boolean setSinrSamplesCnt(int samplesCnt);
+ boolean setSinrTh(int sinr);
+ boolean setIntfDetLowTh(int intfLowTh);
+ boolean setIntfDetHighTh(int intfHighTh);
+ String getExtenRadioText();
+}
+
diff --git a/fmapp2/src/com/caf/fmradio/IFMRadioServiceCallbacks.aidl b/fmapp2/src/com/caf/fmradio/IFMRadioServiceCallbacks.aidl
new file mode 100644
index 0000000..1926f24
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/IFMRadioServiceCallbacks.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+interface IFMRadioServiceCallbacks
+{
+ void onEnabled();
+ void onDisabled();
+ void onRadioReset();
+ void onTuneStatusChanged();
+ void onProgramServiceChanged();
+ void onRadioTextChanged();
+ void onAlternateFrequencyChanged();
+ void onSignalStrengthChanged();
+ void onSearchComplete();
+ void onSearchListComplete();
+ void onMute(boolean bMuted);
+ void onAudioUpdate(boolean bStereo);
+ void onStationRDSSupported(boolean bRDSSupported);
+ void onRecordingStopped();
+ void onExtenRadioTextChanged();
+}
diff --git a/fmapp2/src/com/caf/fmradio/IFMTransmitterService.aidl b/fmapp2/src/com/caf/fmradio/IFMTransmitterService.aidl
new file mode 100644
index 0000000..79e01de
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/IFMTransmitterService.aidl
@@ -0,0 +1,23 @@
+package com.caf.fmradio;
+
+import com.caf.fmradio.IFMTransmitterServiceCallbacks;
+interface IFMTransmitterService
+{
+ boolean fmOn();
+ boolean fmOff();
+ boolean fmRestart();
+ boolean isFmOn();
+ boolean fmReconfigure();
+ void registerCallbacks(IFMTransmitterServiceCallbacks cb);
+ void unregisterCallbacks();
+ boolean tune(int frequency);
+ boolean searchWeakStationList(int numStations);
+ int[] getSearchList();
+ boolean cancelSearch();
+ String getRadioText();
+ boolean isInternalAntennaAvailable();
+ boolean isHeadsetPlugged();
+ boolean isCallActive();
+ String getPSData();
+}
+
diff --git a/fmapp2/src/com/caf/fmradio/IFMTransmitterServiceCallbacks.aidl b/fmapp2/src/com/caf/fmradio/IFMTransmitterServiceCallbacks.aidl
new file mode 100644
index 0000000..641785d
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/IFMTransmitterServiceCallbacks.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2009,2012-2013 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+interface IFMTransmitterServiceCallbacks
+{
+ void onEnabled(boolean bStatus);
+ void onDisabled();
+ void onRadioReset();
+ void onTuneStatusChanged(int frequency);
+ void onRadioTextChanged();
+ void onSearchListComplete(boolean bStatus);
+ void onReconfigured();
+ void onMetaDataChanged(String str);
+ void onPSInfoSent(String str);
+}
diff --git a/fmapp2/src/com/caf/fmradio/PresetList.java b/fmapp2/src/com/caf/fmradio/PresetList.java
new file mode 100644
index 0000000..b1a0214
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/PresetList.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2009,2013 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PresetList{
+ private List<PresetStation> mPresetList = new ArrayList<PresetStation>();
+ private int mCurrentStation = 0;
+ private String mListName = "";
+
+ public PresetList(String name) {
+ mListName = name;
+ }
+
+ public String getName(){
+ return mListName;
+ }
+
+ public String toString(){
+ return mListName;
+ }
+
+ public synchronized int getStationCount(){
+ return mPresetList.size();
+ }
+
+ public synchronized String getStationName(int stationNum){
+ String name = "";
+ if (mPresetList.size() > stationNum){
+ name = mPresetList.get(stationNum).getName();
+ }
+ return name;
+ }
+
+ public synchronized int getStationFrequency(int stationNum){
+ int frequency = 102100;
+ if (mPresetList.size() > stationNum){
+ frequency = mPresetList.get(stationNum).getFrequency();
+ }
+ return frequency;
+ }
+
+ public void setName(String name){
+ mListName = name;
+ }
+
+ public synchronized void setStationFrequency(int stationNum, int frequency){
+ PresetStation mStation = mPresetList.get(stationNum);
+ mStation.setFrequency(frequency);
+ }
+
+ public synchronized void setStationName(int stationNum, String name){
+ PresetStation mStation = mPresetList.get(stationNum);
+ mStation.setName(name);
+ }
+
+ public synchronized PresetStation getStationFromIndex(int index){
+ int totalPresets = mPresetList.size();
+ PresetStation station = null;
+ if (index < totalPresets) {
+ station = mPresetList.get(index);
+ }
+ return station;
+ }
+
+ public synchronized PresetStation getStationFromFrequency(int frequency){
+ int totalPresets = mPresetList.size();
+ for (int presetNum = 0; presetNum < totalPresets; presetNum++ ) {
+ PresetStation station = mPresetList.get(presetNum);
+ if (station != null) {
+ if(frequency == station.getFrequency()) {
+ return station;
+ }
+ }
+ }
+ return null;
+ }
+
+ public synchronized PresetStation addStation(String name, int freq){
+ PresetStation addStation = new PresetStation(name, freq);
+ if(addStation != null) {
+ mPresetList.add(addStation);
+ }
+ return addStation;
+ }
+
+ public synchronized PresetStation addStation(PresetStation station){
+ PresetStation addStation = null;
+ if(station != null) {
+ addStation = new PresetStation (station);
+ mPresetList.add(addStation);
+ }
+ return addStation;
+ }
+
+ public synchronized void removeStation(int index){
+ int totalPresets = mPresetList.size();
+ if((index >= 0) && (index < totalPresets))
+ {
+ mPresetList.remove(index);
+ }
+ }
+
+ public synchronized void removeStation(PresetStation station){
+ int index = mPresetList.indexOf(station);
+ int totalPresets = mPresetList.size();
+ if((index >= 0) && (index < totalPresets))
+ {
+ mPresetList.remove(index);
+ }
+ }
+ public synchronized void clear(){
+ mPresetList.clear();
+ }
+
+ /* If a user selects a new station in this list, this routine will be called to
+ * to update the list.
+ */
+ public synchronized boolean setSelectedStation(PresetStation selectStation){
+ int totalPresets = mPresetList.size();
+ if (selectStation != null) {
+ for (int presetNum = 0; presetNum < totalPresets; presetNum++ ) {
+ PresetStation station = mPresetList.get(presetNum);
+ if (station != null) {
+ if(selectStation.getFrequency() == station.getFrequency()) {
+ if(selectStation.getName().equalsIgnoreCase(station.getName())) {
+ mCurrentStation = presetNum;
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /* Check if the same station already exists in a List
+ * to update the list.
+ */
+ public synchronized boolean sameStationExists(PresetStation compareStation){
+ int totalPresets = mPresetList.size();
+ if (compareStation != null) {
+ for (int presetNum = 0; presetNum < totalPresets; presetNum++ ) {
+ PresetStation station = mPresetList.get(presetNum);
+ if (station != null) {
+ if(compareStation.getFrequency() == station.getFrequency()) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /* If a user selects a new station in this list, this routine will be called to
+ * to update the list.
+ */
+ public synchronized boolean setSelectedStation(int stationIndex){
+ boolean foundStation = false;
+ int totalPresets = mPresetList.size();
+ if (stationIndex < totalPresets) {
+ mCurrentStation = stationIndex;
+ foundStation = true;
+ }
+ return foundStation;
+ }
+
+ public synchronized PresetStation getSelectedStation(){
+ int totalPresets = mPresetList.size();
+ PresetStation station = null;
+ if (mCurrentStation < totalPresets) {
+ station = mPresetList.get(mCurrentStation);
+ }
+ return station;
+ }
+
+ public synchronized PresetStation selectNextStation(){
+ int totalPresets = mPresetList.size();
+ PresetStation station = null;
+ if(totalPresets > 0) {
+ mCurrentStation ++;
+ if ( (mCurrentStation) >= totalPresets) {
+ mCurrentStation =0;
+ }
+ station = mPresetList.get(mCurrentStation);
+ }
+ return station;
+ }
+
+ public synchronized PresetStation selectPrevStation(){
+ int totalPresets = mPresetList.size();
+ PresetStation station = null;
+ if(totalPresets > 0) {
+ mCurrentStation --;
+ if ( mCurrentStation < 0) {
+ mCurrentStation = totalPresets-1;
+ }
+ station = mPresetList.get(mCurrentStation);
+ }
+ return station;
+ }
+
+ public synchronized void selectStation(PresetStation selectStation){
+ int totalPresets = mPresetList.size();
+ if (selectStation != null) {
+ for (int presetNum = 0; presetNum < totalPresets; presetNum++ ) {
+ PresetStation station = mPresetList.get(presetNum);
+ if (station != null) {
+ if(selectStation.getFrequency() == station.getFrequency()) {
+ mCurrentStation = presetNum;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /* Test Code */
+ public void addDummyStations() {
+ PresetStation station;
+ int freq;
+ String name ;
+ int pty ;
+
+ mPresetList.clear();
+ freq = 89500;
+ name = "KPBS";
+ pty = 22; //public
+ station = addStation(name, freq);
+ if(station != null) {
+ station.setPty(pty);
+ station.setPI(0);
+ station.setRDSSupported(false);
+ }
+
+ freq = 96500;
+ name = "KYXY";
+ //pty = "Soft Rock";
+ pty = 8;
+
+ station = addStation(name, freq);
+ if(station != null) {
+ station.setPty(pty);
+ station.setPI(0);
+ station.setRDSSupported(false);
+ }
+
+ freq = 98100;
+ name = "KIFM";
+ //pty = "Smooth Jazz";
+ pty = 14;
+
+ station = addStation(name, freq);
+ if(station != null) {
+ station.setPty(pty);
+ station.setPI(0);
+ station.setRDSSupported(false);
+ }
+
+ freq = 101500;
+ name = "KGB";
+ //pty = "Classic Rock";
+ pty = 6;
+
+ station = addStation(name, freq);
+ if(station != null) {
+ station.setPty(pty);
+ station.setPI(0);
+ station.setRDSSupported(false);
+ }
+
+ freq = 102100;
+ name = "KPRI";
+ //pty = "Rock";
+ pty = 5;
+ station = addStation(name, freq);
+ if(station != null) {
+ station.setPty(pty);
+ station.setPI(0);
+ station.setRDSSupported(true);
+ }
+
+ freq = 105300;
+ name = "KIOZ";
+ //pty = "Rock";
+ pty = 5;
+ station = addStation(name, freq);
+ if(station != null) {
+ station.setPty(pty);
+ station.setPI(0);
+ station.setRDSSupported(true);
+ }
+ }
+
+}
diff --git a/fmapp2/src/com/caf/fmradio/PresetStation.java b/fmapp2/src/com/caf/fmradio/PresetStation.java
new file mode 100644
index 0000000..77bf648
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/PresetStation.java
@@ -0,0 +1,951 @@
+/*
+ * Copyright (c) 2009,2013 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import qcom.fmradio.FmReceiver;
+
+import android.text.TextUtils;
+//import android.util.Log;
+
+public class PresetStation
+{
+ private String mName = "";
+ private int mFrequency = 102100;
+ private int mPty = 0;
+ private int mPI = 0;
+ private String mPtyStr = "";
+ private String mPIStr = "";
+ private boolean mRDSSupported = false;
+
+ public PresetStation(String name, int frequency) {
+ mName = name;
+ /*
+ * setFrequency set the name to
+ * "Frequency" String if Name is empty
+ */
+ setFrequency(frequency);
+ }
+
+ public PresetStation(PresetStation station) {
+ Copy(station);
+ /*
+ * setFrequency set the name to
+ * "Frequency" String if Name is empty
+ */
+ setFrequency(station.getFrequency());
+ }
+
+ public void Copy(PresetStation station) {
+ /* Let copy just do a copy
+ * without any manipulation
+ */
+ mName = station.getName();
+ mFrequency = station.getFrequency();
+ mPI = station.getPI();
+ mPty = station.getPty();
+ mRDSSupported = station.getRDSSupported();
+
+ mPtyStr = station.getPtyString();
+ mPIStr = station.getPIString();
+ }
+
+ public boolean equals(PresetStation station) {
+ boolean equal = false;
+ if (mFrequency == station.getFrequency())
+ {
+ if (mPty == (station.getPty()))
+ {
+ if (mPI == (station.getPI()))
+ {
+ if (mRDSSupported == (station.getRDSSupported()))
+ {
+ equal = true;
+ }
+ }
+ }
+ }
+ return equal;
+ }
+
+ public void setName(String name){
+ if (!TextUtils.isEmpty(name) &&!TextUtils.isEmpty(name.trim()))
+ {
+ mName = name;
+ } else
+ {
+ mName = ""+mFrequency/1000.0;
+ }
+ }
+
+ public void setFrequency(int freq){
+ mFrequency = freq;
+ /* If no name set it to the frequency */
+ if (TextUtils.isEmpty(mName))
+ {
+ mName = ""+mFrequency/1000.0;
+ }
+ return;
+ }
+
+ public void setPty(int pty){
+ mPty = pty;
+ mPtyStr = PresetStation.parsePTY(mPty);
+ }
+
+ public void setPI(int pi){
+ mPI = pi;
+ mPIStr = PresetStation.parsePI(mPI);
+ }
+
+ public void setRDSSupported(boolean rds){
+ mRDSSupported = rds;
+ }
+
+ public String getName(){
+ return mName;
+ }
+
+ public int getFrequency(){
+ return mFrequency;
+ }
+
+ /**
+ * Routine to get the Frequency in String from an integer
+ *
+ * @param frequency : Frequency to be converted (ex: 96500)
+ *
+ * @return String : Frequency in String form (ex: 96.5)
+ */
+ public static String getFrequencyString(int frequency) {
+ double frequencyDbl = frequency / 1000.0;
+ String frequencyString =""+frequencyDbl;
+ return frequencyString;
+ }
+
+ public int getPty(){
+ return mPty;
+ }
+
+ public String getPtyString(){
+ return mPtyStr;
+ }
+
+ public int getPI(){
+ return mPI;
+ }
+
+ public String getPIString(){
+ return mPIStr;
+ }
+
+ public boolean getRDSSupported(){
+ return mRDSSupported;
+ }
+
+ /** Routine parses the PI Code from Integer to Call Sign String
+ * Example: 0x54A6 -> KZZY
+ */
+ public static String parsePI(int piCode)
+ {
+ String callSign = "";
+ if ( (piCode >> 8) == 0xAF)
+ {//CALL LETTERS THAT MAP TO PI CODES = _ _ 0 0.
+ piCode = ((piCode & 0xFF) << 8);
+ }
+ /* Run the second exception
+ NOTE: For 9 special cases 1000,2000,..,9000 a double mapping
+ occurs utilizing exceptions 1 and 2:
+ 1000->A100->AFA1;2000->A200->AFA2; ... ;
+ 8000->A800->AFA8;9000->A900->AFA9
+ */
+ if ( (piCode >> 12) == 0xA)
+ {//CALL LETTERS THAT MAP TO PI CODES = _ 0 _ _.
+ piCode = ((piCode & 0xF00) << 4) + (piCode & 0xFF);
+ }
+ if ( (piCode >= 0x1000) && (piCode <= 0x994E))
+ {
+ String ShartChar;
+ /* KAAA - KZZZ */
+ if ( (piCode >= 0x1000) && (piCode <= 0x54A7))
+ {
+ piCode -= 0x1000;
+ ShartChar = "K";
+ } else
+ { /* WAAA - WZZZ*/
+ piCode -= 0x54A8;
+ ShartChar = "W";
+ }
+ int CharDiv = piCode / 26;
+ int CharPos = piCode - (CharDiv * 26);
+ char c3 = (char)('A'+CharPos);
+
+ piCode = CharDiv;
+ CharDiv = piCode / 26;
+ CharPos = piCode - (CharDiv * 26);
+ char c2 = (char)('A'+CharPos);
+
+ piCode = CharDiv;
+ CharDiv = piCode / 26;
+ CharPos = piCode - (CharDiv * 26);
+ char c1 = (char)('A'+CharPos);
+ callSign = ShartChar + c1+ c2+ c3;
+ } else if ( (piCode >= 0x9950) && (piCode <= 0x9EFF))
+ {//3-LETTER-ONLY CALL LETTERS
+ callSign = get3LetterCallSign(piCode);
+ } else
+ {//NATIONALLY-LINKED RADIO STATIONS CARRYING DIFFERENT CALL LETTERS
+ callSign = getOtherCallSign(piCode);
+ }
+ return callSign;
+ }
+
+ private static String getOtherCallSign(int piCode)
+ {
+ String callSign = "";
+ if ( (piCode >= 0xB001) && (piCode <= 0xBF01))
+ {
+ callSign = "NPR";
+ } else if ( (piCode >= 0xB002) && (piCode <= 0xBF02))
+ {
+ callSign = "CBC English";
+ } else if ( (piCode >= 0xB003) && (piCode <= 0xBF03))
+ {
+ callSign = "CBC French";
+ }
+ return callSign;
+ }
+
+ private static String get3LetterCallSign(int piCode)
+ {
+ String callSign = "";
+ switch (piCode)
+ {
+ case 0x99A5:
+ {
+ callSign = "KBW";
+ break;
+ }
+ case 0x9992:
+ {
+ callSign = "KOY";
+ break;
+ }
+ case 0x9978:
+ {
+ callSign = "WHO";
+ break;
+ }
+ case 0x99A6:
+ {
+ callSign = "KCY";
+ break;
+ }
+ case 0x9993:
+ {
+ callSign = "KPQ";
+ break;
+ }
+ case 0x999C:
+ {
+ callSign = "WHP";
+ break;
+ }
+ case 0x9990:
+ {
+ callSign = "KDB";
+ break;
+ }
+ case 0x9964:
+ {
+ callSign = "KQV";
+ break;
+ }
+ case 0x999D:
+ {
+ callSign = "WIL";
+ break;
+ }
+ case 0x99A7:
+ {
+ callSign = "KDF";
+ break;
+ }
+ case 0x9994:
+ {
+ callSign = "KSD";
+ break;
+ }
+ case 0x997A:
+ {
+ callSign = "WIP";
+ break;
+ }
+ case 0x9950:
+ {
+ callSign = "KEX";
+ break;
+ }
+ case 0x9965:
+ {
+ callSign = "KSL";
+ break;
+ }
+ case 0x99B3:
+ {
+ callSign = "WIS";
+ break;
+ }
+ case 0x9951:
+ {
+ callSign = "KFH";
+ break;
+ }
+ case 0x9966:
+ {
+ callSign = "KUJ";
+ break;
+ }
+ case 0x997B:
+ {
+ callSign = "WJR";
+ break;
+ }
+ case 0x9952:
+ {
+ callSign = "KFI";
+ break;
+ }
+ case 0x9995:
+ {
+ callSign = "KUT";
+ break;
+ }
+ case 0x99B4:
+ {
+ callSign = "WJW";
+ break;
+ }
+ case 0x9953:
+ {
+ callSign = "KGA";
+ break;
+ }
+ case 0x9967:
+ {
+ callSign = "KVI";
+ break;
+ }
+ case 0x99B5:
+ {
+ callSign = "WJZ";
+ break;
+ }
+ case 0x9991:
+ {
+ callSign = "KGB";
+ break;
+ }
+ case 0x9968:
+ {
+ callSign = "KWG";
+ break;
+ }
+ case 0x997C:
+ {
+ callSign = "WKY";
+ break;
+ }
+ case 0x9954:
+ {
+ callSign = "KGO";
+ break;
+ }
+ case 0x9996:
+ {
+ callSign = "KXL";
+ break;
+ }
+ case 0x997D:
+ {
+ callSign = "WLS";
+ break;
+ }
+ case 0x9955:
+ {
+ callSign = "KGU";
+ break;
+ }
+ case 0x9997:
+ {
+ callSign = "KXO";
+ break;
+ }
+ case 0x997E:
+ {
+ callSign = "WLW";
+ break;
+ }
+ case 0x9956:
+ {
+ callSign = "KGW";
+ break;
+ }
+ case 0x996B:
+ {
+ callSign = "KYW";
+ break;
+ }
+ case 0x999E:
+ {
+ callSign = "WMC";
+ break;
+ }
+ case 0x9957:
+ {
+ callSign = "KGY";
+ break;
+ }
+ case 0x9999:
+ {
+ callSign = "WBT";
+ break;
+ }
+ case 0x999F:
+ {
+ callSign = "WMT";
+ break;
+ }
+ case 0x99AA:
+ {
+ callSign = "KHQ";
+ break;
+ }
+ case 0x996D:
+ {
+ callSign = "WBZ";
+ break;
+ }
+ case 0x9981:
+ {
+ callSign = "WOC";
+ break;
+ }
+ case 0x9958:
+ {
+ callSign = "KID";
+ break;
+ }
+ case 0x996E:
+ {
+ callSign = "WDZ";
+ break;
+ }
+ case 0x99A0:
+ {
+ callSign = "WOI";
+ break;
+ }
+ case 0x9959:
+ {
+ callSign = "KIT";
+ break;
+ }
+ case 0x996F:
+ {
+ callSign = "WEW";
+ break;
+ }
+ case 0x9983:
+ {
+ callSign = "WOL";
+ break;
+ }
+ case 0x995A:
+ {
+ callSign = "KJR";
+ break;
+ }
+ case 0x999A:
+ {
+ callSign = "WGH";
+ break;
+ }
+ case 0x9984:
+ {
+ callSign = "WOR";
+ break;
+ }
+ case 0x995B:
+ {
+ callSign = "KLO";
+ break;
+ }
+ case 0x9971:
+ {
+ callSign = "WGL";
+ break;
+ }
+ case 0x99A1:
+ {
+ callSign = "WOW";
+ break;
+ }
+ case 0x995C:
+ {
+ callSign = "KLZ";
+ break;
+ }
+ case 0x9972:
+ {
+ callSign = "WGN";
+ break;
+ }
+ case 0x99B9:
+ {
+ callSign = "WRC";
+ break;
+ }
+ case 0x995D:
+ {
+ callSign = "KMA";
+ break;
+ }
+ case 0x9973:
+ {
+ callSign = "WGR";
+ break;
+ }
+ case 0x99A2:
+ {
+ callSign = "WRR";
+ break;
+ }
+ case 0x995E:
+ {
+ callSign = "KMJ";
+ break;
+ }
+ case 0x999B:
+ {
+ callSign = "WGY";
+ break;
+ }
+ case 0x99A3:
+ {
+ callSign = "WSB";
+ break;
+ }
+ case 0x995F:
+ {
+ callSign = "KNX";
+ break;
+ }
+ case 0x9975:
+ {
+ callSign = "WHA";
+ break;
+ }
+ case 0x99A4:
+ {
+ callSign = "WSM";
+ break;
+ }
+ case 0x9960:
+ {
+ callSign = "KOA";
+ break;
+ }
+ case 0x9976:
+ {
+ callSign = "WHB";
+ break;
+ }
+ case 0x9988:
+ {
+ callSign = "WWJ";
+ break;
+ }
+ case 0x99AB:
+ {
+ callSign = "KOB";
+ break;
+ }
+ case 0x9977:
+ {
+ callSign = "WHK";
+ break;
+ }
+ case 0x9989:
+ {
+ callSign = "WWL";
+ break;
+ }
+ }
+ return callSign;
+ }
+
+ /**
+ * Get the Text String for the Program type Code
+ */
+ public static String parsePTY(int pty)
+ {
+ String ptyStr="";
+ int rdsStd = FmSharedPreferences.getFMConfiguration().getRdsStd();
+ if(rdsStd == FmReceiver.FM_RDS_STD_RBDS)
+ {
+ ptyStr = getRBDSPtyString(pty);
+ }
+ else if(rdsStd == FmReceiver.FM_RDS_STD_RDS)
+ {
+ ptyStr = getRDSPtyString(pty);
+ }
+ return (ptyStr);
+ }
+
+ /**
+ * get the Text String for the RBDS Program type Code
+ */
+ public static String getRBDSPtyString(int pty)
+ {
+ String ptyStr = "";
+ switch (pty)
+ {
+ case 1:
+ {
+ ptyStr = "News";
+ break;
+ }
+ case 2:
+ {
+ ptyStr = "Information";
+ break;
+ }
+ case 3:
+ {
+ ptyStr = "Sports";
+ break;
+ }
+ case 4:
+ {
+ ptyStr = "Talk";
+ break;
+ }
+ case 5:
+ {
+ ptyStr = "Rock";
+ break;
+ }
+ case 6:
+ {
+ ptyStr = "Classic Rock";
+ break;
+ }
+ case 7:
+ {
+ ptyStr = "Adult Hits";
+ break;
+ }
+ case 8:
+ {
+ ptyStr = "Soft Rock";
+ break;
+ }
+ case 9:
+ {
+ ptyStr = "Top 40";
+ break;
+ }
+ case 10:
+ {
+ ptyStr = "Country";
+ break;
+ }
+ case 11:
+ {
+ ptyStr = "Oldies";
+ break;
+ }
+ case 12:
+ {
+ ptyStr = "Soft";
+ break;
+ }
+ case 13:
+ {
+ ptyStr = "Nostalgia";
+ break;
+ }
+ case 14:
+ {
+ ptyStr = "Jazz";
+ break;
+ }
+ case 15:
+ {
+ ptyStr = "Classical";
+ break;
+ }
+ case 16:
+ {
+ ptyStr = "Rhythm and Blues";
+ break;
+ }
+ case 17:
+ {
+ ptyStr = "Soft Rhythm and Blues";
+ break;
+ }
+ case 18:
+ {
+ ptyStr = "Foreign Language";
+ break;
+ }
+ case 19:
+ {
+ ptyStr = "Religious Music";
+ break;
+ }
+ case 20:
+ {
+ ptyStr = "Religious Talk";
+ break;
+ }
+ case 21:
+ {
+ ptyStr = "Personality";
+ break;
+ }
+ case 22:
+ {
+ ptyStr = "Public";
+ break;
+ }
+ case 23:
+ {
+ ptyStr = "College";
+ break;
+ }
+ case 29:
+ {
+ ptyStr = "Weather";
+ break;
+ }
+ case 30:
+ {
+ ptyStr = "Emergency Test";
+ break;
+ }
+ case 31:
+ {
+ ptyStr = "Emergency";
+ break;
+ }
+ default:
+ {
+ ptyStr = "";
+ //Log.e(FMRadio.LOGTAG, "Unknown RBDS ProgramType [" + pty + "]");
+ break;
+ }
+ }
+ return ptyStr;
+ }
+
+ /** get the Text String for the Program type Code */
+ public static String getRDSPtyString(int pty)
+ {
+ String ptyStr = "";
+ switch (pty)
+ {
+ case 1:
+ {
+ ptyStr = "News";
+ break;
+ }
+ case 2:
+ {
+ ptyStr = "Current Affairs";
+ break;
+ }
+ case 3:
+ {
+ ptyStr = "Information";
+ break;
+ }
+ case 4:
+ {
+ ptyStr = "Sport";
+ break;
+ }
+ case 5:
+ {
+ ptyStr = "Education";
+ break;
+ }
+ case 6:
+ {
+ ptyStr = "Drama";
+ break;
+ }
+ case 7:
+ {
+ ptyStr = "Culture";
+ break;
+ }
+ case 8:
+ {
+ ptyStr = "Science";
+ break;
+ }
+ case 9:
+ {
+ ptyStr = "Varied";
+ break;
+ }
+ case 10:
+ {
+ ptyStr = "Pop Music";
+ break;
+ }
+ case 11:
+ {
+ ptyStr = "Rock Music";
+ break;
+ }
+ case 12:
+ {
+ ptyStr = "Easy Listening Music";
+ break;
+ }
+ case 13:
+ {
+ ptyStr = "Light classical";
+ break;
+ }
+ case 14:
+ {
+ ptyStr = "Serious classical";
+ break;
+ }
+ case 15:
+ {
+ ptyStr = "Other Music";
+ break;
+ }
+ case 16:
+ {
+ ptyStr = "Weather";
+ break;
+ }
+ case 17:
+ {
+ ptyStr = "Finance";
+ break;
+ }
+ case 18:
+ {
+ ptyStr = "Children programs";
+ break;
+ }
+ case 19:
+ {
+ ptyStr = "Social Affairs";
+ break;
+ }
+ case 20:
+ {
+ ptyStr = "Religion";
+ break;
+ }
+ case 21:
+ {
+ ptyStr = "Phone In";
+ break;
+ }
+ case 22:
+ {
+ ptyStr = "Travel";
+ break;
+ }
+ case 23:
+ {
+ ptyStr = "Leisure";
+ break;
+ }
+ case 24:
+ {
+ ptyStr = "Jazz Music";
+ break;
+ }
+ case 25:
+ {
+ ptyStr = "Country Music";
+ break;
+ }
+ case 26:
+ {
+ ptyStr = "National Music";
+ break;
+ }
+ case 27:
+ {
+ ptyStr = "Oldies Music";
+ break;
+ }
+ case 28:
+ {
+ ptyStr = "Folk Music";
+ break;
+ }
+ case 29:
+ {
+ ptyStr = "Documentary";
+ break;
+ }
+ case 30:
+ {
+ ptyStr = "Emergency Test";
+ break;
+ }
+ case 31:
+ {
+ ptyStr = "Emergency";
+ break;
+ }
+ default:
+ {
+ ptyStr = "";
+ //Log.e(FMRadio.LOGTAG, "Unknown RDS ProgramType [" + pty + "]");
+ break;
+ }
+ }
+ return ptyStr;
+ }
+
+
+}
diff --git a/fmapp2/src/com/caf/fmradio/Settings.java b/fmapp2/src/com/caf/fmradio/Settings.java
new file mode 100644
index 0000000..f5c5d07
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/Settings.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2009-2013, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.fmradio;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.preference.ListPreference;
+import android.preference.CheckBoxPreference;
+import android.preference.PreferenceCategory;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import java.util.Locale;
+
+import android.util.Log;
+
+public class Settings extends PreferenceActivity implements
+ OnSharedPreferenceChangeListener, OnPreferenceClickListener {
+ public static final String RX_MODE = "rx_mode";
+ public static final String REGIONAL_BAND_KEY = "regional_band";
+ public static final String AUDIO_OUTPUT_KEY = "audio_output_mode";
+ public static final String RECORD_DURATION_KEY = "record_duration";
+ public static final String AUTO_AF = "af_checkbox_preference";
+ public static final String RESTORE_FACTORY_DEFAULT = "revert_to_fac";
+ public static final int RESTORE_FACTORY_DEFAULT_INT = 1;
+ public static final String RESTORE_FACTORY_DEFAULT_ACTION = "com.caf.fmradio.settings.revert_to_defaults";
+
+ private static final String LOGTAG = FMRadio.LOGTAG;
+
+ private ListPreference mBandPreference;
+ private ListPreference mAudioPreference;
+ private ListPreference mRecordDurPreference;
+ private CheckBoxPreference mAfPref;
+ private Preference mRestoreDefaultPreference;
+
+ private FmSharedPreferences mPrefs = null;
+ private boolean mRxMode = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ if (intent != null) {
+ mRxMode = intent.getBooleanExtra(RX_MODE, false);
+ }
+ mPrefs = new FmSharedPreferences(this);
+ if (mPrefs != null) {
+ setPreferenceScreen(createPreferenceHierarchy());
+ }
+ }
+
+ private PreferenceScreen createPreferenceHierarchy() {
+ int index = 0;
+ if (mPrefs == null) {
+ return null;
+ }
+ // Root
+ PreferenceScreen root = getPreferenceManager().createPreferenceScreen(
+ this);
+
+ // Band/Country
+ String[] summaryBandItems = getResources().getStringArray(
+ R.array.regional_band_summary);
+ mBandPreference = new ListPreference(this);
+ mBandPreference.setEntries(R.array.regional_band_entries);
+ mBandPreference.setEntryValues(R.array.regional_band_values);
+ mBandPreference.setDialogTitle(R.string.sel_band_menu);
+ mBandPreference.setKey(REGIONAL_BAND_KEY);
+ mBandPreference.setTitle(R.string.regional_band);
+ index = FmSharedPreferences.getCountry();
+ Log.d(LOGTAG, "createPreferenceHierarchy: Country: " + index);
+ // Get the preference and list the value.
+ if ((index < 0) || (index >= summaryBandItems.length)) {
+ index = 0;
+ }
+ Log.d(LOGTAG, "createPreferenceHierarchy: CountrySummary: "
+ + summaryBandItems[index]);
+ mBandPreference.setSummary(summaryBandItems[index]);
+ mBandPreference.setValueIndex(index);
+ root.addPreference(mBandPreference);
+
+ if (mRxMode) {
+ // Audio Output (Stereo or Mono)
+ String[] summaryAudioModeItems = getResources().getStringArray(
+ R.array.ster_mon_entries);
+ mAudioPreference = new ListPreference(this);
+ mAudioPreference.setEntries(R.array.ster_mon_entries);
+ mAudioPreference.setEntryValues(R.array.ster_mon_values);
+ mAudioPreference.setDialogTitle(R.string.sel_audio_output);
+ mAudioPreference.setKey(AUDIO_OUTPUT_KEY);
+ mAudioPreference.setTitle(R.string.aud_output_mode);
+ boolean audiomode = FmSharedPreferences.getAudioOutputMode();
+ if (audiomode) {
+ index = 0;
+ }else {
+ index = 1;
+ }
+ Log.d(LOGTAG, "createPreferenceHierarchy: audiomode: " + audiomode);
+ mAudioPreference.setSummary(summaryAudioModeItems[index]);
+ mAudioPreference.setValueIndex(index);
+ root.addPreference(mAudioPreference);
+
+ // AF Auto Enable (Checkbox)
+ mAfPref = new CheckBoxPreference(this);
+ mAfPref.setKey(AUTO_AF);
+ mAfPref.setTitle(R.string.auto_select_af);
+ mAfPref.setSummaryOn(R.string.auto_select_af_enabled);
+ mAfPref.setSummaryOff(R.string.auto_select_af_disabled);
+ boolean bAFAutoSwitch = FmSharedPreferences.getAutoAFSwitch();
+ Log.d(LOGTAG, "createPreferenceHierarchy: bAFAutoSwitch: "
+ + bAFAutoSwitch);
+ mAfPref.setChecked(bAFAutoSwitch);
+ root.addPreference(mAfPref);
+
+ if(FMRadio.RECORDING_ENABLE) {
+ String[] summaryRecordItems = getResources().getStringArray(
+ R.array.record_durations_entries);
+ int nRecordDuration = 0;
+ mRecordDurPreference = new ListPreference(this);
+ mRecordDurPreference.setEntries(R.array.record_durations_entries);
+ mRecordDurPreference.setEntryValues(R.array.record_duration_values);
+ mRecordDurPreference.setDialogTitle(R.string.sel_rec_dur);
+ mRecordDurPreference.setKey(RECORD_DURATION_KEY);
+ mRecordDurPreference.setTitle(R.string.record_dur);
+ nRecordDuration = FmSharedPreferences.getRecordDuration();
+ Log.d(LOGTAG, "createPreferenceHierarchy: recordDuration: "
+ + nRecordDuration);
+ switch(nRecordDuration) {
+ case FmSharedPreferences.RECORD_DUR_INDEX_0_VAL:
+ index = 0;
+ break;
+ case FmSharedPreferences.RECORD_DUR_INDEX_1_VAL:
+ index = 1;
+ break;
+ case FmSharedPreferences.RECORD_DUR_INDEX_2_VAL:
+ index = 2;
+ break;
+ case FmSharedPreferences.RECORD_DUR_INDEX_3_VAL:
+ index = 3;
+ break;
+ }
+ // Get the preference and list the value.
+ if ((index < 0) || (index >= summaryRecordItems.length)) {
+ index = 0;
+ }
+ Log.d(LOGTAG, "createPreferenceHierarchy: recordDurationSummary: "
+ + summaryRecordItems[index]);
+ mRecordDurPreference.setSummary(summaryRecordItems[index]);
+ mRecordDurPreference.setValueIndex(index);
+ root.addPreference(mRecordDurPreference);
+ }
+ }
+
+ // Add a new category
+ PreferenceCategory prefCat = new PreferenceCategory(this);
+ root.addPreference(prefCat);
+
+ mRestoreDefaultPreference = new Preference(this);
+ mRestoreDefaultPreference.setTitle(
+ R.string.settings_revert_defaults_title);
+ mRestoreDefaultPreference.setKey(RESTORE_FACTORY_DEFAULT);
+ mRestoreDefaultPreference
+ .setSummary(R.string.settings_revert_defaults_summary);
+ mRestoreDefaultPreference.setOnPreferenceClickListener(this);
+ root.addPreference(mRestoreDefaultPreference);
+ return root;
+ }
+ public void clearStationList() {
+ SharedPreferences sp = getSharedPreferences(FMRadio.SCAN_STATION_PREFS_NAME, 0);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.clear();
+ editor.commit();
+ }
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key) {
+ int mTunedFreq = 0;
+ boolean bStatus = false;
+ if (key.equals(REGIONAL_BAND_KEY)) {
+ int curListIndex = FmSharedPreferences.getCurrentListIndex();
+ PresetList curList = FmSharedPreferences.getStationList(curListIndex);
+ String[] summaryBandItems = getResources().getStringArray(
+ R.array.regional_band_summary);
+ String valueStr = sharedPreferences.getString(key, "");
+ int index = 0;
+ if (valueStr != null) {
+ index = mBandPreference.findIndexOfValue(valueStr);
+ }
+ if ((index < 0) || (index >= summaryBandItems.length)) {
+ index = 0;
+ mBandPreference.setValueIndex(0);
+ }
+ Log.d(LOGTAG, "onSharedPreferenceChanged: Country Change: "
+ + index);
+ mBandPreference.setSummary(summaryBandItems[index]);
+ FmSharedPreferences.setCountry(index);
+ bStatus = FMRadio.fmConfigure();
+ FMTransmitterActivity.fmConfigure();
+ if (curList != null) {
+ curList.clear();
+ }
+ clearStationList();
+ }else {
+ if(mRxMode) {
+ if (key.equals(AUTO_AF)) {
+ boolean bAFAutoSwitch = mAfPref.isChecked();
+ Log.d(LOGTAG, "onSharedPreferenceChanged: Auto AF Enable: "
+ + bAFAutoSwitch);
+ FmSharedPreferences.setAutoAFSwitch(bAFAutoSwitch);
+ FMRadio.fmAutoAFSwitch();
+ mPrefs.Save();
+ }else if(key.equals(RECORD_DURATION_KEY)) {
+ if(FMRadio.RECORDING_ENABLE) {
+ String[] recordItems = getResources().getStringArray(
+ R.array.record_durations_entries);
+ String valueStr = mRecordDurPreference.getValue();
+ int index = 0;
+ if (valueStr != null) {
+ index = mRecordDurPreference.findIndexOfValue(valueStr);
+ }
+ if ((index < 0) || (index >= recordItems.length)) {
+ index = 0;
+ mRecordDurPreference.setValueIndex(index);
+ }
+ Log.d(LOGTAG, "onSharedPreferenceChanged: recorddur: "
+ + recordItems[index]);
+ mRecordDurPreference.setSummary(recordItems[index]);
+ FmSharedPreferences.setRecordDuration(index);
+ }
+ }else if (key.equals(AUDIO_OUTPUT_KEY)) {
+ String[] bandItems = getResources().getStringArray(
+ R.array.ster_mon_entries);
+ String valueStr = mAudioPreference.getValue();
+ int index = 0;
+ if (valueStr != null) {
+ index = mAudioPreference.findIndexOfValue(valueStr);
+ }
+ if (index != 1) {
+ if (index != 0) {
+ index = 0;
+ /* It shud be 0(Stereo) or 1(Mono) */
+ mAudioPreference.setValueIndex(index);
+ }
+ }
+ Log.d(LOGTAG, "onSharedPreferenceChanged: audiomode: "
+ + bandItems[index]);
+ mAudioPreference.setSummary(bandItems[index]);
+ if (index == 0) {
+ // Stereo
+ FmSharedPreferences.setAudioOutputMode(true);
+ }else {
+ // Mono
+ FmSharedPreferences.setAudioOutputMode(false);
+ }
+ FMRadio.fmAudioOutputMode();
+ }
+ }
+ }
+ if (mPrefs != null) {
+ if(bStatus) {
+ mPrefs.Save();
+ }else {
+ mTunedFreq = FmSharedPreferences.getTunedFrequency();
+ if (mTunedFreq > FmSharedPreferences.getUpperLimit() ||
+ mTunedFreq < FmSharedPreferences.getLowerLimit()) {
+ FmSharedPreferences.setTunedFrequency(
+ FmSharedPreferences.getLowerLimit());
+ }
+ mPrefs.Save();
+ }
+ }
+ }
+
+ public boolean onPreferenceClick(Preference preference) {
+ boolean handled = false;
+ if (preference == mRestoreDefaultPreference) {
+ showDialog(RESTORE_FACTORY_DEFAULT_INT);
+ }
+ return handled;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case RESTORE_FACTORY_DEFAULT_INT:
+ return new AlertDialog.Builder(this).setIcon(
+ R.drawable.alert_dialog_icon).setTitle(
+ R.string.settings_revert_confirm_title).setMessage(
+ R.string.settings_revert_confirm_msg).setPositiveButton(
+ R.string.alert_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ Intent data = new Intent(
+ RESTORE_FACTORY_DEFAULT_ACTION);
+ setResult(RESULT_OK, data);
+ restoreSettingsDefault();
+ finish();
+ }
+ }).setNegativeButton(R.string.alert_dialog_cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ }
+ }).create();
+ default:
+ break;
+ }
+ return null;
+ }
+
+ private void restoreSettingsDefault() {
+ if (mPrefs != null) {
+ if (Locale.getDefault().equals(Locale.CHINA)) {
+ mBandPreference
+ .setValueIndex(FmSharedPreferences.REGIONAL_BAND_CHINA);
+ }else {
+ mBandPreference
+ .setValueIndex(FmSharedPreferences.REGIONAL_BAND_NORTH_AMERICA);
+ }
+ if (mRxMode) {
+ mAudioPreference.setValueIndex(0);
+ if (FMRadio.RECORDING_ENABLE) {
+ mRecordDurPreference.setValueIndex(0);
+ }
+ mAfPref.setChecked(true);
+ FmSharedPreferences.SetDefaults();
+ }else {
+ if (Locale.getDefault().equals(Locale.CHINA)) {
+ FmSharedPreferences
+ .setCountry(FmSharedPreferences.REGIONAL_BAND_CHINA);
+ }else{
+ FmSharedPreferences
+ .setCountry(FmSharedPreferences.REGIONAL_BAND_NORTH_AMERICA);
+ }
+ }
+ mPrefs.Save();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+ SharedPreferences sharedPreferences = null;
+ if (preferenceScreen != null) {
+ sharedPreferences = preferenceScreen.getSharedPreferences();
+ }
+ if (sharedPreferences != null) {
+ sharedPreferences.registerOnSharedPreferenceChangeListener(this);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+ SharedPreferences sharedPreferences = null;
+ if (preferenceScreen != null) {
+ sharedPreferences = preferenceScreen.getSharedPreferences();
+ }
+ if (sharedPreferences != null) {
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
+ }
+ }
+}
diff --git a/fmapp2/src/com/caf/fmradio/StationListActivity.java b/fmapp2/src/com/caf/fmradio/StationListActivity.java
new file mode 100644
index 0000000..46b59ad
--- /dev/null
+++ b/fmapp2/src/com/caf/fmradio/StationListActivity.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2012-2013, 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.caf.fmradio;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.Handler.Callback;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Gravity;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+
+public class StationListActivity extends Activity implements
+ View.OnCreateContextMenuListener {
+
+ private static final String LOGTAG = "StationList";
+ private ListView mStationList;
+ private PresetList mPresetList;
+ private SimpleAdapter mAdapter;
+ private static IFMRadioService mService = null;
+ private static final int CONTEXT_MENU_RENAME = 1;
+ private static final int CONTEXT_MENU_DELETE = 2;
+
+ static final int DIALOG_RENAME_ID = 0;
+ static final int DIALOG_DELETE_ID = 1;
+ // private static String mDialogTitle;
+ private static String mDialogStationName;
+ private static int mItemId;
+ private Dialog mRenameDialog;
+ private Dialog mDeleteDialog;
+ //whether is the first headset plug event
+ private boolean isFirst = true;
+ private ServiceConnection osc = new ServiceConnection() {
+ public void onServiceConnected(ComponentName classname, IBinder obj) {
+ mService = IFMRadioService.Stub.asInterface(obj);
+ if (mService != null) {
+ return;
+ } else {
+ Log.e(LOGTAG, "IFMRadioService onServiceConnected failed");
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName classname) {
+ }
+ };
+
+ ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
+ HashMap<Integer, Integer> mIndex = new HashMap<Integer, Integer>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.station_list);
+ bindService((new Intent()).setClass(this, FMRadioService.class), osc, 0);
+
+ mStationList = (ListView) findViewById(R.id.station_list);
+ // mPresetList = new PresetList("StationList");
+ mStationList.setOnCreateContextMenuListener(this);
+ mStationList.setOnItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
+ long arg3) {
+ String freq = ((HashMap<String, String>) mAdapter.getItem(arg2))
+ .get("freq");
+ Float fFreq = Float.parseFloat(freq);
+ if (mService != null) {
+ try {
+ mService.tune((int) ((fFreq * 1000)));
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ } else {
+ Log.d(LOGTAG, "mService is null........");
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ // TODO Auto-generated method stub
+ AdapterContextMenuInfo mi = (AdapterContextMenuInfo) item.getMenuInfo();
+ switch (item.getItemId()) {
+ case CONTEXT_MENU_RENAME:
+ showDialog(DIALOG_RENAME_ID);
+ break;
+ case CONTEXT_MENU_DELETE:
+ showDialog(DIALOG_DELETE_ID);
+ break;
+
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ // TODO Auto-generated method stub
+ AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfo;
+ menu.add(0, CONTEXT_MENU_RENAME, 0, getString(R.string.preset_rename));
+ menu.add(0, CONTEXT_MENU_DELETE, 0, getString(R.string.preset_delete));
+ mItemId = mi.position;
+ menu.setHeaderTitle(getString(R.string.station_name)+getNameFromId(mItemId));
+ }
+
+ @Override
+ protected void onResume() {
+ // TODO Auto-generated method stub
+ load();
+ super.onResume();
+ }
+
+ private boolean stationNameExist(String name) {
+ for (HashMap<String, String> item : list) {
+ if (item != null && name.equals(item.get("name"))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle b) {
+ // TODO Auto-generated method stub
+ // super.onPrepareDialog(id, dialog);
+ // After change system language, current function will be executed before
+ // onResume , so execute load to ensure adapter is not null.
+ load();
+ switch (id) {
+ case DIALOG_RENAME_ID:
+ mRenameDialog.setTitle(getString(R.string.station_name)+getNameFromId(mItemId));
+ final EditText editText = (EditText) mRenameDialog
+ .findViewById(R.id.name);
+ editText.setText(getNameFromId(mItemId));
+ Button bOk = (Button) mRenameDialog.findViewById(R.id.save);
+
+ bOk.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String rename = editText.getText().toString();
+ if (TextUtils.isEmpty(rename) || TextUtils.isEmpty(rename.trim())) {
+ Context context = getApplicationContext();
+ Toast toast = Toast.makeText(context, getString(R.string.station_name_empty),
+ Toast.LENGTH_SHORT);
+ toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL, 0, 0);
+ toast.show();
+ } else if (stationNameExist(rename)) {
+ Context context = getApplicationContext();
+ Toast toast = Toast.makeText(context,
+ getString(R.string.station_name_exist, rename), Toast.LENGTH_SHORT);
+ toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL, 0, 0);
+ toast.show();
+ } else {
+ saveStationName(mItemId,rename);
+ mRenameDialog.dismiss();
+ }
+ }
+
+ });
+ Button bCancel = (Button) mRenameDialog.findViewById(R.id.cancel);
+ bCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ mRenameDialog.dismiss();
+ }
+ });
+ break;
+ case DIALOG_DELETE_ID:
+ mDeleteDialog.setTitle(getString(R.string.station_list_delete_station, getNameFromId(mItemId)));
+ TextView prompt = (TextView) mDeleteDialog.findViewById(R.id.prompt);
+ prompt.setText(getString(R.string.station_list_delete_station_prompt,getNameFromId(mItemId)));
+ Button bDelete = (Button) mDeleteDialog.findViewById(R.id.delete);
+
+ bDelete.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ deleteStation(mItemId);
+ mDeleteDialog.dismiss();
+ }
+ });
+ Button bCancelDelete = (Button) mDeleteDialog.findViewById(R.id.cancel);
+ bCancelDelete.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mDeleteDialog.dismiss();
+ }
+ });
+ break;
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle b) {
+ // TODO Auto-generated method stub
+ //should return different dialog
+ switch (id) {
+ case DIALOG_RENAME_ID:
+ mRenameDialog = new Dialog(this);
+ mRenameDialog.setContentView(R.layout.rename_dialog);
+ return mRenameDialog;
+ case DIALOG_DELETE_ID:
+ mDeleteDialog = new Dialog(this);
+ mDeleteDialog.setContentView(R.layout.delete_dialog);
+ return mDeleteDialog;
+ default:
+ ;
+ }
+ return null;
+ }
+
+ private void saveStationName(int id, String name) {
+ Integer stationIndex = mIndex.get(new Integer(id));
+ SharedPreferences sp = getSharedPreferences(
+ FMRadio.SCAN_STATION_PREFS_NAME, 0);
+ SharedPreferences.Editor editor = sp.edit();
+ editor
+ .putString(FMRadio.STATION_NAME + (stationIndex.intValue()),
+ name);
+ editor.commit();
+ load();
+ }
+
+ private void deleteStation(int id) {
+ SharedPreferences sp = getSharedPreferences(
+ FMRadio.SCAN_STATION_PREFS_NAME, 0);
+ Integer stationIndex = mIndex.get(new Integer(id));
+ SharedPreferences.Editor editor = sp.edit();
+ editor.remove(FMRadio.STATION_NAME + (stationIndex));
+ editor.remove(FMRadio.STATION_FREQUENCY + (stationIndex));
+ editor.commit();
+ load();
+ }
+
+ private int getFrequencyFromId(int id) {
+ String freq = ((HashMap<String, String>) mAdapter.getItem(id))
+ .get("freq");
+ Float fFreq = Float.parseFloat(freq);
+ return (int) ((fFreq * 1000));
+ }
+
+ private String getNameFromId(int id) {
+ String name = ((HashMap<String, String>) mAdapter.getItem(id))
+ .get("name");
+ return name;
+
+ }
+
+ protected void load() {
+ list.clear();
+ mIndex.clear();
+ SharedPreferences sp = getSharedPreferences(
+ FMRadio.SCAN_STATION_PREFS_NAME, 0);
+ int station_number = sp.getInt(FMRadio.NUM_OF_STATIONS, 0);
+ for (int i = 1; i <= station_number; i++) {
+ HashMap<String, String> item = new HashMap<String, String>();
+ String name = sp.getString(FMRadio.STATION_NAME + i, "");
+
+ int frequency = sp.getInt(FMRadio.STATION_FREQUENCY + i, 0);
+
+ if (!name.equals("") && frequency != 0) {
+ item.put("name", name);
+ item.put("freq", String.valueOf(frequency / 1000.0f));
+ mIndex.put(new Integer(list.size()), new Integer(i));
+ list.add(item);
+ }
+ }
+
+ mAdapter = new SimpleAdapter(this, list, R.layout.station_list_item,
+ new String[] { "name", "freq" }, new int[] { R.id.name,
+ R.id.freq });
+
+ mStationList.setAdapter(mAdapter);
+ }
+
+ @Override
+ protected void onDestroy() {
+ unbindService(osc);
+ mService = null;
+ super.onDestroy();
+ }
+
+}
diff --git a/fmapp2/src/com/caf/hc_utils/A2dpDeviceStatus.java b/fmapp2/src/com/caf/hc_utils/A2dpDeviceStatus.java
new file mode 100644
index 0000000..45b647e
--- /dev/null
+++ b/fmapp2/src/com/caf/hc_utils/A2dpDeviceStatus.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2011-2012, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.utils;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.content.Context;
+
+public class A2dpDeviceStatus {
+ private BluetoothAdapter mAdapter;
+ private BluetoothA2dp mA2dp = null;
+ public String getActionSinkStateChangedString (){
+ return BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED;
+ }
+ public String getActionPlayStateChangedString (){
+ return BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED;
+ }
+ public boolean isA2dpStateChange( String action) {
+ if(action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED) ) {
+ return true;
+ }
+ return false;
+ }
+ public boolean isA2dpPlayStateChange( String action) {
+ if(action.equals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED) ) {
+ return true;
+ }
+ return false;
+ }
+ public boolean isConnected(Intent intent) {
+ boolean isConnected = false;
+ int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE,
+ BluetoothA2dp.STATE_DISCONNECTED);
+ if((state == BluetoothA2dp.STATE_CONNECTED) || (state == BluetoothProfile.STATE_CONNECTED)) {
+ isConnected = true;
+ }
+ return isConnected;
+ }
+ public boolean isPlaying(Intent intent) {
+ boolean isPlaying = false;
+ int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE,
+ BluetoothA2dp.STATE_DISCONNECTED);
+ if(state == BluetoothA2dp.STATE_PLAYING ) {
+ isPlaying = true;
+ }
+ return isPlaying;
+ }
+ public boolean isDeviceAvailable() {
+ if(null != mA2dp) {
+ if( 0 != mA2dp.getConnectedDevices().size() ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public A2dpDeviceStatus(Context mContext) {
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mAdapter.getProfileProxy(mContext, mProfileListener,
+ BluetoothProfile.A2DP);
+ }
+ private BluetoothProfile.ServiceListener mProfileListener =
+ new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (profile == BluetoothProfile.A2DP) {
+ mA2dp = (BluetoothA2dp) proxy;
+ }
+ }
+ public void onServiceDisconnected(int profile) {
+ if (profile == BluetoothProfile.A2DP) {
+ mA2dp = null;
+ }
+ }
+};
+}
+
diff --git a/fmapp2/src/com/caf/hc_utils/FrequencyPicker.java b/fmapp2/src/com/caf/hc_utils/FrequencyPicker.java
new file mode 100644
index 0000000..c88bb0a
--- /dev/null
+++ b/fmapp2/src/com/caf/hc_utils/FrequencyPicker.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution
+ *
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.caf.utils;
+
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+
+import com.caf.fmradio.R;
+import android.widget.NumberPicker;
+import android.widget.NumberPicker.OnValueChangeListener;
+
+/**
+ * A view for selecting the frequency
+ *
+ * For a dialog using this view, see {FrequencyPickerDialog}.
+ */
+
+public class FrequencyPicker extends FrameLayout {
+
+ /* UI Components */
+ private final NumberPicker mMHzPicker;
+ private final NumberPicker mKHzPicker;
+
+ /**
+ * How we notify users the Frequency has changed.
+ */
+ private OnFrequencyChangedListener mOnFrequencyChangedListener;
+
+ private int mFrequency;
+ private int mMin;
+ private int mMax;
+ private int mStep;
+ private int mMhz;
+ private int mKhz;
+
+ /**
+ * The callback used to indicate the user changes the Frequency.
+ */
+ public interface OnFrequencyChangedListener {
+
+ /**
+ * @param view The view associated with this listener.
+ * @param frequency The Frequency that was set.
+ */
+ void onFrequencyChanged(FrequencyPicker view, int frequency);
+ }
+
+ public FrequencyPicker(Context context) {
+ this(context, null);
+ }
+
+ public FrequencyPicker(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FrequencyPicker(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.frequency_picker, this, true);
+
+ mMHzPicker = (NumberPicker) findViewById(R.id.mhz);
+ if (mMHzPicker != null) {
+ mMHzPicker.setOnLongPressUpdateInterval(100);
+
+ mMHzPicker.setOnValueChangedListener(new OnValueChangeListener() {
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ mMhz = newVal;
+ mFrequency = (mMhz*1000) + (getFrequencyKHz(mKhz, mMin, mStep));
+ validateFrequencyRange();
+ if (mOnFrequencyChangedListener != null) {
+ mOnFrequencyChangedListener.onFrequencyChanged(FrequencyPicker.this, mFrequency);
+ }
+ }
+ });
+ }
+ mKHzPicker = (NumberPicker) findViewById(R.id.khz);
+ if (mKHzPicker != null) {
+ mKHzPicker.setOnLongPressUpdateInterval(100);
+ mKHzPicker.setOnValueChangedListener(new OnValueChangeListener() {
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ mKhz = newVal;
+ mFrequency = (mMhz*1000) + (getFrequencyKHz(mKhz, mMin, mStep));
+
+ validateFrequencyRange();
+
+ if (mOnFrequencyChangedListener != null) {
+ mOnFrequencyChangedListener.onFrequencyChanged(FrequencyPicker.this, mFrequency);
+ }
+ }
+ });
+ }
+
+ updateSpinnerRange();
+
+ if (!isEnabled()) {
+ setEnabled(false);
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ mMHzPicker.setEnabled(enabled);
+ mKHzPicker.setEnabled(enabled);
+ }
+
+
+ public void updateFrequency(int frequency) {
+ mFrequency = frequency;
+ updateSpinners();
+ }
+
+ private static class SavedState extends BaseSavedState {
+
+ private final int mMHZ;
+ private final int mKHZ;
+
+ /**
+ * Constructor called from {@link FrequencyPicker#onSaveInstanceState()}
+ */
+ private SavedState(Parcelable superState, int mhz, int khz) {
+ super(superState);
+ mMHZ = mhz;
+ mKHZ = khz;
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ mMHZ = in.readInt();
+ mKHZ = in.readInt();
+ }
+
+ public int getMHz() {
+ return mMHZ;
+ }
+
+ public int getKHz() {
+ return mKHZ;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mMHZ);
+ dest.writeInt(mKHZ);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Creator<SavedState>() {
+
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+
+ /**
+ * Override so we are in complete control of save / restore for this widget.
+ */
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ dispatchThawSelfOnly(container);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ return new SavedState(superState, mMhz, mKhz);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ mMhz = ss.getMHz();
+ mKhz = ss.getKHz();
+ }
+
+ private String[] getKHzStrings(int min, int stepSize)
+ {
+ if(stepSize == 100)
+ {
+ return (get100KHzStrings());
+ }
+ else if(stepSize == 50)
+ {
+ return (get50KHzStrings());
+ }
+ return (get200KHzStrings(min));
+ }
+
+ private int getKHzCount(int stepSize)
+ {
+ if(stepSize == 100)
+ {
+ return (10);
+ }
+ else if(stepSize == 50)
+ {
+ return (20);
+ }
+ return (5);
+ }
+ private int getCurrentKHz(int frequency, int min, int stepSize)
+ {
+ if(stepSize == 100)
+ {
+ return (getCurrent100KHz(frequency));
+ }
+ else if(stepSize == 50)
+ {
+ return (getCurrent50KHz(frequency));
+ }
+ return (getCurrent200KHz(frequency, min));
+ }
+
+ private int getFrequencyKHz(int kHz, int min, int stepSize)
+ {
+ if(stepSize == 100)
+ {
+ return (getFrequency100KHz(kHz));
+ }
+ else if(stepSize == 50)
+ {
+ return (getFrequency50KHz(kHz));
+ }
+ return (getFrequency200KHz(kHz, min));
+ }
+
+ private int getFrequency100KHz(int kHz)
+ {
+ int frequencykhz = ((kHz-1)*100) ;
+ //Log.d("FMRadio", "FP: getCurrent100KHz: " + frequencykhz);
+ return (frequencykhz);
+ }
+
+ private int getFrequency50KHz(int kHz)
+ {
+ int frequencykhz = ((kHz-1)*50) ;
+ //Log.d("FMRadio", "FP: getCurrent100KHz: " + frequencykhz);
+ return (frequencykhz);
+ }
+
+ private int getFrequency200KHz(int kHz, int min)
+ {
+ int frequencykhz = ((kHz-1)*200) ;
+ if(min%200 != 0)
+ {
+ frequencykhz = ((kHz-1)*200)+100 ;
+ }
+ //Log.d("FMRadio", "FP: getCurrent200KHz: " + frequencykhz);
+ return (frequencykhz);
+ }
+
+ private int getCurrent100KHz(int frequency)
+ {
+ int khz = ((frequency%1000)/100) ;
+ //Log.d("FMRadio", "FP: getCurrent100KHz: " + khz);
+ return (khz+1);
+ }
+
+ private int getCurrent50KHz(int frequency)
+ {
+ int khz = ((frequency%1000)/50) ;
+ //Log.d("FMRadio", "FP: getCurrent50KHz: " + khz);
+ return (khz+1);
+ }
+
+ private int getCurrent200KHz(int frequency, int min)
+ {
+ int khz = ((frequency%1000)/200);
+ //Log.d("FMRadio", "FP: getCurrent200KHz: " + khz);
+ return (khz+1);
+ }
+
+ private String[] get50KHzStrings()
+ {
+ String[] khzStrings= {"00","05","10","15","20","25","30","35","40","45",
+ "50","55","60","65","70","75","80","85","90","95"};
+ //Log.d("FMRadio", "FP: get50KHzStrings");
+ return khzStrings;
+ }
+
+ private String[] get100KHzStrings()
+ {
+ String[] khzStrings= {"0","1","2","3","4","5",
+ "6","7","8","9"};
+ //Log.d("FMRadio", "FP: get100KHzStrings");
+ return khzStrings;
+ }
+
+ private String[] get200KHzStrings(int min)
+ {
+ if(min%200 == 0)
+ {
+ return (getEven200KHzStrings());
+ }
+ return (getOdd200KHzStrings());
+ }
+
+ private String[] getEven200KHzStrings()
+ {
+ String[] khzStrings= {"0","2","4","6","8"};
+ //Log.d("FMRadio", "FP: getEven200KHzStrings");
+ return khzStrings;
+ }
+ private String[] getOdd200KHzStrings()
+ {
+ String[] khzStrings= {"1","3","5","7","9"};
+ //Log.d("FMRadio", "FP: getOdd200KHzStrings");
+ return khzStrings;
+ }
+ public void updateSteps(int steps)
+ {
+ mStep = steps;
+ }
+ public void updateMinFreq(int freq)
+ {
+ mMin = freq;
+ }
+ public void updateMaxFreq(int freq)
+ {
+ mMax = freq;
+ }
+ /**
+ * Initialize the state.
+ * @param year The initial year.
+ * @param monthOfYear The initial month.
+ * @param dayOfMonth The initial day of the month.
+ * @param onDateChangedListener How user is notified date is changed by user, can be null.
+ */
+ public void init(int min, int max, int step, int frequency,
+ OnFrequencyChangedListener onFrequencyChangedListener) {
+
+ mMin = min;
+ mMax = max;
+ mStep = step;
+ mFrequency = frequency;
+ mOnFrequencyChangedListener = onFrequencyChangedListener;
+
+ updateSpinners();
+ }
+ private void updateSpinnerRange() {
+ String[] khzStrings = getKHzStrings(mMin, mStep);
+ int khzNumSteps = getKHzCount(mStep);
+
+ if (mMHzPicker != null) {
+ mMHzPicker.setMinValue(mMin/1000);
+ mMHzPicker.setMaxValue(mMax/1000);
+ }
+ if (mKHzPicker != null) {
+ mKHzPicker.setDisplayedValues(get50KHzStrings());
+ mKHzPicker.setMinValue(1);
+ mKHzPicker.setMaxValue(khzNumSteps);
+ mKHzPicker.setDisplayedValues(khzStrings);
+ }
+ }
+
+ private void updateSpinners() {
+ int khzNumSteps = getKHzCount(mStep);
+ updateSpinnerRange();
+ mMhz = (int)(mFrequency/1000);
+ mKhz = getCurrentKHz(mFrequency, mMin, mStep) ;
+ if((mMin/1000 <=mMhz) && (mMax/1000 >=mMhz))
+ {
+ mMHzPicker.setValue(mMhz);
+ }
+ if(mKhz <= khzNumSteps)
+ {
+ mKHzPicker.setValue(mKhz);
+ }
+ }
+ private void validateFrequencyRange()
+ {
+ boolean bUpdateSpinner=false;
+ if(mFrequency < mMin)
+ {
+ mFrequency = mMin;
+ bUpdateSpinner = true;
+ }
+ if(mFrequency > mMax)
+ {
+ mFrequency = mMax;
+ bUpdateSpinner = true;
+ }
+ if(bUpdateSpinner == true)
+ {
+ updateSpinners();
+ }
+ }
+
+ public int getFrequency() {
+ return (mFrequency);
+ }
+}
diff --git a/fmapp2/src/com/caf/hc_utils/FrequencyPickerDialog.java b/fmapp2/src/com/caf/hc_utils/FrequencyPickerDialog.java
new file mode 100644
index 0000000..cac7dc1
--- /dev/null
+++ b/fmapp2/src/com/caf/hc_utils/FrequencyPickerDialog.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2012 - 2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution
+ *
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.caf.utils;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import qcom.fmradio.FmConfig;
+import qcom.fmradio.FmReceiver;
+
+import com.caf.fmradio.R;
+import com.caf.utils.FrequencyPicker.OnFrequencyChangedListener;
+
+/**
+ * A simple dialog containing an FrequencyPicker.
+ */
+public class FrequencyPickerDialog extends AlertDialog implements OnClickListener,
+OnFrequencyChangedListener {
+
+ private static final String FREQUENCY = "FREQUENCY";
+ private static final String FREQ_MIN = "FREQ_MIN";
+ private static final String FREQ_MAX = "FREQ_MAX";
+ private static final String FREQ_STEP = "FREQ_STEP";
+
+ private final FrequencyPicker mFrequencyPicker;
+ private final OnFrequencySetListener mCallBack;
+ private int mMinFrequency;
+ private int mMaxFrequency;
+ private int mChannelSpacing;
+
+ /**
+ * The callback used to indicate the user is done filling in the date.
+ */
+ public interface OnFrequencySetListener {
+
+ void onFrequencySet(FrequencyPicker view, int frequency);
+ }
+ /**
+ */
+ public FrequencyPickerDialog(Context context,
+ FmConfig fmConfig,
+ int frequency,
+ OnFrequencySetListener callback) {
+ //this(context, android.R.style.Theme_Dialog, fmConfig, frequency, callback);
+ this(context, com.android.internal.R.style.Theme_Dialog_Alert, fmConfig, frequency, callback);
+ }
+
+ /**
+ */
+ public FrequencyPickerDialog(Context context,
+ int theme,
+ FmConfig fmConfig,
+ int frequency,
+ OnFrequencySetListener callback) {
+ super(context, theme);
+ mMinFrequency = fmConfig.getLowerLimit();
+ mMaxFrequency = fmConfig.getUpperLimit();
+ mChannelSpacing = 200;
+ if(FmReceiver.FM_CHSPACE_200_KHZ == fmConfig.getChSpacing())
+ {
+ mChannelSpacing = 200;
+ }
+ else if(FmReceiver.FM_CHSPACE_100_KHZ == fmConfig.getChSpacing())
+ {
+ mChannelSpacing = 100;
+ }
+ else if(FmReceiver.FM_CHSPACE_50_KHZ == fmConfig.getChSpacing())
+ {
+ mChannelSpacing = 50;
+ }
+ int MHz = frequency/1000;
+ int KHz = (frequency%1000)/100;
+ setTitle("FM - "+MHz+"."+KHz);
+ mCallBack = callback;
+
+ setButton("Set", this);
+ setButton2("Cancel", (OnClickListener) null);
+ setIcon(R.drawable.alert_dialog_icon);
+
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.frequency_picker_dialog, null);
+ setView(view);
+ mFrequencyPicker = (FrequencyPicker) view.findViewById(R.id.frequencyPicker);
+ if(mFrequencyPicker != null)
+ {
+ mFrequencyPicker.init(mMinFrequency, mMaxFrequency, mChannelSpacing, frequency, this);
+ }
+ else
+ {
+ Log.e("fmRadio", "Failed to find ID: R.id.frequencyPicker");
+ }
+ }
+
+ public void UpdateFrequency(int frequency)
+ {
+ int MHz = frequency/1000;
+ int KHz = (frequency%1000)/100;
+ setTitle("FM - "+MHz+"."+KHz);
+ mFrequencyPicker.updateFrequency(frequency);
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (mCallBack != null) {
+ mFrequencyPicker.clearFocus();
+ int frequency = mFrequencyPicker.getFrequency();
+ mCallBack.onFrequencySet(mFrequencyPicker, frequency);
+ }
+ }
+
+ public void onFrequencyChanged(FrequencyPicker view, int frequency) {
+ UpdateFrequency(frequency);
+ }
+
+ @Override
+ public Bundle onSaveInstanceState() {
+ Bundle state = super.onSaveInstanceState();
+ state.putInt(FREQUENCY, mFrequencyPicker.getFrequency());
+ state.putInt(FREQ_MIN, mMinFrequency);
+ state.putInt(FREQ_MAX, mMaxFrequency);
+ state.putInt(FREQ_STEP, mChannelSpacing);
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ int frequency = savedInstanceState.getInt(FREQUENCY);
+ mMinFrequency = savedInstanceState.getInt(FREQ_MIN);
+ mMaxFrequency = savedInstanceState.getInt(FREQ_MAX );
+ mChannelSpacing = savedInstanceState.getInt(FREQ_STEP);
+ mFrequencyPicker.init(mMinFrequency, mMaxFrequency, mChannelSpacing, frequency, this);
+ int MHz = frequency/1000;
+ int KHz = (frequency%1000)/100;
+ setTitle("FM - "+MHz+"."+KHz);
+ }
+ public void updateSteps(int steps)
+ {
+ mChannelSpacing = 200;
+ if(FmReceiver.FM_CHSPACE_200_KHZ == steps)
+ {
+ mChannelSpacing = 200;
+ }
+ else if(FmReceiver.FM_CHSPACE_100_KHZ == steps)
+ {
+ mChannelSpacing = 100;
+ }
+ else if(FmReceiver.FM_CHSPACE_50_KHZ == steps)
+ {
+ mChannelSpacing = 50;
+ }
+ mFrequencyPicker.updateSteps(mChannelSpacing);
+ }
+ public void updateMinFreq(int freq)
+ {
+ mMinFrequency = freq;
+ mFrequencyPicker.updateMinFreq(mMinFrequency);
+ }
+ public void updateMaxFreq(int freq)
+ {
+ mMaxFrequency = freq;
+ mFrequencyPicker.updateMaxFreq(mMaxFrequency);
+ }
+}
diff --git a/fmapp2/src/com/caf/utils/A2dpDeviceStatus.java b/fmapp2/src/com/caf/utils/A2dpDeviceStatus.java
new file mode 100644
index 0000000..3f1c010
--- /dev/null
+++ b/fmapp2/src/com/caf/utils/A2dpDeviceStatus.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2011, 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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.caf.utils;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.Set;
+
+
+public class A2dpDeviceStatus {
+ private BluetoothA2dp mA2dp = null;
+ public String getActionSinkStateChangedString (){
+ return BluetoothA2dp.ACTION_SINK_STATE_CHANGED;
+ }
+ public String getActionPlayStateChangedString (){
+ return BluetoothA2dp.ACTION_SINK_STATE_CHANGED;
+ }
+ public boolean isA2dpStateChange( String action) {
+ if(action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED) ) {
+ return true;
+ }
+ return false;
+ }
+ public boolean isA2dpPlayStateChange( String action) {
+ return isA2dpStateChange(action);
+ }
+ public boolean isConnected(Intent intent) {
+ boolean isConnected = false;
+ int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE,
+ BluetoothA2dp.STATE_DISCONNECTED);
+ if (state == BluetoothA2dp.STATE_CONNECTED ||
+ state == BluetoothA2dp.STATE_PLAYING){
+ isConnected = true;
+ }
+ return isConnected;
+ }
+ public boolean isPlaying(Intent intent) {
+ boolean isPlaying = false;
+ int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE,
+ BluetoothA2dp.STATE_DISCONNECTED);
+ if(state == BluetoothA2dp.STATE_PLAYING){
+ isPlaying = true;
+ }
+ return isPlaying;
+ }
+ public boolean isDeviceAvailable() {
+ if(null == mA2dp) return false;
+ Set<BluetoothDevice> sinks = mA2dp.getConnectedSinks();
+ if (sinks != null && sinks.size() != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ public A2dpDeviceStatus(Context mContext) {
+ mA2dp = new BluetoothA2dp(mContext);
+ }
+
+}
diff --git a/fmapp2/src/com/caf/utils/FrequencyPicker.java b/fmapp2/src/com/caf/utils/FrequencyPicker.java
new file mode 100644
index 0000000..1ea10e8
--- /dev/null
+++ b/fmapp2/src/com/caf/utils/FrequencyPicker.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.caf.utils;
+
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+
+import com.caf.fmradio_2.R;
+import android.widget.NumberPicker;
+import android.widget.NumberPicker.OnChangedListener;
+
+/**
+ * A view for selecting the frequency
+ *
+ * For a dialog using this view, see {FrequencyPickerDialog}.
+ */
+
+public class FrequencyPicker extends FrameLayout {
+
+ /* UI Components */
+ private final NumberPicker mMHzPicker;
+ private final NumberPicker mKHzPicker;
+
+ /**
+ * How we notify users the Frequency has changed.
+ */
+ private OnFrequencyChangedListener mOnFrequencyChangedListener;
+
+ private int mFrequency;
+ private int mMin;
+ private int mMax;
+ private int mStep;
+ private int mMhz;
+ private int mKhz;
+
+ /**
+ * The callback used to indicate the user changes the Frequency.
+ */
+ public interface OnFrequencyChangedListener {
+
+ /**
+ * @param view The view associated with this listener.
+ * @param frequency The Frequency that was set.
+ */
+ void onFrequencyChanged(FrequencyPicker view, int frequency);
+ }
+
+ public FrequencyPicker(Context context) {
+ this(context, null);
+ }
+
+ public FrequencyPicker(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FrequencyPicker(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.frequency_picker, this, true);
+
+ mMHzPicker = (NumberPicker) findViewById(R.id.mhz);
+ if (mMHzPicker != null) {
+ mMHzPicker.setSpeed(100);
+ mMHzPicker.setOnChangeListener(new OnChangedListener() {
+ public void onChanged(NumberPicker picker, int oldVal, int newVal) {
+ mMhz = newVal;
+ mFrequency = (mMhz * 1000) + (getFrequencyKHz(mKhz, mMin, mStep));
+ validateFrequencyRange();
+ if (mOnFrequencyChangedListener != null) {
+ mOnFrequencyChangedListener.onFrequencyChanged(FrequencyPicker.this, mFrequency);
+ }
+ }
+ });
+ }
+ mKHzPicker = (NumberPicker) findViewById(R.id.khz);
+ if (mKHzPicker != null) {
+ mKHzPicker.setSpeed(100);
+ mKHzPicker.setOnChangeListener(new OnChangedListener() {
+ public void onChanged(NumberPicker picker, int oldVal, int newVal) {
+ mKhz = newVal;
+ mFrequency = (mMhz * 1000) + (getFrequencyKHz(mKhz, mMin, mStep));
+
+ validateFrequencyRange();
+
+ if (mOnFrequencyChangedListener != null) {
+ mOnFrequencyChangedListener.onFrequencyChanged(FrequencyPicker.this, mFrequency);
+ }
+ }
+ });
+ }
+
+ updateSpinnerRange();
+
+ if (!isEnabled()) {
+ setEnabled(false);
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (mMHzPicker != null) {
+ mMHzPicker.setEnabled(enabled);
+ }
+ if (mKHzPicker != null) {
+ mKHzPicker.setEnabled(enabled);
+ }
+ }
+
+
+ public void updateFrequency(int frequency) {
+ mFrequency = frequency;
+ updateSpinners();
+ }
+
+ private static class SavedState extends BaseSavedState {
+
+ private final int mMHZ;
+ private final int mKHZ;
+
+ /**
+ * Constructor called from {@link FrequencyPicker#onSaveInstanceState()}
+ */
+ private SavedState(Parcelable superState, int mhz, int khz) {
+ super(superState);
+ mMHZ = mhz;
+ mKHZ = khz;
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ mMHZ = in.readInt();
+ mKHZ = in.readInt();
+ }
+
+ public int getMHz() {
+ return mMHZ;
+ }
+
+ public int getKHz() {
+ return mKHZ;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mMHZ);
+ dest.writeInt(mKHZ);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Creator<SavedState>() {
+
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+
+ /**
+ * Override so we are in complete control of save / restore for this widget.
+ */
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ dispatchThawSelfOnly(container);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ return new SavedState(superState, mMhz, mKhz);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ mMhz = ss.getMHz();
+ mKhz = ss.getKHz();
+ }
+
+ private String[] getKHzStrings(int min, int stepSize)
+ {
+ if(stepSize == 100)
+ {
+ return (get100KHzStrings());
+ }
+ else if(stepSize == 50)
+ {
+ return (get50KHzStrings());
+ }
+ return (get200KHzStrings(min));
+ }
+
+ private int getKHzCount(int stepSize)
+ {
+ if(stepSize == 100)
+ {
+ return (10);
+ }
+ else if(stepSize == 50)
+ {
+ return (20);
+ }
+ return (5);
+ }
+
+ private int getCurrentKHz(int frequency, int min, int stepSize)
+ {
+ if(stepSize == 100)
+ {
+ return (getCurrent100KHz(frequency));
+ }
+ else if(stepSize == 50)
+ {
+ return (getCurrent50KHz(frequency));
+ }
+ return (getCurrent200KHz(frequency, min));
+ }
+
+ private int getFrequencyKHz(int kHz, int min, int stepSize)
+ {
+ if(stepSize == 100)
+ {
+ return (getFrequency100KHz(kHz));
+ }
+ else if(stepSize == 50)
+ {
+ return (getFrequency50KHz(kHz));
+ }
+ return (getFrequency200KHz(kHz, min));
+ }
+
+ private int getFrequency100KHz(int kHz)
+ {
+ int frequencykhz = ((kHz - 1) * 100);
+ return (frequencykhz);
+ }
+
+ private int getFrequency50KHz(int kHz)
+ {
+ int frequencykhz = ((kHz - 1) * 50);
+ return (frequencykhz);
+ }
+
+ private int getFrequency200KHz(int kHz, int min)
+ {
+ int frequencykhz = ((kHz - 1) * 200);
+ if((min % 200) != 0)
+ {
+ frequencykhz = ((kHz - 1) * 200) + 100;
+ }
+ return (frequencykhz);
+ }
+
+ private int getCurrent100KHz(int frequency)
+ {
+ int khz = ((frequency % 1000) / 100);
+ return (khz + 1);
+ }
+
+ private int getCurrent50KHz(int frequency)
+ {
+ int khz = ((frequency % 1000) / 50);
+ return (khz + 1);
+ }
+
+ private int getCurrent200KHz(int frequency, int min)
+ {
+ int khz = ((frequency % 1000) / 200);
+ return (khz + 1);
+ }
+
+ private String[] get50KHzStrings()
+ {
+ String[] khzStrings= {"00","05","10","15","20","25","30","35","40","45",
+ "50","55","60","65","70","75","80","85","90","95"};
+ return khzStrings;
+ }
+
+ private String[] get100KHzStrings()
+ {
+ String[] khzStrings= {"0","1","2","3","4","5",
+ "6","7","8","9"};
+ return khzStrings;
+ }
+
+ private String[] get200KHzStrings(int min)
+ {
+ if((min % 200) == 0)
+ {
+ return (getEven200KHzStrings());
+ }
+ return (getOdd200KHzStrings());
+ }
+
+ private String[] getEven200KHzStrings()
+ {
+ String[] khzStrings= {"0","2","4","6","8"};
+ return khzStrings;
+ }
+ private String[] getOdd200KHzStrings()
+ {
+ String[] khzStrings= {"1","3","5","7","9"};
+ return khzStrings;
+ }
+
+ /**
+ * Initialize the state.
+ * @param year The initial year.
+ * @param monthOfYear The initial month.
+ * @param dayOfMonth The initial day of the month.
+ * @param onDateChangedListener How user is notified date is changed by user, can be null.
+ */
+ public void init(int min, int max, int step, int frequency,
+ OnFrequencyChangedListener onFrequencyChangedListener) {
+
+ mMin = min;
+ mMax = max;
+ mStep = step;
+ mFrequency = frequency;
+ mOnFrequencyChangedListener = onFrequencyChangedListener;
+
+ updateSpinners();
+ }
+ private void updateSpinnerRange() {
+ String[] khzStrings = getKHzStrings(mMin, mStep);
+ int khzNumSteps = getKHzCount(mStep);
+
+ if (mMHzPicker != null) {
+ mMHzPicker.setRange(mMin / 1000, mMax / 1000);
+ }
+ if (mKHzPicker != null) {
+ mKHzPicker.setRange(1, khzNumSteps, khzStrings);
+ }
+ }
+
+ private void updateSpinners() {
+ int khzNumSteps = getKHzCount(mStep);
+ updateSpinnerRange();
+ mMhz = (int)(mFrequency / 1000);
+ mKhz = getCurrentKHz(mFrequency, mMin, mStep) ;
+ if((mMin / 1000 <= mMhz) && (mMax / 1000 >= mMhz))
+ {
+ mMHzPicker.setCurrent(mMhz);
+ }
+ if(mKhz <= khzNumSteps)
+ {
+ mKHzPicker.setCurrent(mKhz);
+ }
+ }
+ private void validateFrequencyRange()
+ {
+ boolean bUpdateSpinner = false;
+ if(mFrequency < mMin)
+ {
+ mFrequency = mMin;
+ bUpdateSpinner = true;
+ }
+ if(mFrequency > mMax)
+ {
+ mFrequency = mMax;
+ bUpdateSpinner = true;
+ }
+ if(bUpdateSpinner == true)
+ {
+ updateSpinners();
+ }
+ }
+
+ public int getFrequency() {
+ return (mFrequency);
+ }
+}
diff --git a/fmapp2/src/com/caf/utils/FrequencyPickerDialog.java b/fmapp2/src/com/caf/utils/FrequencyPickerDialog.java
new file mode 100644
index 0000000..3c6168b
--- /dev/null
+++ b/fmapp2/src/com/caf/utils/FrequencyPickerDialog.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution
+ *
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.caf.utils;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import qcom.fmradio.FmConfig;
+import qcom.fmradio.FmReceiver;
+
+import com.caf.fmradio.R;
+import com.caf.utils.FrequencyPicker.OnFrequencyChangedListener;
+
+/**
+ * A simple dialog containing an FrequencyPicker.
+ */
+public class FrequencyPickerDialog extends AlertDialog implements OnClickListener,
+OnFrequencyChangedListener {
+
+ private static final String FREQUENCY = "FREQUENCY";
+ private static final String FREQ_MIN = "FREQ_MIN";
+ private static final String FREQ_MAX = "FREQ_MAX";
+ private static final String FREQ_STEP = "FREQ_STEP";
+
+ private final FrequencyPicker mFrequencyPicker;
+ private final OnFrequencySetListener mCallBack;
+ private int mMinFrequency;
+ private int mMaxFrequency;
+ private int mChannelSpacing;
+
+ /**
+ * The callback used to indicate the user is done filling in the date.
+ */
+ public interface OnFrequencySetListener {
+
+ void onFrequencySet(FrequencyPicker view, int frequency);
+ }
+ /**
+ */
+ public FrequencyPickerDialog(Context context,
+ FmConfig fmConfig,
+ int frequency,
+ OnFrequencySetListener callback) {
+ //this(context, android.R.style.Theme_Dialog, fmConfig, frequency, callback);
+ this(context, com.android.internal.R.style.Theme_Dialog_Alert, fmConfig, frequency, callback);
+ }
+
+ /**
+ */
+ public FrequencyPickerDialog(Context context,
+ int theme,
+ FmConfig fmConfig,
+ int frequency,
+ OnFrequencySetListener callback) {
+ super(context, theme);
+ mMinFrequency = fmConfig.getLowerLimit();
+ mMaxFrequency = fmConfig.getUpperLimit();
+ mChannelSpacing = 200;
+ if(FmReceiver.FM_CHSPACE_200_KHZ == fmConfig.getChSpacing())
+ {
+ mChannelSpacing = 200;
+ }
+ else if(FmReceiver.FM_CHSPACE_100_KHZ == fmConfig.getChSpacing())
+ {
+ mChannelSpacing = 100;
+ }
+ else if(FmReceiver.FM_CHSPACE_50_KHZ == fmConfig.getChSpacing())
+ {
+ mChannelSpacing = 50;
+ }
+ int MHz = frequency/1000;
+ int KHz = (frequency%1000)/100;
+ setTitle("FM - "+MHz+"."+KHz);
+ mCallBack = callback;
+
+ setButton("Set", this);
+ setButton2("Cancel", (OnClickListener) null);
+ setIcon(R.drawable.alert_dialog_icon);
+
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.frequency_picker_dialog, null);
+ setView(view);
+ mFrequencyPicker = (FrequencyPicker) view.findViewById(R.id.frequencyPicker);
+ if(mFrequencyPicker != null)
+ {
+ mFrequencyPicker.init(mMinFrequency, mMaxFrequency, mChannelSpacing, frequency, this);
+ }
+ else
+ {
+ Log.e("fmRadio", "Failed to find ID: R.id.frequencyPicker");
+ }
+ }
+
+ public void UpdateFrequency(int frequency)
+ {
+ int MHz = frequency/1000;
+ int KHz = (frequency%1000)/100;
+ setTitle("FM - "+MHz+"."+KHz);
+ mFrequencyPicker.updateFrequency(frequency);
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (mCallBack != null) {
+ mFrequencyPicker.clearFocus();
+ int frequency = mFrequencyPicker.getFrequency();
+ mCallBack.onFrequencySet(mFrequencyPicker, frequency);
+ }
+ }
+
+ public void onFrequencyChanged(FrequencyPicker view, int frequency) {
+ UpdateFrequency(frequency);
+ }
+
+ @Override
+ public Bundle onSaveInstanceState() {
+ Bundle state = super.onSaveInstanceState();
+ state.putInt(FREQUENCY, mFrequencyPicker.getFrequency());
+ state.putInt(FREQ_MIN, mMinFrequency);
+ state.putInt(FREQ_MAX, mMaxFrequency);
+ state.putInt(FREQ_STEP, mChannelSpacing);
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ int frequency = savedInstanceState.getInt(FREQUENCY);
+ mMinFrequency = savedInstanceState.getInt(FREQ_MIN);
+ mMaxFrequency = savedInstanceState.getInt(FREQ_MAX );
+ mChannelSpacing = savedInstanceState.getInt(FREQ_STEP);
+ mFrequencyPicker.init(mMinFrequency, mMaxFrequency, mChannelSpacing, frequency, this);
+ int MHz = frequency/1000;
+ int KHz = (frequency%1000)/100;
+ setTitle("FM - "+MHz+"."+KHz);
+ }
+}