summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnurag Gupta <anurag.gupta@stericsson.com>2012-07-18 16:18:35 +0530
committerGerrit Code Review <gerrit@review.cyanogenmod.com>2013-02-09 10:39:42 -0800
commit44f2f8d10e87fd315abeb267a3159f2e00ce8e73 (patch)
tree538615c69de88d72173a6e1683744288a9e34978
parentc26fe2aa66679a2f64d9fb8fd28d7149293fe421 (diff)
downloadframeworks_base-44f2f8d10e87fd315abeb267a3159f2e00ce8e73.zip
frameworks_base-44f2f8d10e87fd315abeb267a3159f2e00ce8e73.tar.gz
frameworks_base-44f2f8d10e87fd315abeb267a3159f2e00ce8e73.tar.bz2
FM Radio: Add support for FM Radio in Android
Creating interface and framework for using FM Radio RX and TX from different vendors. Signed-off-by: Christian Bejram <christian.bejram@stericsson.com> Change-Id: I1a71aed01bfffdddfabf1cdfbfa3707cb1ed016b Conflicts: core/java/android/app/ContextImpl.java
-rwxr-xr-x[-rw-r--r--]Android.mk21
-rw-r--r--core/java/android/app/ContextImpl.java21
-rw-r--r--core/java/android/content/pm/PackageManager.java16
-rw-r--r--core/res/AndroidManifest.xml16
-rwxr-xr-xcore/res/res/values/strings.xml12
-rw-r--r--data/etc/com.stericsson.hardware.fm.receiver.xml20
-rw-r--r--data/etc/com.stericsson.hardware.fm.transmitter.xml20
-rw-r--r--fmradio/include/android_fmradio.h203
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/FmBand.aidl22
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/FmBand.java258
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/FmReceiver.java1327
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/FmReceiverImpl.java1126
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/FmReceiverService.java1347
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/FmTransmitter.java807
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/FmTransmitterImpl.java703
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/FmTransmitterService.java846
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IFmReceiver.aidl84
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IFmTransmitter.aidl64
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnAutomaticSwitchListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnBlockScanListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnErrorListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnExtraCommandListener.aidl30
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnForcedPauseListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnForcedResetListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnRDSDataFoundListener.aidl30
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnScanListener.aidl29
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnSignalStrengthListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnStartedListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnStateChangedListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/IOnStereoListener.aidl28
-rw-r--r--fmradio/java/com/stericsson/hardware/fm/package.html13
-rw-r--r--fmradio/jni/Android.mk41
-rwxr-xr-xfmradio/jni/android_fmradio.cpp1188
-rwxr-xr-xfmradio/jni/android_fmradio_Receiver.cpp1494
-rwxr-xr-xfmradio/jni/android_fmradio_Transmitter.cpp1098
-rw-r--r--services/java/com/android/server/SystemServer.java17
36 files changed, 11100 insertions, 5 deletions
diff --git a/Android.mk b/Android.mk
index 61a17f1..6720ae9 100644..100755
--- a/Android.mk
+++ b/Android.mk
@@ -26,8 +26,6 @@ LOCAL_PATH := $(call my-dir)
# TODO: find a more appropriate way to do this.
framework_res_source_path := APPS/framework-res_intermediates/src
-# the library
-# ============================================================
include $(CLEAR_VARS)
# FRAMEWORKS_BASE_SUBDIRS comes from build/core/pathmap.mk
@@ -225,7 +223,21 @@ LOCAL_SRC_FILES += \
wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
voip/java/android/net/sip/ISipSession.aidl \
voip/java/android/net/sip/ISipSessionListener.aidl \
- voip/java/android/net/sip/ISipService.aidl
+ voip/java/android/net/sip/ISipService.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IFmReceiver.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IFmTransmitter.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnStateChangedListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnStartedListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnErrorListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnScanListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnForcedPauseListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnForcedResetListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnBlockScanListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnRDSDataFoundListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnSignalStrengthListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnStereoListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnExtraCommandListener.aidl \
+ fmradio/java/com/stericsson/hardware/fm/IOnAutomaticSwitchListener.aidl
#
@@ -558,7 +570,7 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:=$(framework_docs_LOCAL_API_CHECK_SRC_FILES)
LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
-LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
+LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES) framework
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
@@ -735,7 +747,6 @@ LOCAL_DX_FLAGS := --core-library
include $(BUILD_JAVA_LIBRARY)
-
# Include subdirectory makefiles
# ============================================================
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7439426..e247939 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -126,6 +126,13 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
+import com.stericsson.hardware.fm.IFmReceiver;
+import com.stericsson.hardware.fm.IFmTransmitter;
+import com.stericsson.hardware.fm.FmReceiver;
+import com.stericsson.hardware.fm.FmTransmitter;
+import com.stericsson.hardware.fm.FmReceiverImpl;
+import com.stericsson.hardware.fm.FmTransmitterImpl;
+
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
super(base);
@@ -552,6 +559,20 @@ class ContextImpl extends Context {
public Object createService(ContextImpl ctx) {
return WimaxHelper.createWimaxService(ctx, ctx.mMainThread.getHandler());
}});
+
+ registerService("fm_receiver", new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService("fm_receiver");
+ IFmReceiver service = IFmReceiver.Stub.asInterface(b);
+ return new FmReceiverImpl(service);
+ }});
+
+ registerService("fm_transmitter", new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService("fm_transmitter");
+ IFmTransmitter service = IFmTransmitter.Stub.asInterface(b);
+ return new FmTransmitterImpl(service);
+ }});
}
static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ba85efe..eb6e640 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -858,6 +858,22 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is able to receive FM radio.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_RADIO_FM_RECEIVER = "com.stericsson.hardware.fm.receiver";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is able to transmit FM radio.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_RADIO_FM_TRANSMITTER = "com.stericsson.hardware.fm.transmitter";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports one or more methods of
* reporting current location.
*/
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7b1543f..47a617c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -755,6 +755,22 @@
android:label="@string/permlab_vibrate"
android:description="@string/permdesc_vibrate" />
+ <!-- Allows access to the FM Radio receiver
+ @hide Pending API council approval -->
+ <permission android:name="com.stericsson.permission.FM_RADIO_RECEIVER"
+ android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
+ android:protectionLevel="normal"
+ android:label="@string/permlab_fm_radio_receiver"
+ android:description="@string/permdesc_fm_radio_receiver" />
+
+ <!-- Allows access to the FM Radio transmitter
+ @hide Pending API council approval -->
+ <permission android:name="com.stericsson.permission.FM_RADIO_TRANSMITTER"
+ android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_fm_radio_transmitter"
+ android:description="@string/permdesc_fm_radio_transmitter" />
+
<!-- Allows access to the flashlight -->
<permission android:name="android.permission.FLASHLIGHT"
android:permissionGroup="android.permission-group.AFFECTS_BATTERY"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ec90b4a..9e58df6 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1448,6 +1448,18 @@
<string name="permdesc_vibrate">Allows the app to control the vibrator.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_fm_radio_receiver">control FM receiver</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_fm_radio_receiver">Allows the application to control
+ the FM receiver.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_fm_radio_transmitter">control FM transmitter</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_fm_radio_transmitter">Allows the application to control
+ the FM transmitter.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_flashlight">control flashlight</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_flashlight">Allows the app to control the flashlight.</string>
diff --git a/data/etc/com.stericsson.hardware.fm.receiver.xml b/data/etc/com.stericsson.hardware.fm.receiver.xml
new file mode 100644
index 0000000..13092fa
--- /dev/null
+++ b/data/etc/com.stericsson.hardware.fm.receiver.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- This is the standard feature indicating that the device includes FM receiver. -->
+<permissions>
+ <feature name="com.stericsson.hardware.fm.receiver" />
+</permissions>
diff --git a/data/etc/com.stericsson.hardware.fm.transmitter.xml b/data/etc/com.stericsson.hardware.fm.transmitter.xml
new file mode 100644
index 0000000..9b51c03
--- /dev/null
+++ b/data/etc/com.stericsson.hardware.fm.transmitter.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- This is the standard feature indicating that the device includes FM transmitter. -->
+<permissions>
+ <feature name="com.stericsson.hardware.fm.transmitter" />
+</permissions>
diff --git a/fmradio/include/android_fmradio.h b/fmradio/include/android_fmradio.h
new file mode 100644
index 0000000..18518b6
--- /dev/null
+++ b/fmradio/include/android_fmradio.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: johan.xj.palmaeus@stericsson.com for ST-Ericsson
+ */
+
+/*
+ * Internal stuff for android_fmradio(_Receiver/_Transmitter).cpp
+ */
+
+#ifndef ANDROID_FMRADIO_H
+#define ANDROID_FMRADIO_H
+
+#include "jni.h"
+#include "fmradio.h"
+
+enum FmRadioState_t {
+ FMRADIO_STATE_IDLE,
+ FMRADIO_STATE_STARTING,
+ FMRADIO_STATE_STARTED,
+ FMRADIO_STATE_PAUSED,
+ FMRADIO_STATE_SCANNING,
+ FMRADIO_STATE_EXTRA_COMMAND,
+ /* sum up */
+ FMRADIO_NUMBER_OF_STATES
+};
+
+enum FmRadioCommand_t {
+ FMRADIO_EVENT_START,
+ FMRADIO_EVENT_START_ASYNC,
+ FMRADIO_EVENT_PAUSE,
+ FMRADIO_EVENT_RESUME,
+ FMRADIO_EVENT_RESET,
+ FMRADIO_EVENT_GET_FREQUENCY,
+ FMRADIO_EVENT_SET_FREQUENCY,
+ FMRADIO_EVENT_SET_PARAMETER,
+ FMRADIO_EVENT_STOP_SCAN,
+ FMRADIO_EVENT_EXTRA_COMMAND,
+ /* RX Only */
+ FMRADIO_EVENT_GET_PARAMETER,
+ FMRADIO_EVENT_GET_SIGNAL_STRENGTH,
+ FMRADIO_EVENT_SCAN,
+ FMRADIO_EVENT_FULL_SCAN,
+ /* TX Only */
+ FMRADIO_EVENT_BLOCK_SCAN,
+ /* sum up */
+ FMRADIO_NUMBER_OF_EVENTS
+};
+
+enum RadioMode_t {
+ FMRADIO_RX,
+ FMRADIO_TX
+};
+
+typedef bool ValidEventsForStates_t[FMRADIO_NUMBER_OF_EVENTS]
+ [FMRADIO_NUMBER_OF_STATES];
+
+struct FmRadioCallbacks_t {
+ void (*onStateChanged) (int, int);
+ void (*onError) (void);
+ void (*onStarted) (void);
+ void (*onScan) (int, int, int, bool); /* RX only */
+ void (*onFullScan) (int, int *, int *, bool); /* RX only */
+ void (*onBlockScan) (int, int *, int *, bool); /* TX only */
+ void (*onForcedReset) (enum fmradio_reset_reason_t reason);
+ void (*onSendExtraCommand) (char*, struct fmradio_extra_command_ret_item_t *);
+};
+
+struct bundle_descriptor_offsets_t {
+ jclass mClass;
+ jmethodID mConstructor;
+ jmethodID mGetInt;
+ jmethodID mGetIntArray;
+ jmethodID mGetShort;
+ jmethodID mGetShortArray;
+ jmethodID mGetString;
+ jmethodID mContainsKey;
+ jmethodID mSize;
+ jmethodID mKeySet;
+ jmethodID mPutInt;
+ jmethodID mPutShort;
+ jmethodID mPutIntArray;
+ jmethodID mPutShortArray;
+ jmethodID mPutString;
+};
+
+struct FmSession_t {
+ // vendor specific data, we do not know about this type
+ void *vendorData_p;
+ void *fmLibrary_p;
+ bool isRegistered;
+ enum FmRadioState_t state;
+ struct fmradio_vendor_methods_t *vendorMethods_p;
+ const ValidEventsForStates_t *validEventsForStates_p;
+ const struct FmRadioCallbacks_t *callbacks_p;
+ JavaVM *jvm_p;
+ jobject jobj;
+ struct FmSession_t *partnerSession_p;
+ struct bundle_descriptor_offsets_t *bundleOffsets_p;
+ enum FmRadioState_t oldState; /* used when scanning */
+ bool lastScanAborted; /* used when scanning */
+ bool pendingPause; /* used when scanning & asyncStarting */
+ bool ongoingReset; /* used during reset while waiting */
+ pthread_mutex_t *dataMutex_p; /* data access to this struct */
+ pthread_cond_t sync_cond;
+ struct ThreadCtrl_t *signalStrengthThreadCtrl_p; /* RX Only */
+};
+
+#define FMRADIO_SET_STATE(_session_p,_newState) {int _oldState = (_session_p)->state; (_session_p)->state = _newState;(_session_p)->callbacks_p->onStateChanged(_oldState, _newState);}
+
+/* exceptions */
+
+#define THROW_ILLEGAL_ARGUMENT(_session_p) \
+ androidFmRadioThrowException(_session_p,\
+ "java/lang/IllegalArgumentException",\
+ "Illegal argument", __FILE__, __LINE__,\
+ __FUNCTION__)
+#define THROW_UNSUPPORTED_OPERATION(_session_p) \
+ androidFmRadioThrowException(_session_p,\
+ "java/lang/UnsupportedOperationException",\
+ "Unsupported operation", __FILE__, __LINE__,\
+ __FUNCTION__)
+#define THROW_INVALID_STATE(_session_p) \
+ androidFmRadioThrowException(_session_p,\
+ "java/lang/IllegalStateException",\
+ "State is invalid", __FILE__, __LINE__,\
+ __FUNCTION__)
+#define THROW_IO_ERROR(_session_p) \
+ androidFmRadioThrowException(_session_p,\
+ "java/io/IOException",\
+ "IO Exception", __FILE__, __LINE__,\
+ __FUNCTION__)
+
+
+#define FM_LIBRARY_NAME_MAX_LENGTH 128
+
+#define THREAD_WAIT_TIMEOUT_S 2
+
+#define SIGNAL_STRENGTH_MAX 1000
+#define SIGNAL_STRENGTH_UNKNOWN -1
+
+extern pthread_mutex_t rx_tx_common_mutex;
+
+jobject extraCommandRetList2Bundle(JNIEnv * env_p, struct bundle_descriptor_offsets_t
+ *bundleOffsets_p,
+ struct fmradio_extra_command_ret_item_t *itemList);
+
+void freeExtraCommandRetList(struct extra_command_ret_item_t *itemList);
+
+void androidFmRadioTempResumeIfPaused(struct FmSession_t *session_p);
+
+void androidFmRadioPauseIfTempResumed(struct FmSession_t *session_p);
+
+bool androidFmRadioIsValidEventForState(struct FmSession_t *session_p,
+ enum FmRadioCommand_t event);
+
+void androidFmRadioThrowException(struct FmSession_t *session_p,
+ const char *exception,
+ const char *message, const char *file,
+ int line, const char *function);
+
+bool androidFmRadioLoadFmLibrary(struct FmSession_t *session_p,
+ enum RadioMode_t mode);
+
+void androidFmRadioUnLoadFmLibrary(struct FmSession_t *session_p);
+
+int
+androidFmRadioStart(struct FmSession_t *session_p, enum RadioMode_t mode,
+ const struct fmradio_vendor_callbacks_t *callbacks,
+ bool async, int lowFreq, int highFreq, int defaultFreq,
+ int grid);
+
+int androidFmRadioPause(struct FmSession_t *session_p);
+
+int androidFmRadioResume(struct FmSession_t *session_p);
+
+int androidFmRadioReset(struct FmSession_t *session_p);
+
+void androidFmRadioSetFrequency(struct FmSession_t *session_p,
+ int frequency);
+
+int androidFmRadioGetFrequency(struct FmSession_t *session_p);
+
+void androidFmRadioStopScan(struct FmSession_t *session_p);
+
+void
+androidFmRadioSendExtraCommand(struct FmSession_t *session_p, JNIEnv * env,
+ jstring command, jobjectArray parameter);
+
+#endif
diff --git a/fmradio/java/com/stericsson/hardware/fm/FmBand.aidl b/fmradio/java/com/stericsson/hardware/fm/FmBand.aidl
new file mode 100644
index 0000000..76def36
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/FmBand.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+parcelable FmBand;
diff --git a/fmradio/java/com/stericsson/hardware/fm/FmBand.java b/fmradio/java/com/stericsson/hardware/fm/FmBand.java
new file mode 100644
index 0000000..f1002f3
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/FmBand.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Bjorn Pileryd (bjorn.pileryd@sonyericsson.com)
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes the properties of the FM frequency band. The frequency band range
+ * and the channel offset vary in different regions. The unit for all
+ * frequencies in this class is kHz.
+ */
+public class FmBand implements Parcelable {
+
+ /**
+ * Default band for US 87.9MHz - 107.9MHz, 200kHz channel offset.
+ */
+ public static final int BAND_US = 0;
+
+ /**
+ * Default band for EU 87.5MHz - 108MHz, 100kHz channel offset.
+ */
+ public static final int BAND_EU = 1;
+
+ /**
+ * Default band for Japan 76MHz - 90MHz, 100kHz channel offset.
+ */
+ public static final int BAND_JAPAN = 2;
+
+ /**
+ * Default band for China 70MHz - 108MHz, 50kHz channel offset.
+ */
+ public static final int BAND_CHINA = 3;
+
+ /**
+ * Default band for EU 87.5MHz - 108MHz, 50kHz channel offset.
+ */
+ public static final int BAND_EU_50K_OFFSET = 4;
+
+ /**
+ * Unknown frequency.
+ */
+ public static final int FM_FREQUENCY_UNKNOWN = -1;
+
+ /**
+ * The lowest frequency of the band.
+ */
+ private int mMinFrequency;
+
+ /**
+ * The highest frequency of the band.
+ */
+ private int mMaxFrequency;
+
+ /**
+ * The default frequency of the band.
+ */
+ private int mDefaultFrequency;
+
+ /**
+ * The offset between the channels in the band.
+ */
+ private int mChannelOffset;
+
+ /**
+ * Creates a band representation.
+ *
+ * @param minFrequency
+ * the lowest frequency of the band in kHz
+ * @param maxFrequency
+ * the highest frequency of the band in kHz
+ * @param channelOffset
+ * the offset between the channels in the band in kHz
+ * @param defaultFrequency
+ * the default frequency that the hardware will tune to at
+ * startup
+ * @throws IllegalArgumentException
+ * if the minFrequency is equal or higher then maxFrequency
+ * @throws IllegalArgumentException
+ * if the defaultFrequency is not within the limits of
+ * minFrequency and maxFrequency
+ * @throws IllegalArgumentException
+ * if the minFrequency or maxFrequency is not a multiplier of channelOffset
+ */
+ public FmBand(int minFrequency, int maxFrequency, int channelOffset, int defaultFrequency) {
+ if (minFrequency >= maxFrequency) {
+ throw new IllegalArgumentException(
+ "Minimum frequency can not be equal or higher than maximum frequency");
+ }
+ if (defaultFrequency < minFrequency) {
+ throw new IllegalArgumentException(
+ "Default frequency can not be less than minFrequency");
+ }
+ if (defaultFrequency > maxFrequency) {
+ throw new IllegalArgumentException(
+ "Default frequency can not be higher than maxFrequency");
+ }
+ if ((maxFrequency - minFrequency) % channelOffset != 0
+ || (defaultFrequency - minFrequency) % channelOffset != 0) {
+ throw new IllegalArgumentException(
+ "Frequency has invalid offset");
+ }
+ this.mMinFrequency = minFrequency;
+ this.mMaxFrequency = maxFrequency;
+ this.mDefaultFrequency = defaultFrequency;
+ this.mChannelOffset = channelOffset;
+ }
+
+ /**
+ * Creates a standard band representation. The default frequency will be the
+ * lowest frequency for the specified band.
+ *
+ * @param band
+ * one of {@link #BAND_US}, {@link #BAND_EU}, {@link #BAND_JAPAN}
+ * , {@link #BAND_CHINA}, {@link #BAND_EU_50K_OFFSET}
+ * @throws IllegalArgumentException
+ * if the band is not one of {@link #BAND_US}, {@link #BAND_EU},
+ * {@link #BAND_JAPAN}, {@link #BAND_CHINA}, {@link #BAND_EU_50K_OFFSET}
+ */
+ public FmBand(int band) {
+ switch (band) {
+ case BAND_US:
+ this.mMinFrequency = 87900;
+ this.mMaxFrequency = 107900;
+ this.mDefaultFrequency = 87900;
+ this.mChannelOffset = 200;
+ break;
+ case BAND_EU:
+ this.mMinFrequency = 87500;
+ this.mMaxFrequency = 108000;
+ this.mDefaultFrequency = 87500;
+ this.mChannelOffset = 100;
+ break;
+ case BAND_JAPAN:
+ this.mMinFrequency = 76000;
+ this.mMaxFrequency = 90000;
+ this.mDefaultFrequency = 76000;
+ this.mChannelOffset = 100;
+ break;
+ case BAND_CHINA:
+ this.mMinFrequency = 70000;
+ this.mMaxFrequency = 108000;
+ this.mDefaultFrequency = 70000;
+ this.mChannelOffset = 50;
+ break;
+ case BAND_EU_50K_OFFSET:
+ this.mMinFrequency = 87500;
+ this.mMaxFrequency = 108000;
+ this.mDefaultFrequency = 87500;
+ this.mChannelOffset = 50;
+ break;
+ default:
+ throw new IllegalArgumentException("Wrong band identifier");
+ }
+ }
+
+ /**
+ * Checks if a frequency is valid to the band. To be valid it must be within
+ * the frequency range and on a frequency with correct channel offset.
+ *
+ * @param frequency
+ * the frequency to validate
+ * @return true if the frequency is valid for this band
+ */
+ public boolean isFrequencyValid(int frequency) {
+ if (frequency < mMinFrequency || frequency > mMaxFrequency) {
+ return false;
+ }
+ if ((frequency - mMinFrequency) % mChannelOffset != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return the lowest frequency of the band.
+ *
+ * @return the lowest frequency of the band in kHz
+ */
+ public int getMinFrequency() {
+ return mMinFrequency;
+ }
+
+ /**
+ * Returns the highest frequency of the band.
+ *
+ * @return the highest frequency of the band in kHz
+ */
+ public int getMaxFrequency() {
+ return mMaxFrequency;
+ }
+
+ /**
+ * Returns the default frequency of the band that the hardware will tune to
+ * at startup.
+ *
+ * @return the default frequency of the band in kHz
+ */
+ public int getDefaultFrequency() {
+ return mDefaultFrequency;
+ }
+
+ /**
+ * Returns the offset between the channels in the band.
+ *
+ * @return the offset between the channels in the band in kHz
+ */
+ public int getChannelOffset() {
+ return mChannelOffset;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMinFrequency);
+ dest.writeInt(mMaxFrequency);
+ dest.writeInt(mChannelOffset);
+ dest.writeInt(mDefaultFrequency);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<FmBand> CREATOR = new Creator<FmBand>() {
+ public FmBand createFromParcel(Parcel in) {
+ int minfreq = in.readInt();
+ int maxfreq = in.readInt();
+ int offset = in.readInt();
+ int defaultFreq = in.readInt();
+ FmBand band = new FmBand(minfreq, maxfreq, offset, defaultFreq);
+ return band;
+ }
+
+ public FmBand[] newArray(int size) {
+ return new FmBand[size];
+ }
+ };
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/FmReceiver.java b/fmradio/java/com/stericsson/hardware/fm/FmReceiver.java
new file mode 100644
index 0000000..1055429
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/FmReceiver.java
@@ -0,0 +1,1327 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Bjorn Pileryd (bjorn.pileryd@sonyericsson.com)
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import java.io.IOException;
+
+/**
+ * The FmReceiver controls reception of FM radio. This API enables an
+ * application to tune/scan for channels, receive RDS data, etc. The unit for
+ * all frequencies in this class is kHz. Note that this API only controls the
+ * reception of FM radio, to play FM radio the MediaPlayer interfaces should be
+ * used, see code example below the state diagram.
+ * <p>
+ * Get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(String)
+ * Context.getSystemService("fm_receiver")}.
+ * </p>
+ * <a name="StateDiagram"></a> <h3>State Diagram</h3>
+ * <p>
+ * The state machine is designed to take into account that some hardware may
+ * need time to prepare, and that it is likely to consume more power when paused
+ * and started than it does in the idle state. The hardware implementation of
+ * this interface should do the time consuming preparation procedures in the
+ * starting state. The switching between paused and started states should be
+ * fast to give a good user experience.
+ * </p>
+ * <p>
+ * <img src="../../../../images/FmReceiver_states.gif"
+ * alt="FmReceiver State diagram" border="0" />
+ * </p>
+ * <table border="1">
+ * <tr>
+ * <th>Method Name</th>
+ * <th>Valid States</th>
+ * <th>Invalid States</th>
+ * <th>Comments</th>
+ * </tr>
+ * <tr>
+ * <td>{@link #startAsync(FmBand)}</td>
+ * <td>{idle}</td>
+ * <td>{starting, paused, started, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the starting state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #start(FmBand)}</td>
+ * <td>{idle}</td>
+ * <td>{starting, paused, started, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the started state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #resume()}</td>
+ * <td>{paused, started}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the started state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #pause()}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the paused state. Calling this method in an invalid state throws an
+ * IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #reset()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>Successful invocation of this method transfers the object to the idle
+ * state, the object is like being just created.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #getState()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #isApiSupported(Context)}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #isRDSDataSupported()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #isTunedToValidChannel()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #setThreshold(int)}</td>
+ * <td>{started, paused, scanning}</td>
+ * <td>{idle, starting}</td>
+ * <td>Calling this method in an invalid state throws an IllegalStateException.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #getThreshold()}</td>
+ * <td>{started, paused, scanning}</td>
+ * <td>{idle, starting}</td>
+ * <td>Calling this method in an invalid state throws an IllegalStateException.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #getFrequency()}</td>
+ * <td>{paused, started}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state does not change the
+ * object state. Calling this method in an invalid state throws an
+ * IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #getSignalStrength()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #isPlayingInStereo()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #setForceMono(boolean)}</td>
+ * <td>{started, paused, scanning}</td>
+ * <td>{idle, starting}</td>
+ * <td>Calling this method in an invalid state throws an IllegalStateException.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #setAutomaticAFSwitching(boolean)}</td>
+ * <td>{started, paused, scanning}</td>
+ * <td>{idle, starting}</td>
+ * <td>Calling this method in an invalid state throws an IllegalStateException.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #setAutomaticTASwitching(boolean)}</td>
+ * <td>{started, paused, scanning}</td>
+ * <td>{idle, starting}</td>
+ * <td>Calling this method in an invalid state throws an IllegalStateException.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #setFrequency(int)}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state does not change the
+ * object state. Calling this method in an invalid state throws an
+ * IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #startFullScan()}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the scanning state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #scanUp()}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the scanning state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #scanDown()}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the scanning state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #stopScan()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>Successful invocation of this method in a valid state tries to stop
+ * performing a scan operation. The hardware might continue the scan for an
+ * unspecified amount of time after this method is called. Once the scan has
+ * stopped, it will be notified via {@link OnScanListener}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #sendExtraCommand(String, String[])}</td>
+ * <td>vendor specific</td>
+ * <td>vendor specific</td>
+ * <td>vendor specific</td>
+ * </tr>
+ * </table>
+ * <a name="Examples"></a> <h3>Example code</h3>
+ * <pre>
+ * // start receiving FM radio
+ * FmReceiver fmr = (FmReceiver) getSystemService("fm_receiver");
+ * fmr.start(new FmBand(FmBand.BAND_EU));
+ *
+ * // prepare and start playback
+ * MediaPlayer mp = new MediaPlayer();
+ * mp.setDataSource(&quot;fmradio://rx&quot;);
+ * mp.prepare();
+ * mp.start();
+ * </pre>
+ * <a name="FMHandling"></a> <h3>FM receiving/transmission handling</h3>
+ * <p>
+ * In this API, FM radio cannot be received and transmitted at the same time,
+ * therefore the state machine is designed to prevent incorrect usage. The
+ * FmReceiver and FmTransmitter has a separate state machine and only one can be
+ * <i>active</i> (state other than idle).
+ * <ul>
+ * <li>If start is called on FmReceiver and the FmTransmitter is <i>active</i>,
+ * the FmTransmitter MUST release resources and change state to idle.</li>
+ * <li>The FmTransmitter will in that case be notified by
+ * {@link com.stericsson.hardware.fm.FmTransmitter.OnForcedResetListener#onForcedReset(int)}.</li>
+ * </ul>
+ * </p>
+ * <a name="RDSHandling"></a> <h3>Receiving/transmitting RDS data</h3>
+ * <p>
+ * RDS data can be received by setting the
+ * {@link #addOnRDSDataFoundListener(OnRDSDataFoundListener)}. When RDS data is
+ * available the data can be extracted from the Bundle object in
+ * {@link OnRDSDataFoundListener#onRDSDataFound(Bundle, int)} according to the
+ * table below. This table can also be used when transmitting RDS data with the
+ * FmTransmitter.
+ * </p>
+ * <table border="1">
+ * <tr>
+ * <th>RDS description</th>
+ * <th>key name</th>
+ * <th>value type</th>
+ * <th>value description</th>
+ * </tr>
+ * <tr>
+ * <td>Program Identification code</td>
+ * <td>PI</td>
+ * <td>short</td>
+ * <td>N/A</td>
+ * </tr>
+ * <tr>
+ * <td>Traffic Program Identification code</td>
+ * <td>TP</td>
+ * <td>short</td>
+ * <td>1 bit</td>
+ * </tr>
+ * <tr>
+ * <td>Program Type code</td>
+ * <td>PTY</td>
+ * <td>short</td>
+ * <td>5 bits</td>
+ * </tr>
+ * <tr>
+ * <td>Traffic Announcement code</td>
+ * <td>TA</td>
+ * <td>short</td>
+ * <td>1 bit</td>
+ * </tr>
+ * <tr>
+ * <td>Music/Speech switch code</td>
+ * <td>M/S</td>
+ * <td>short</td>
+ * <td>1 bit</td>
+ * </tr>
+ * <tr>
+ * <td>Alternative Frequency</td>
+ * <td>AF</td>
+ * <td>int[]</td>
+ * <td>kHz</td>
+ * </tr>
+ * <tr>
+ * <td>Program service name</td>
+ * <td>PSN</td>
+ * <td>string</td>
+ * <td>8 chars</td>
+ * </tr>
+ * <tr>
+ * <td>Radio text</td>
+ * <td>RT</td>
+ * <td>string</td>
+ * <td>64 chars</td>
+ * </tr>
+ * <tr>
+ * <td>Clock-time and date</td>
+ * <td>CT</td>
+ * <td>string</td>
+ * <td>Yr:mo:dy:hr:min</td>
+ * </tr>
+ * <tr>
+ * <td>Program Type name</td>
+ * <td>PTYN</td>
+ * <td>string</td>
+ * <td>8 chars</td>
+ * </tr>
+ * <tr>
+ * <td>Traffic Message Channel</td>
+ * <td>TMC</td>
+ * <td>short[]</td>
+ * <td>X:Y:Z -> 5+16+16 bits</td>
+ * </tr>
+ * <tr>
+ * <td>TA Frequency</td>
+ * <td>TAF</td>
+ * <td>int</td>
+ * <td>kHz</td>
+ * </tr>
+ * </table>
+ * <p>
+ * The RDS specification can be found <a
+ * href="http://www.rds.org.uk/rds98/pdf/IEC%2062106-E_no%20print.pdf">here</a>
+ * </p>
+ * <a name="ErrorHandling"></a> <h3>Error handling</h3>
+ * <p>
+ * In general, it is up to the application that uses this API to keep track of
+ * events that could affect the FM radio user experience. The hardware
+ * implementation side of this API should only take actions when it is really
+ * necessary, e.g. if the hardware is forced to pause or reset, and notify the
+ * application by using the {@link OnForcedPauseListener},
+ * {@link OnForcedResetListener} or {@link OnErrorListener}.
+ * </p>
+ */
+public abstract class FmReceiver {
+
+ /**
+ * The FmReceiver had to be shut down due to a non-critical error, meaning
+ * that it is OK to attempt a restart immediately after this. For example,
+ * if the hardware was shut down in order to save power after being in the
+ * paused state for too long.
+ */
+ public static final int RESET_NON_CRITICAL = 0;
+
+ /**
+ * The FmReceiver had to be shut down due to a critical error. The FM
+ * hardware it not guaranteed to work as expected after receiving this
+ * error.
+ */
+ public static final int RESET_CRITICAL = 1;
+
+ /**
+ * The FmTransmitter was activated and therefore the FmReceiver must be put
+ * in idle.
+ *
+ * @see FmTransmitter#startAsync(FmBand)
+ */
+ public static final int RESET_TX_IN_USE = 2;
+
+ /**
+ * The radio is not allowed to be used, typically when flight mode is
+ * enabled.
+ */
+ public static final int RESET_RADIO_FORBIDDEN = 3;
+
+ /**
+ * Indicates that the FmReceiver is in an idle state. No resources are
+ * allocated and power consumption is kept to a minimum.
+ */
+ public static final int STATE_IDLE = 0;
+
+ /**
+ * Indicates that the FmReceiver is allocating resources and preparing to
+ * receive FM radio.
+ */
+ public static final int STATE_STARTING = 1;
+
+ /**
+ * Indicates that the FmReceiver is receiving FM radio. Note that the
+ * FmReceiver is considered to be started even if it is receiving noise or
+ * gets a signal with not good enough quality to consider a valid channel.
+ */
+ public static final int STATE_STARTED = 2;
+
+ /**
+ * Indicates that the FmReceiver has allocated resources and is ready to
+ * instantly receive FM radio.
+ */
+ public static final int STATE_PAUSED = 3;
+
+ /**
+ * Indicates that the FmReceiver is scanning. FM radio will not be received
+ * in this state.
+ */
+ public static final int STATE_SCANNING = 4;
+
+ /**
+ * Unknown signal strength.
+ */
+ public static final int SIGNAL_STRENGTH_UNKNOWN = -1;
+
+ /**
+ * The frequency switch occurred as a stronger alternate frequency was
+ * found.
+ */
+ public static final int SWITCH_AF = 0;
+
+ /**
+ * The frequency switch occurred as there is a traffic announcement
+ * in progress.
+ */
+ public static final int SWITCH_TA = 1;
+
+ /**
+ * The frequency switch occurred at the cessation of a traffic
+ * announcement.
+ */
+ public static final int SWITCH_TA_END = 2;
+
+ /**
+ * Scan direction down towards lower frequencies.
+ */
+ public static final int SCAN_DOWN = 0;
+
+ /**
+ * Scan direction up towards higher frequencies.
+ */
+ public static final int SCAN_UP = 1;
+
+ /**
+ * Returns true if the FM receiver API is supported by the system.
+ */
+ public static boolean isApiSupported(Context context) {
+ return context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_RADIO_FM_RECEIVER);
+ }
+
+ /**
+ * Starts reception of the FM hardware. This is an asynchronous method since
+ * different hardware can have varying startup times. When the reception is
+ * started a callback to {@link OnStartedListener#onStarted()} is made.
+ * <p>
+ * When calling this method, an FmBand parameter must be passed that
+ * describes the properties of the band that the FmReceiver should prepare
+ * for. If the band is null, invalid or not supported, an exception will be
+ * thrown.
+ * </p>
+ * <p>
+ * If the FmTransmitter is active it will be forced to reset. See
+ * {@link FmTransmitter#RESET_RX_IN_USE}.
+ * </p>
+ *
+ * @param band
+ * the band to use
+ * @throws IllegalArgumentException
+ * if the band is null
+ * @throws UnsupportedOperationException
+ * if the band is not supported by the hardware
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to start
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ * @see FmBand
+ */
+ public abstract void startAsync(FmBand band) throws IOException;
+
+ /**
+ * Starts reception of the FM hardware. This is a synchronous method and the
+ * method call will block until the hardware is started.
+ * <p>
+ * When calling this method, an FmBand parameter must be passed that
+ * describes the properties of the band that the FmReceiver should prepare
+ * for. If the band is null, invalid or not supported, an exception will be
+ * thrown.
+ * </p>
+ * <p>
+ * If the FmTransmitter is active it will be forced to reset. See
+ * {@link FmTransmitter#RESET_RX_IN_USE}.
+ * </p>
+ *
+ * @param band
+ * the band to use
+ * @throws IllegalArgumentException
+ * if the band is null
+ * @throws UnsupportedOperationException
+ * if the band is not supported by the hardware
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to start
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ * @see FmBand
+ */
+ public abstract void start(FmBand band) throws IOException;
+
+ /**
+ * Resumes FM reception.
+ * <p>
+ * Calling this method when the FmReceiver is in started state has no
+ * affect.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to resume
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void resume() throws IOException;
+
+ /**
+ * Pauses FM reception. No FM radio is received as long as the FmReceiver is
+ * paused. Call {@link #resume()} to resume reception. The hardware should
+ * be able to resume reception quickly from the paused state to give a good
+ * user experience.
+ * <p>
+ * Note that the hardware provider may choose to turn off the hardware after
+ * being paused a certain amount of time to save power. This will be
+ * reported in {@link OnForcedResetListener#onForcedReset(int)} with reason
+ * {@link #RESET_NON_CRITICAL} and the FmReceiver will be set to the idle
+ * state.
+ * </p>
+ * <p>
+ * Calling this method when the FmReceiver is in paused state has no affect.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to pause
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void pause() throws IOException;
+
+ /**
+ * Resets the FmReceiver to its idle state.
+ *
+ * @throws IOException
+ * if the FM hardware failed to reset
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void reset() throws IOException;
+
+ /**
+ * Returns the state of the FmReceiver.
+ *
+ * @return One of {@link #STATE_IDLE}, {@link #STATE_STARTING},
+ * {@link #STATE_STARTED}, {@link #STATE_PAUSED},
+ * {@link #STATE_SCANNING}
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract int getState();
+
+ /**
+ * Returns true if the hardware/implementation supports RDS data. If true
+ * the {@link OnRDSDataFoundListener} will work. If not it will never report
+ * any data.
+ * <p>
+ * The motivation for having this function is that an application can take
+ * this capability into account when laying out its UI.
+ * </p>
+ *
+ * @return true if RDS data is supported by the FmReceiver, false otherwise
+ *
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract boolean isRDSDataSupported();
+
+ /**
+ * Checks if the tuned frequency is considered to contain a channel.
+ *
+ * @return true if the FmReceiver is tuned to a valid channel
+ *
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract boolean isTunedToValidChannel();
+
+ /**
+ * Sets the threshold for the tuner. The threshold can be 0-1000. A low
+ * threshold indicates that the tuner will find stations with a weak signal
+ * and a high threshold will find stations with a strong signal.
+ * <p>
+ * This is used then calling {@link FmReceiver#scanUp()},
+ * {@link FmReceiver#scanDown()} or {@link FmReceiver#startFullScan()}.
+ * </p>
+ *
+ * @param threshold
+ * a value between 0-1000
+ * @throws IllegalArgumentException
+ * if the value is not between 0-1000
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to set threshold
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void setThreshold(int threshold) throws IOException;
+
+ /**
+ * Returns the threshold for the tuner.
+ *
+ * @return the threshold for the tuner
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to get the threshold
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract int getThreshold() throws IOException;
+
+ /**
+ * Returns the tuned frequency.
+ *
+ * @return the tuned frequency in kHz
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to get the frequency
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract int getFrequency() throws IOException;
+
+ /**
+ * Returns the signal strength of the tuned frequency. The signal strength
+ * is a value from 0 to 1000. A high value indicates a strong signal and a
+ * low value indicates a weak signal.
+ *
+ * @return the signal strength or {@link #SIGNAL_STRENGTH_UNKNOWN}
+ *
+ * @throws IOException
+ * if the FM hardware failed to get the signal strength
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract int getSignalStrength() throws IOException;
+
+ /**
+ * Checks if the tuned frequency is played in stereo. If
+ * {@link #setForceMono(boolean)} is set, this method will always return
+ * false.
+ *
+ * @return true if the tuned frequency is playing in stereo
+ *
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract boolean isPlayingInStereo();
+
+ /**
+ * Force the playback to always be in mono.
+ *
+ * @param forceMono
+ * if true, the hardware will only output mono audio. If false,
+ * stereo is allowed if supported by hardware and signal.
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void setForceMono(boolean forceMono);
+
+ /**
+ * Sets the automatic switching of the FmReceiver in the case of a stronger
+ * transmitter with the same Programme Identification (PI) presence. The
+ * application should register for callbacks using
+ * {@link #addOnAutomaticSwitchListener(OnAutomaticSwitchListener)}
+ * to receive a callback when channels are found. The reason stated in
+ * the callback will be {@link FmReceiver#SWITCH_AF}.
+ *
+ * @param automatic
+ * enable or disable automatic switching
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void setAutomaticAFSwitching(boolean automatic);
+
+ /**
+ * Sets the automatic switching of the program in case of the presence of
+ * traffic announcement in another program. The application should register
+ * for callbacks using {@link #addOnAutomaticSwitchListener(OnAutomaticSwitchListener)}
+ * to receive a callback when channels are found. The reason stated in
+ * the callback will be {@link FmReceiver#SWITCH_TA} when switching to
+ * traffic announcement and {@link FmReceiver#SWITCH_TA_END} when switching
+ * back after the announcement.
+ *
+ * @param automatic
+ * enable or disable automatic switching
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void setAutomaticTASwitching(boolean automatic);
+
+ /**
+ * Sets the frequency. Unlike {@link #scanUp()} and {@link #scanDown()},
+ * this method will directly jump to the specified frequency instead of
+ * trying to find a channel while scanning.
+ * <p>
+ * The frequency must be within the band that the FmReceiver prepared for.
+ * </p>
+ *
+ * @param frequency
+ * the frequency to tune to in kHz
+ * @throws IllegalArgumentException
+ * if the frequency is not supported
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to set frequency
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ * @see FmBand
+ */
+ public abstract void setFrequency(int frequency) throws IOException;
+
+ /**
+ * Starts a full scan. The tuner will scan the entire frequency band for
+ * channels. The application should register for callbacks using
+ * {@link #addOnScanListener(OnScanListener)} to receive a callback when
+ * channels are found.
+ * <p>
+ * If the application wants to stop the full scan, a call to
+ * {@link #stopScan()} should be made.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void startFullScan();
+
+ /**
+ * Starts seeking for a channel downwards in the frequency band from the
+ * currently tuned frequency. When a channel with enough signal strength is
+ * found the scanning will stop.
+ * <p>
+ * The seek will always stop if it reaches back to the frequency it started
+ * from, meaning that in the worst case scenario, when no channel can be
+ * found, the seek will run through one full cycle of the frequency band
+ * and stop at the frequency it started from.
+ * </p>
+ * The application should register for callbacks using
+ * {@link #addOnScanListener(OnScanListener)} to receive a callback when the
+ * scan is complete.
+ * <p>
+ * If the application wants to stop the scan, a call to {@link #stopScan()}
+ * should be made.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ * @see FmReceiver#scanUp()
+ */
+ public abstract void scanDown();
+
+ /**
+ * Same as {@link #scanDown()} but seeks upwards in the frequency band.
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ * @see FmReceiver#scanDown()
+ */
+ public abstract void scanUp();
+
+ /**
+ * Stops performing a scan operation. The hardware might continue the scan
+ * for an unspecified amount of time after this method is called. Once the
+ * scan has stopped, it will be notified via {@link OnScanListener}.
+ * <p>
+ * Note that this method has no affect if called in other states than the
+ * scanning state.
+ * </p>
+ *
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void stopScan();
+
+ /**
+ * This method can be used to send vendor specific commands. These commands
+ * must not follow any common design for all vendors, and information about
+ * the commands that a vendor implements is out of scope in this API.
+ * <p>
+ * However, one command must be supported by all vendors that implements
+ * vendor specific commands, the <i>vendor_information</i> command. In the
+ * Bundle parameter in
+ * {@link OnExtraCommandListener#onExtraCommand(String, Bundle)} the FM
+ * radio device name and version can be extracted according to the table
+ * below.
+ * </p>
+ * <table border="1">
+ * <tr>
+ * <th>key name</th>
+ * <th>value type</th>
+ * </tr>
+ * <tr>
+ * <td>device_name</td>
+ * <td>string</td>
+ * </tr>
+ * <tr>
+ * <td>device_version</td>
+ * <td>string</td>
+ * </tr>
+ * </table>
+ *
+ * @param command
+ * the command to send
+ * @param extras
+ * extra parameters to the command
+ * @return true if the command was accepted, otherwise false
+ *
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract boolean sendExtraCommand(String command, String[] extras);
+
+ /**
+ * Register a callback to be invoked when the FmReceiver is started.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnStartedListener(OnStartedListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmReceiver is started.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnStartedListener(OnStartedListener listener);
+
+ /**
+ * Register a callback to be invoked during a scan.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnScanListener(OnScanListener listener);
+
+ /**
+ * Unregister a callback to be invoked during a scan.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnScanListener(OnScanListener listener);
+
+ /**
+ * Register a callback to be invoked when RDS data is found. Having a
+ * listener registered for this might cause continuous callbacks, so it is
+ * considered good practice to set this listener to null whenever the
+ * application is not interested in these updates, e.g. when the application
+ * UI is not visible.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnRDSDataFoundListener(OnRDSDataFoundListener listener);
+
+ /**
+ * Unregister a callback to be invoked when RDS data is found.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnRDSDataFoundListener(OnRDSDataFoundListener listener);
+
+ /**
+ * Register a callback to be invoked when an error has happened during an
+ * asynchronous operation.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnErrorListener(OnErrorListener listener);
+
+ /**
+ * Unregister a callback to be invoked when an error has happened during an
+ * asynchronous operation.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnErrorListener(OnErrorListener listener);
+
+ /**
+ * Register a callback to be invoked when the signal strength of the
+ * currently tuned frequency changes. Having a listener registered to this
+ * method may cause frequent callbacks, hence it is good practice to only
+ * have a listener registered for this when necessary.
+ * <p>
+ * Example: If the application uses this information to visualize the signal
+ * strength on the UI, it should unregister the listener whenever the UI is
+ * not visible.
+ * </p>
+ * <p>
+ * The listener will only receive callbacks when the signal strength
+ * changes.
+ * </p>
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnSignalStrengthChangedListener(OnSignalStrengthChangedListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the signal strength of the
+ * currently tuned frequency changes.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnSignalStrengthChangedListener(OnSignalStrengthChangedListener listener);
+
+ /**
+ * Register a callback to be invoked when playback of the tuned frequency
+ * changes between mono and stereo. Having a listener registered to this
+ * method may cause frequent callbacks, hence it is good practice to only
+ * have a listener registered for this when necessary.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnPlayingInStereoListener(OnPlayingInStereoListener listener);
+
+ /**
+ * Unregister a callback to be invoked when playback of the tuned frequency
+ * changes between mono and stereo.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnPlayingInStereoListener(OnPlayingInStereoListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmReceiver is forced to pause
+ * due to external reasons.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnForcedPauseListener(OnForcedPauseListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmReceiver is forced to
+ * pause due to external reasons.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnForcedPauseListener(OnForcedPauseListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmReceiver is forced to reset
+ * due to external reasons.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnForcedResetListener(OnForcedResetListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmReceiver is forced to
+ * reset due to external reasons.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnForcedResetListener(OnForcedResetListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmReceiver changes state.
+ * Having a listener registered to this method may cause frequent callbacks,
+ * hence it is good practice to only have a listener registered for this
+ * when necessary.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnStateChangedListener(OnStateChangedListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmReceiver changes state.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnStateChangedListener(OnStateChangedListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmReceiver want's to invoke a
+ * vendor specific callback.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnExtraCommandListener(OnExtraCommandListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmReceiver want's to invoke
+ * a vendor specific callback.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnExtraCommandListener(OnExtraCommandListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmReceiver has triggered
+ * a changed frequency.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void addOnAutomaticSwitchListener(OnAutomaticSwitchListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmReceiver has triggered
+ * a changed frequency.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_RECEIVER permission is not present
+ */
+ public abstract void removeOnAutomaticSwitchListener(OnAutomaticSwitchListener listener);
+
+ /**
+ * Interface definition of a callback to be invoked when the FmReceiver is
+ * started.
+ */
+ public interface OnStartedListener {
+ /**
+ * Called when the FmReceiver is started. The FmReceiver is now
+ * receiving FM radio.
+ */
+ void onStarted();
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when a scan operation is
+ * complete.
+ */
+ public interface OnScanListener {
+ /**
+ * Called when the full scan is completed.
+ * <p>
+ * If the full scan is aborted with stopScan, this will be indicated
+ * with the aborted argument.
+ * <p>
+ * If an error occurs during a full scan, it will be reported via
+ * {@link OnErrorListener#onError()} and this method callback will not
+ * be invoked.
+ * </p>
+ *
+ * @param frequency
+ * the frequency in kHz where the channel was found
+ * @param signalStrength
+ * the signal strength, 0-1000
+ * @param aborted
+ * true if the full scan was aborted, false otherwise
+ */
+ void onFullScan(int[] frequency, int[] signalStrength, boolean aborted);
+
+ /**
+ * Called when {@link FmReceiver#scanDown()} or
+ * {@link FmReceiver#scanUp()} has successfully completed a scan
+ * operation. Note that failing to find a channel during a scan
+ * operation does not mean that it is an error, and it will still result
+ * in a call to this interface.
+ * <p>
+ * If the scan is aborted with stopScan, this will be indicated with the
+ * aborted argument.
+ * <p>
+ *
+ * @param tunedFrequency
+ * the current frequency in kHz of the tuner after the scan
+ * operation was completed
+ * @param signalStrength
+ * the signal strength, 0-1000
+ * @param scanDirection
+ * direction of scan, SCAN_DOWN or SCAN_UP
+ * @param aborted
+ * true if the scan was aborted, false otherwise
+ */
+ void onScan(int tunedFrequency, int signalStrength, int scanDirection, boolean aborted);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when RDS data has been
+ * found. Note that there is not necessarily a relation between the
+ * frequency that the RDS data is found at and the currently tuned
+ * frequency.
+ */
+ public interface OnRDSDataFoundListener {
+ /**
+ * Called when RDS data has been found or updated.
+ *
+ * @param rdsData
+ * the RDS data that was found
+ * @param frequency
+ * the frequency where the RDS data was found
+ */
+ void onRDSDataFound(Bundle rdsData, int frequency);
+ };
+
+ /**
+ * Interface definition of a callback to be invoked when there has been an
+ * error during an asynchronous operation.
+ */
+ public interface OnErrorListener {
+ /**
+ * Called to indicate an error.
+ */
+ void onError();
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the signal strength
+ * of the currently tuned frequency changes.
+ */
+ public interface OnSignalStrengthChangedListener {
+ /**
+ * Called to indicate that the signal strength has changed.
+ *
+ * @param signalStrength
+ * the signal strength, 0-1000
+ */
+ void onSignalStrengthChanged(int signalStrength);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when playback of the
+ * tuned frequency changes between mono and stereo. This is useful if the
+ * application wants to display some icon that shows if playing in stereo or
+ * not.
+ */
+ public interface OnPlayingInStereoListener {
+ /**
+ * Called when switching between mono and stereo.
+ *
+ * @param inStereo
+ * true if playback is in stereo, false if in mono
+ */
+ void onPlayingInStereo(boolean inStereo);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmReceiver was
+ * forced to pause due to external reasons.
+ */
+ public interface OnForcedPauseListener {
+ /**
+ * Called when an external reason caused the FmReceiver to pause. When
+ * this callback is received, the FmReceiver is still able to resume
+ * reception by calling {@link FmReceiver#resume()}.
+ */
+ void onForcedPause();
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmReceiver was
+ * forced to reset due to external reasons.
+ */
+ public interface OnForcedResetListener {
+ /**
+ * Called when an external reason caused the FmReceiver to reset. The
+ * application that uses the FmReceiver should take action according to
+ * the reason for resetting.
+ *
+ * @param reason
+ * reason why the FmReceiver reset:
+ * <ul>
+ * <li>{@link FmReceiver#RESET_NON_CRITICAL}
+ * <li>{@link FmReceiver#RESET_CRITICAL}
+ * <li>{@link FmReceiver#RESET_TX_IN_USE}
+ * <li>{@link FmReceiver#RESET_RADIO_FORBIDDEN}
+ * </ul>
+ */
+ void onForcedReset(int reason);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmReceiver
+ * changes state.
+ */
+ public interface OnStateChangedListener {
+ /**
+ * Called when the state is changed in the FmReceiver. This is useful if
+ * an application want's to monitor the FmReceiver state.
+ *
+ * @param oldState
+ * the old state of the FmReceiver
+ * @param newState
+ * the new state of the FmReceiver
+ */
+ void onStateChanged(int oldState, int newState);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmReceiver
+ * responds to a vendor specific command.
+ */
+ public interface OnExtraCommandListener {
+ /**
+ * Called when the FmReceiver responds to a vendor specific command.
+ *
+ * @param response
+ * the command the FmReceiver responds to
+ * @param extras
+ * extra parameters to the command
+ */
+ void onExtraCommand(String response, Bundle extras);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmReceiver
+ * changes frequency either due to AF switch or TA event.
+ */
+ public interface OnAutomaticSwitchListener {
+ /**
+ * Called when the FmReceiver changes frequency either due to AF
+ * switch or TA event.
+ *
+ * @param newFrequency
+ * the frequency switched to
+ * @param reason
+ * the reason for the switch:
+ * <ul>
+ * <li>{@link FmReceiver#SWITCH_AF}
+ * <li>{@link FmReceiver#SWITCH_TA}
+ * <li>{@link FmReceiver#SWITCH_TA_END}
+ * </ul>
+ */
+ void onAutomaticSwitch(int newFrequency, int reason);
+ }
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/FmReceiverImpl.java b/fmradio/java/com/stericsson/hardware/fm/FmReceiverImpl.java
new file mode 100644
index 0000000..da57bc0
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/FmReceiverImpl.java
@@ -0,0 +1,1126 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Bjorn Pileryd (bjorn.pileryd@sonyericsson.com)
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ * Author: Andreas Gustafsson (andreas.a.gustafsson@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * The implementation of the FmReceiver.
+ *
+ * @hide
+ */
+public class FmReceiverImpl extends FmReceiver {
+
+ private static final String TAG = "FmReceiver";
+
+ private IFmReceiver mService;
+
+ /**
+ * Save the FmBand used to be able to validate frequencies.
+ */
+ private FmBand mBand;
+
+ /**
+ * Map from OnStateChanged to their associated ListenerTransport objects.
+ */
+ private HashMap<OnStateChangedListener, OnStateChangedListenerTransport> mOnStateChanged =
+ new HashMap<OnStateChangedListener, OnStateChangedListenerTransport>();
+
+ /**
+ * Map from OnStarted to their associated ListenerTransport objects.
+ */
+ private HashMap<OnStartedListener, OnStartedListenerTransport> mOnStarted =
+ new HashMap<OnStartedListener, OnStartedListenerTransport>();
+
+ /**
+ * Map from OnError to their associated ListenerTransport objects.
+ */
+ private HashMap<OnErrorListener, OnErrorListenerTransport> mOnError =
+ new HashMap<OnErrorListener, OnErrorListenerTransport>();
+
+ /**
+ * Map from OnScan to their associated ListenerTransport objects.
+ */
+ private HashMap<OnScanListener, OnScanListenerTransport> mOnScan =
+ new HashMap<OnScanListener, OnScanListenerTransport>();
+
+ /**
+ * Map from OnForcedPause to their associated ListenerTransport objects.
+ */
+ private HashMap<OnForcedPauseListener, OnForcedPauseListenerTransport> mOnForcedPause =
+ new HashMap<OnForcedPauseListener, OnForcedPauseListenerTransport>();
+
+ /**
+ * Map from OnForcedReset to their associated ListenerTransport objects.
+ */
+ private HashMap<OnForcedResetListener, OnForcedResetListenerTransport> mOnForcedReset =
+ new HashMap<OnForcedResetListener, OnForcedResetListenerTransport>();
+
+ /**
+ * Map from OnRDSDataFound to their associated ListenerTransport objects.
+ */
+ private HashMap<OnRDSDataFoundListener, OnRDSDataListenerTransport> mOnRDSData =
+ new HashMap<OnRDSDataFoundListener, OnRDSDataListenerTransport>();
+
+ /**
+ * Map from OnSignalStrength to their associated ListenerTransport objects.
+ */
+ private HashMap<OnSignalStrengthChangedListener, OnSignalStrengthListenerTransport> mOnSignalStrength =
+ new HashMap<OnSignalStrengthChangedListener, OnSignalStrengthListenerTransport>();
+
+ /**
+ * Map from OnStereo to their associated ListenerTransport objects.
+ */
+ private HashMap<OnPlayingInStereoListener, OnStereoListenerTransport> mOnStereo =
+ new HashMap<OnPlayingInStereoListener, OnStereoListenerTransport>();
+
+ /**
+ * Map from OnExtraCommand to their associated ListenerTransport objects.
+ */
+ private HashMap<OnExtraCommandListener, OnExtraCommandListenerTransport> mOnExtraCommand =
+ new HashMap<OnExtraCommandListener, OnExtraCommandListenerTransport>();
+
+ /**
+ * Map from OnAutomaticSwitch to their associated ListenerTransport objects.
+ */
+ private HashMap<OnAutomaticSwitchListener, OnAutomaticSwitchListenerTransport> mOnAutomaticSwitch =
+ new HashMap<OnAutomaticSwitchListener, OnAutomaticSwitchListenerTransport>();
+
+ private static class OnStateChangedListenerTransport extends IOnStateChangedListener.Stub {
+ private static final int TYPE_ON_STATE_CHANGED = 1;
+
+ private OnStateChangedListener mListener;
+ private final Handler mListenerHandler;
+
+ OnStateChangedListenerTransport(OnStateChangedListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+
+ public void onStateChanged(int oldState, int newState) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_STATE_CHANGED;
+ Bundle b = new Bundle();
+ b.putInt("oldState", oldState);
+ b.putInt("newState", newState);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_STATE_CHANGED:
+ Bundle b = (Bundle) msg.obj;
+ int oldState = b.getInt("oldState");
+ int newState = b.getInt("newState");
+ mListener.onStateChanged(oldState, newState);
+ break;
+ }
+ }
+ }
+
+ private static class OnStartedListenerTransport extends IOnStartedListener.Stub {
+ private static final int TYPE_ON_STARTED = 1;
+
+ private OnStartedListener mListener;
+ private final Handler mListenerHandler;
+
+ OnStartedListenerTransport(OnStartedListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onStarted() {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_STARTED;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_STARTED:
+ mListener.onStarted();
+ break;
+ }
+ }
+ }
+
+ private static class OnErrorListenerTransport extends IOnErrorListener.Stub {
+ private static final int TYPE_ON_ERROR = 1;
+
+ private OnErrorListener mListener;
+ private final Handler mListenerHandler;
+
+ OnErrorListenerTransport(OnErrorListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onError() {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_ERROR;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_ERROR:
+ mListener.onError();
+ break;
+ }
+ }
+ }
+
+ private static class OnScanListenerTransport extends IOnScanListener.Stub {
+ private static final int TYPE_ON_SCAN = 1;
+ private static final int TYPE_ON_FULLSCAN = 2;
+
+ private OnScanListener mListener;
+ private final Handler mListenerHandler;
+
+ OnScanListenerTransport(OnScanListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onScan(int tunedFrequency, int signalStrength,
+ int scanDirection, boolean aborted) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_SCAN;
+ Bundle b = new Bundle();
+ b.putInt("tunedFrequency", tunedFrequency);
+ b.putInt("signalStrength", signalStrength);
+ b.putInt("scanDirection", scanDirection);
+ b.putBoolean("aborted", aborted);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ public void onFullScan(int[] frequency, int[] signalStrength, boolean aborted) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_FULLSCAN;
+ Bundle b = new Bundle();
+ b.putIntArray("frequency", frequency);
+ b.putIntArray("signalStrength", signalStrength);
+ b.putBoolean("aborted", aborted);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ Bundle b;
+ boolean aborted;
+
+ switch (msg.what) {
+ case TYPE_ON_SCAN:
+ b = (Bundle) msg.obj;
+ int tunedFrequency = b.getInt("tunedFrequency");
+ int signalStrength = b.getInt("signalStrength");
+ int scanDirection = b.getInt("scanDirection");
+ aborted = b.getBoolean("aborted");
+ mListener.onScan(tunedFrequency, signalStrength, scanDirection, aborted);
+ break;
+ case TYPE_ON_FULLSCAN:
+ b = (Bundle) msg.obj;
+ int[] frequency = b.getIntArray("frequency");
+ int[] signalStrengths = b.getIntArray("signalStrength");
+ aborted = b.getBoolean("aborted");
+ mListener.onFullScan(frequency, signalStrengths, aborted);
+ break;
+ }
+ }
+ }
+
+ private static class OnForcedPauseListenerTransport extends IOnForcedPauseListener.Stub {
+ private static final int TYPE_ON_FORCEDPAUSE = 1;
+
+ private OnForcedPauseListener mListener;
+ private final Handler mListenerHandler;
+
+ OnForcedPauseListenerTransport(OnForcedPauseListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onForcedPause() {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_FORCEDPAUSE;
+ Bundle b = new Bundle();
+ // Need more here? Or remove?
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_FORCEDPAUSE:
+ Bundle b = (Bundle) msg.obj;
+ mListener.onForcedPause();
+ break;
+ }
+ }
+ }
+
+ private static class OnForcedResetListenerTransport extends IOnForcedResetListener.Stub {
+ private static final int TYPE_ON_FORCEDRESET = 1;
+
+ private OnForcedResetListener mListener;
+ private final Handler mListenerHandler;
+
+ OnForcedResetListenerTransport(OnForcedResetListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onForcedReset(int reason) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_FORCEDRESET;
+ Bundle b = new Bundle();
+ b.putInt("reason", reason);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_FORCEDRESET:
+ Bundle b = (Bundle) msg.obj;
+ int reason = b.getInt("reason");
+ mListener.onForcedReset(reason);
+ break;
+ }
+ }
+ }
+
+ private static class OnRDSDataListenerTransport extends IOnRDSDataFoundListener.Stub {
+ private static final int TYPE_ON_RDS_DATA = 1;
+
+ private OnRDSDataFoundListener mListener;
+ private final Handler mListenerHandler;
+
+ OnRDSDataListenerTransport(OnRDSDataFoundListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onRDSDataFound(Bundle rdsData, int frequency) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_RDS_DATA;
+ Bundle b = new Bundle();
+ if (rdsData != null) {
+ b.putBundle("rdsData", rdsData);
+ }
+ b.putInt("frequency", frequency);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ Bundle b;
+
+ switch (msg.what) {
+ case TYPE_ON_RDS_DATA:
+ b = (Bundle) msg.obj;
+ int frequency = b.getInt("frequency");
+ Bundle rdsData = b.getBundle("rdsData");
+ mListener.onRDSDataFound(rdsData, frequency);
+ break;
+ }
+ }
+ }
+
+ private static class OnSignalStrengthListenerTransport extends IOnSignalStrengthListener.Stub {
+ private static final int TYPE_ON_SIGNAL_STRENGTH_CHANGED = 1;
+
+ private OnSignalStrengthChangedListener mListener;
+ private final Handler mListenerHandler;
+
+ OnSignalStrengthListenerTransport(OnSignalStrengthChangedListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onSignalStrengthChanged(int signalStrength) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_SIGNAL_STRENGTH_CHANGED;
+ Bundle b = new Bundle();
+ b.putInt("signalStrength", signalStrength);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ Bundle b;
+ boolean aborted;
+
+ switch (msg.what) {
+ case TYPE_ON_SIGNAL_STRENGTH_CHANGED:
+ b = (Bundle) msg.obj;
+ int signalStrength = b.getInt("signalStrength");
+ mListener.onSignalStrengthChanged(signalStrength);
+ break;
+ }
+ }
+ }
+
+ private static class OnStereoListenerTransport extends IOnStereoListener.Stub {
+ private static final int TYPE_ON_STEREO = 1;
+
+ private OnPlayingInStereoListener mListener;
+ private final Handler mListenerHandler;
+
+ OnStereoListenerTransport(OnPlayingInStereoListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onPlayingInStereo(boolean inStereo) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_STEREO;
+ Bundle b = new Bundle();
+ b.putBoolean("inStereo", inStereo);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ Bundle b;
+ boolean aborted;
+
+ switch (msg.what) {
+ case TYPE_ON_STEREO:
+ b = (Bundle) msg.obj;
+ boolean inStereo = b.getBoolean("inStereo");
+ mListener.onPlayingInStereo(inStereo);
+ break;
+ }
+ }
+ }
+
+ private static class OnExtraCommandListenerTransport extends IOnExtraCommandListener.Stub {
+ private static final int TYPE_ON_EXTRA_COMMAND = 1;
+
+ private OnExtraCommandListener mListener;
+ private final Handler mListenerHandler;
+
+ OnExtraCommandListenerTransport(OnExtraCommandListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onExtraCommand(String response, Bundle extras) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_EXTRA_COMMAND;
+ Bundle b = new Bundle();
+ b.putString("response", response);
+ b.putBundle("extras", extras);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ Bundle b;
+ boolean aborted;
+
+ switch (msg.what) {
+ case TYPE_ON_EXTRA_COMMAND:
+ b = (Bundle) msg.obj;
+ String response = b.getString("response");
+ Bundle extras = b.getBundle("extras");
+ mListener.onExtraCommand(response, extras);
+ break;
+ }
+ }
+ }
+
+ private static class OnAutomaticSwitchListenerTransport extends IOnAutomaticSwitchListener.Stub {
+ private static final int TYPE_ON_AUTOMATIC_SWITCH = 1;
+
+ private OnAutomaticSwitchListener mListener;
+ private final Handler mListenerHandler;
+
+ OnAutomaticSwitchListenerTransport(OnAutomaticSwitchListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onAutomaticSwitch(int newFrequency, int reason) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_AUTOMATIC_SWITCH;
+ Bundle b = new Bundle();
+ b.putInt("newFrequency", newFrequency);
+ b.putInt("reason", reason);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ Bundle b;
+ boolean aborted;
+
+ switch (msg.what) {
+ case TYPE_ON_AUTOMATIC_SWITCH:
+ b = (Bundle) msg.obj;
+ int newFrequency = b.getInt("newFrequency");
+ int reason = b.getInt("reason");
+ mListener.onAutomaticSwitch(newFrequency, reason);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Creates a new FmReceiver instance. Applications will almost always want
+ * to use {@link android.content.Context#getSystemService
+ * Context.getSystemService()} to retrieve the standard
+ * {@link android.content.Context "fm_receiver"}.
+ *
+ * @param service
+ * the Binder interface
+ * @hide - hide this because it takes in a parameter of type IFmReceiver,
+ * which is a system private class.
+ */
+ public FmReceiverImpl(IFmReceiver service) {
+ mService = service;
+ }
+
+ @Override
+ public void startAsync(FmBand band) throws IOException {
+ if (band == null) {
+ throw new IllegalArgumentException("Band cannot be null");
+ }
+ try {
+ mService.startAsync(band);
+ mBand = band;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "startAsync: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void start(FmBand band) throws IOException {
+ if (band == null) {
+ throw new IllegalArgumentException("Band cannot be null");
+ }
+ try {
+ mService.start(band);
+ mBand = band;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "start: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void resume() throws IOException {
+ try {
+ mService.resume();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "resume: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void pause() throws IOException {
+ try {
+ mService.pause();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "pause: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void reset() throws IOException {
+ try {
+ mService.reset();
+ mBand = null;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "reset: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public int getState() {
+ try {
+ return mService.getState();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getState: RemoteException", ex);
+ return STATE_IDLE;
+ }
+ }
+
+ @Override
+ public boolean isRDSDataSupported() {
+ try {
+ return mService.isRDSDataSupported();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "isRDSDataSupported: RemoteException", ex);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isTunedToValidChannel() {
+ try {
+ return mService.isTunedToValidChannel();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "isTunedToValidChannel: RemoteException", ex);
+ return false;
+ }
+ }
+
+ @Override
+ public void setThreshold(int threshold) throws IOException {
+ if (threshold < 0 || threshold > 1000) {
+ throw new IllegalArgumentException("threshold not within limits");
+ }
+ try {
+ mService.setThreshold(threshold);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "setThreshold: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public int getThreshold() throws IOException {
+ try {
+ return mService.getThreshold();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getThreshold: RemoteException", ex);
+ return 0;
+ }
+ }
+
+ @Override
+ public int getFrequency() throws IOException {
+ try {
+ return mService.getFrequency();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getFrequency: RemoteException", ex);
+ return FmBand.FM_FREQUENCY_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int getSignalStrength() throws IOException {
+ try {
+ return mService.getSignalStrength();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getSignalStrength: RemoteException", ex);
+ return SIGNAL_STRENGTH_UNKNOWN;
+ }
+ }
+
+ @Override
+ public boolean isPlayingInStereo() {
+ try {
+ return mService.isPlayingInStereo();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "isPlayingInStereo: RemoteException", ex);
+ return false;
+ }
+ }
+
+ @Override
+ public void setForceMono(boolean forceMono) {
+ try {
+ mService.setForceMono(forceMono);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "setForceMono: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void setAutomaticAFSwitching(boolean automatic) {
+ try {
+ mService.setAutomaticAFSwitching(automatic);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "setAutomaticAFSwitching: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void setAutomaticTASwitching(boolean automatic) {
+ try {
+ mService.setAutomaticTASwitching(automatic);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "setAutomaticTASwitching: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void setFrequency(int frequency) throws IOException {
+ if (mBand != null && !mBand.isFrequencyValid(frequency)) {
+ throw new IllegalArgumentException("Frequency is not valid in this band.");
+ }
+
+ try {
+ mService.setFrequency(frequency);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "setFrequency: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void startFullScan() {
+ try {
+ mService.startFullScan();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "startFullScan: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void scanDown() {
+ try {
+ mService.scanDown();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "scanDown: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void scanUp() {
+ try {
+ mService.scanUp();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "scanUp: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void stopScan() {
+ try {
+ mService.stopScan();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "stopScan: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public boolean sendExtraCommand(String command, String[] extras) {
+ try {
+ return mService.sendExtraCommand(command, extras);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "sendExtraCommand: RemoteException", ex);
+ return false;
+ }
+ }
+
+ @Override
+ public void addOnStartedListener(OnStartedListener listener) {
+ if (mOnStarted.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnStarted) {
+ OnStartedListenerTransport transport = new OnStartedListenerTransport(listener);
+ mService.addOnStartedListener(transport);
+ mOnStarted.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnStartedListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnStartedListener(OnStartedListener listener) {
+ try {
+ OnStartedListenerTransport transport = mOnStarted.remove(listener);
+ if (transport != null) {
+ mService.removeOnStartedListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnStartedListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnScanListener(OnScanListener listener) {
+ if (mOnScan.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnScan) {
+ OnScanListenerTransport transport = new OnScanListenerTransport(listener);
+ mService.addOnScanListener(transport);
+ mOnScan.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnScanListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnScanListener(OnScanListener listener) {
+ try {
+ OnScanListenerTransport transport = mOnScan.remove(listener);
+ if (transport != null) {
+ mService.removeOnScanListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnScanListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnRDSDataFoundListener(OnRDSDataFoundListener listener) {
+ if (mOnRDSData.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnRDSData) {
+ OnRDSDataListenerTransport transport = new OnRDSDataListenerTransport(listener);
+ mService.addOnRDSDataFoundListener(transport);
+ mOnRDSData.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnRDSDataFoundListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnRDSDataFoundListener(OnRDSDataFoundListener listener) {
+ try {
+ OnRDSDataListenerTransport transport = mOnRDSData.remove(listener);
+ if (transport != null) {
+ mService.removeOnRDSDataFoundListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnRDSDataFoundListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnErrorListener(OnErrorListener listener) {
+ if (mOnError.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnError) {
+ OnErrorListenerTransport transport = new OnErrorListenerTransport(listener);
+ mService.addOnErrorListener(transport);
+ mOnError.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnErrorListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnErrorListener(OnErrorListener listener) {
+ try {
+ OnErrorListenerTransport transport = mOnError.remove(listener);
+ if (transport != null) {
+ mService.removeOnErrorListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnErrorListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnSignalStrengthChangedListener(OnSignalStrengthChangedListener listener) {
+ if (mOnSignalStrength.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnSignalStrength) {
+ OnSignalStrengthListenerTransport transport = new OnSignalStrengthListenerTransport(
+ listener);
+ mService.addOnSignalStrengthChangedListener(transport);
+ mOnSignalStrength.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnSignalStrengthChangedListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnSignalStrengthChangedListener(OnSignalStrengthChangedListener listener) {
+ try {
+ OnSignalStrengthListenerTransport transport = mOnSignalStrength.remove(listener);
+ if (transport != null) {
+ mService.removeOnSignalStrengthChangedListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnSignalStrengthChangedListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnPlayingInStereoListener(OnPlayingInStereoListener listener) {
+ if (mOnStereo.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnStereo) {
+ OnStereoListenerTransport transport = new OnStereoListenerTransport(listener);
+ mService.addOnPlayingInStereoListener(transport);
+ mOnStereo.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnPlayingInStereoListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnPlayingInStereoListener(OnPlayingInStereoListener listener) {
+ try {
+ OnStereoListenerTransport transport = mOnStereo.remove(listener);
+ if (transport != null) {
+ mService.removeOnPlayingInStereoListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnPlayingInStereoListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnForcedPauseListener(OnForcedPauseListener listener) {
+ if (mOnForcedPause.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnForcedPause) {
+ OnForcedPauseListenerTransport transport = new OnForcedPauseListenerTransport(
+ listener);
+ mService.addOnForcedPauseListener(transport);
+ mOnForcedPause.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnForcedPauseListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnForcedPauseListener(OnForcedPauseListener listener) {
+ try {
+ OnForcedPauseListenerTransport transport = mOnForcedPause.remove(listener);
+ if (transport != null) {
+ mService.removeOnForcedPauseListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnForcedPauseListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnForcedResetListener(OnForcedResetListener listener) {
+ if (mOnForcedReset.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnForcedReset) {
+ OnForcedResetListenerTransport transport = new OnForcedResetListenerTransport(
+ listener);
+ mService.addOnForcedResetListener(transport);
+ mOnForcedReset.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnForcedResetListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnForcedResetListener(OnForcedResetListener listener) {
+ try {
+ OnForcedResetListenerTransport transport = mOnForcedReset.remove(listener);
+ if (transport != null) {
+ mService.removeOnForcedResetListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnForcedResetListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnStateChangedListener(OnStateChangedListener listener) {
+ if (mOnStateChanged.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnStateChanged) {
+ OnStateChangedListenerTransport transport = new OnStateChangedListenerTransport(
+ listener);
+ mService.addOnStateChangedListener(transport);
+ mOnStateChanged.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnStateChangedListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnStateChangedListener(OnStateChangedListener listener) {
+ try {
+ OnStateChangedListenerTransport transport = mOnStateChanged.remove(listener);
+ if (transport != null) {
+ mService.removeOnStateChangedListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnStateChangedListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnExtraCommandListener(OnExtraCommandListener listener) {
+ if (mOnExtraCommand.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnExtraCommand) {
+ OnExtraCommandListenerTransport transport = new OnExtraCommandListenerTransport(
+ listener);
+ mService.addOnExtraCommandListener(transport);
+ mOnExtraCommand.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnExtraCommandListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnExtraCommandListener(OnExtraCommandListener listener) {
+ try {
+ OnExtraCommandListenerTransport transport = mOnExtraCommand.remove(listener);
+ if (transport != null) {
+ mService.removeOnExtraCommandListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnExtraCommandListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnAutomaticSwitchListener(OnAutomaticSwitchListener listener) {
+ if (mOnAutomaticSwitch.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnAutomaticSwitch) {
+ OnAutomaticSwitchListenerTransport transport = new OnAutomaticSwitchListenerTransport(listener);
+ mService.addOnAutomaticSwitchListener(transport);
+ mOnAutomaticSwitch.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnAutomaticSwitchListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnAutomaticSwitchListener(OnAutomaticSwitchListener listener) {
+ try {
+ OnAutomaticSwitchListenerTransport transport = mOnAutomaticSwitch.remove(listener);
+ if (transport != null) {
+ mService.removeOnAutomaticSwitchListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnAutomaticSwitchListener: DeadObjectException", ex);
+ }
+ }
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/FmReceiverService.java b/fmradio/java/com/stericsson/hardware/fm/FmReceiverService.java
new file mode 100644
index 0000000..b002cb1
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/FmReceiverService.java
@@ -0,0 +1,1347 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * The implementation of the FM receiver service.
+ *
+ * @hide
+ */
+public class FmReceiverService extends IFmReceiver.Stub {
+
+ private static final String TAG = "FmReceiverService";
+
+ private Context mContext;
+
+ private final HashMap<Object, OnStateChangedReceiver> mOnStateChangedReceivers =
+ new HashMap<Object, OnStateChangedReceiver>();
+
+ private final HashMap<Object, OnStartedReceiver> mOnStartedReceivers =
+ new HashMap<Object, OnStartedReceiver>();
+
+ private final HashMap<Object, OnErrorReceiver> mOnErrorReceivers =
+ new HashMap<Object, OnErrorReceiver>();
+
+ private final HashMap<Object, OnScanReceiver> mOnScanReceivers =
+ new HashMap<Object, OnScanReceiver>();
+
+ private final HashMap<Object, OnForcedPauseReceiver> mOnForcedPauseReceivers =
+ new HashMap<Object, OnForcedPauseReceiver>();
+
+ private final HashMap<Object, OnForcedResetReceiver> mOnForcedResetReceivers =
+ new HashMap<Object, OnForcedResetReceiver>();
+
+ private final HashMap<Object, OnRDSDataReceiver> mOnRDSDataReceivers =
+ new HashMap<Object, OnRDSDataReceiver>();
+
+ private final HashMap<Object, OnSignalStrengthReceiver> mOnSignalStrengthReceivers =
+ new HashMap<Object, OnSignalStrengthReceiver>();
+
+ private final HashMap<Object, OnStereoReceiver> mOnStereoReceivers =
+ new HashMap<Object, OnStereoReceiver>();
+
+ private final HashMap<Object, OnExtraCommandReceiver> mOnExtraCommandReceivers =
+ new HashMap<Object, OnExtraCommandReceiver>();
+
+ private final HashMap<Object, OnAutomaticSwitchReceiver> mOnAutomaticSwitchReceivers =
+ new HashMap<Object, OnAutomaticSwitchReceiver>();
+
+ private final class OnStateChangedReceiver implements IBinder.DeathRecipient {
+ final IOnStateChangedListener mListener;
+
+ final Object mKey;
+
+ OnStateChangedReceiver(IOnStateChangedListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnStateChangedListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnStateChanged(int oldState, int newState) {
+ try {
+ synchronized (this) {
+ mListener.onStateChanged(oldState, newState);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnStateChanged: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnStateChangedReceivers) {
+ mOnStateChangedReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnStartedReceiver implements IBinder.DeathRecipient {
+ final IOnStartedListener mListener;
+
+ final Object mKey;
+
+ OnStartedReceiver(IOnStartedListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnStartedListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnStarted() {
+ try {
+ synchronized (this) {
+ mListener.onStarted();
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnStarted: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnStartedReceivers) {
+ mOnStartedReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnErrorReceiver implements IBinder.DeathRecipient {
+ final IOnErrorListener mListener;
+
+ final Object mKey;
+
+ OnErrorReceiver(IOnErrorListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnErrorListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnError() {
+ try {
+ synchronized (this) {
+ mListener.onError();
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnError: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnErrorReceivers) {
+ mOnErrorReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnScanReceiver implements IBinder.DeathRecipient {
+ final IOnScanListener mListener;
+
+ final Object mKey;
+
+ OnScanReceiver(IOnScanListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnScanListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnScan(int tunedFrequency, int signalLevel, int scanDirection, boolean aborted) {
+ try {
+ synchronized (this) {
+ mListener.onScan(tunedFrequency, signalLevel, scanDirection, aborted);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnScan: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public boolean callOnFullScan(int[] frequency, int[] signalLevel, boolean aborted) {
+ try {
+ synchronized (this) {
+ mListener.onFullScan(frequency, signalLevel, aborted);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnFullScan: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnScanReceivers) {
+ mOnScanReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnForcedPauseReceiver implements IBinder.DeathRecipient {
+ final IOnForcedPauseListener mListener;
+
+ final Object mKey;
+
+ OnForcedPauseReceiver(IOnForcedPauseListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnForcedPauseListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnForcedPause() {
+ try {
+ synchronized (this) {
+ mListener.onForcedPause();
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnForcedPause: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnForcedPauseReceivers) {
+ mOnForcedPauseReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnForcedResetReceiver implements IBinder.DeathRecipient {
+ final IOnForcedResetListener mListener;
+
+ final Object mKey;
+
+ OnForcedResetReceiver(IOnForcedResetListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnForcedResetListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnForcedReset(int reason) {
+ try {
+ synchronized (this) {
+ mListener.onForcedReset(reason);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnForcedReset: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnForcedResetReceivers) {
+ mOnForcedResetReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnRDSDataReceiver implements IBinder.DeathRecipient {
+ final IOnRDSDataFoundListener mListener;
+
+ final Object mKey;
+
+ OnRDSDataReceiver(IOnRDSDataFoundListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnRDSDataFoundListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnRDSDataFound(Bundle bundle, int frequency) {
+ try {
+ synchronized (this) {
+ mListener.onRDSDataFound(bundle, frequency);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnRDSDataFound: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnRDSDataReceivers) {
+ mOnRDSDataReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnSignalStrengthReceiver implements IBinder.DeathRecipient {
+ final IOnSignalStrengthListener mListener;
+
+ final Object mKey;
+
+ OnSignalStrengthReceiver(IOnSignalStrengthListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnSignalStrengthListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnSignalStrengthChanged(int signalStrength) {
+ try {
+ synchronized (this) {
+ mListener.onSignalStrengthChanged(signalStrength);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnSignalStrengthChanged: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnSignalStrengthReceivers) {
+ mOnSignalStrengthReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnStereoReceiver implements IBinder.DeathRecipient {
+ final IOnStereoListener mListener;
+
+ final Object mKey;
+
+ OnStereoReceiver(IOnStereoListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnStereoListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnPlayingInStereo(boolean inStereo) {
+ try {
+ synchronized (this) {
+ mListener.onPlayingInStereo(inStereo);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnPlayingInStereo: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnStereoReceivers) {
+ mOnStereoReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnExtraCommandReceiver implements IBinder.DeathRecipient {
+ final IOnExtraCommandListener mListener;
+
+ final Object mKey;
+
+ OnExtraCommandReceiver(IOnExtraCommandListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnExtraCommandListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnExtraCommand(String response, Bundle extras) {
+ try {
+ synchronized (this) {
+ mListener.onExtraCommand(response, extras);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnExtraCommand: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnExtraCommandReceivers) {
+ mOnExtraCommandReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnAutomaticSwitchReceiver implements IBinder.DeathRecipient {
+ final IOnAutomaticSwitchListener mListener;
+
+ final Object mKey;
+
+ OnAutomaticSwitchReceiver(IOnAutomaticSwitchListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnAutomaticSwitchListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnAutomaticSwitch(int newFrequency, int reason) {
+ try {
+ synchronized (this) {
+ mListener.onAutomaticSwitch(newFrequency, reason);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnAutomaticSwitch: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnAutomaticSwitchReceivers) {
+ mOnAutomaticSwitchReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ Log.d(TAG, "onReceive:ACTION_AIRPLANE_MODE_CHANGED");
+
+ // check that airplane mode is off
+ if (!isAirplaneModeOn()) {
+ return;
+ }
+
+ // power down hardware
+ if (_fm_receiver_reset() > FmReceiver.STATE_IDLE) {
+ notifyOnForcedReset(FmReceiver.RESET_RADIO_FORBIDDEN);
+ }
+ }
+ }
+
+ };
+
+ /* Returns true if airplane mode is currently on */
+ private boolean isAirplaneModeOn() {
+ return Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ public FmReceiverService(Context context) {
+ Log.i(TAG, "FmReceiverService created");
+ mContext = context;
+
+ // Register for airplane mode
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ filter.addAction(Intent.ACTION_DOCK_EVENT);
+
+ mContext.registerReceiver(mReceiver, filter);
+
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ public void start(FmBand band) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_start(band.getMinFrequency(), band.getMaxFrequency(), band
+ .getDefaultFrequency(), band.getChannelOffset());
+
+ if (mOnRDSDataReceivers.size() > 0) {
+ Log.d(TAG, "Started with RDS receiver(s), switching on RDS");
+ _fm_receiver_setRDS(true);
+ }
+ }
+
+ public void startAsync(FmBand band) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_startAsync(band.getMinFrequency(), band.getMaxFrequency(), band
+ .getDefaultFrequency(), band.getChannelOffset());
+ }
+
+ public void reset() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_reset();
+ }
+
+ public void pause() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_pause();
+ }
+
+ public void resume() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_resume();
+ }
+
+ public int getState() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ return _fm_receiver_getState();
+ }
+
+ public void setFrequency(int frequency) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_setFrequency(frequency);
+ }
+
+ public int getFrequency() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ return _fm_receiver_getFrequency();
+ }
+
+ public void scanUp() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_scanUp();
+ }
+
+ public void scanDown() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_scanDown();
+ }
+
+ public void startFullScan() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_startFullScan();
+ }
+
+ public void stopScan() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_stopScan();
+ }
+
+ public boolean isRDSDataSupported() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ return _fm_receiver_isRDSDataSupported();
+ }
+
+ public boolean isTunedToValidChannel() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ return _fm_receiver_isTunedToValidChannel();
+ }
+
+ public void setThreshold(int threshold) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_setThreshold(threshold);
+ }
+
+ public int getThreshold() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ return _fm_receiver_getThreshold();
+ }
+
+ public int getSignalStrength() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ return _fm_receiver_getSignalStrength();
+ }
+
+ public boolean isPlayingInStereo() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ return _fm_receiver_isPlayingInStereo();
+ }
+
+ public void setForceMono(boolean forceMono) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_setForceMono(forceMono);
+ }
+
+ public void setAutomaticAFSwitching(boolean automatic) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_setAutomaticAFSwitching(automatic);
+ }
+
+ public void setAutomaticTASwitching(boolean automatic) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ _fm_receiver_setAutomaticTASwitching(automatic);
+ }
+
+ public boolean sendExtraCommand(String command, String[] extras) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ return _fm_receiver_sendExtraCommand(command, extras);
+ }
+
+ public void addOnStateChangedListener(IOnStateChangedListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStateChangedReceiver receiver = mOnStateChangedReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnStateChangedReceiver(listener);
+ mOnStateChangedReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnStateChangedListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnStateChangedListener(IOnStateChangedListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStateChangedReceiver receiver = mOnStateChangedReceivers.get(binder);
+ if (receiver != null) {
+ mOnStateChangedReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnStateChangedListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnStartedListener(IOnStartedListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStartedReceiver receiver = mOnStartedReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnStartedReceiver(listener);
+ mOnStartedReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnStartedListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnStartedListener(IOnStartedListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStartedReceiver receiver = mOnStartedReceivers.get(binder);
+ if (receiver != null) {
+ mOnStartedReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnStartedListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnErrorListener(IOnErrorListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnErrorReceiver receiver = mOnErrorReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnErrorReceiver(listener);
+ mOnErrorReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnErrorListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnErrorListener(IOnErrorListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnErrorReceiver receiver = mOnErrorReceivers.get(binder);
+ if (receiver != null) {
+ mOnErrorReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnErrorListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnScanListener(IOnScanListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnScanReceiver receiver = mOnScanReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnScanReceiver(listener);
+ mOnScanReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnScanListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnScanListener(IOnScanListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnScanReceiver receiver = mOnScanReceivers.get(binder);
+ if (receiver != null) {
+ mOnScanReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnScanListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnForcedPauseListener(IOnForcedPauseListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnForcedPauseReceiver receiver = mOnForcedPauseReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnForcedPauseReceiver(listener);
+ mOnForcedPauseReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnForcedPauseListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnForcedPauseListener(IOnForcedPauseListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnForcedPauseReceiver receiver = mOnForcedPauseReceivers.get(binder);
+ if (receiver != null) {
+ mOnForcedPauseReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnForcedPauseListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnForcedResetListener(IOnForcedResetListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnForcedResetReceiver receiver = mOnForcedResetReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnForcedResetReceiver(listener);
+ mOnForcedResetReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnForcedResetListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnForcedResetListener(IOnForcedResetListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnForcedResetReceiver receiver = mOnForcedResetReceivers.get(binder);
+ if (receiver != null) {
+ mOnForcedResetReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnForcedResetListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnRDSDataFoundListener(IOnRDSDataFoundListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnRDSDataReceiver receiver = mOnRDSDataReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnRDSDataReceiver(listener);
+ mOnRDSDataReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnRDSDataFoundListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ if ((getState() >= FmReceiver.STATE_STARTED) &&
+ (mOnRDSDataReceivers.size() == 1)) {
+ Log.d(TAG, "First RDS receiver added, switching on RDS");
+ _fm_receiver_setRDS(true);
+ }
+ }
+ }
+
+ public void removeOnRDSDataFoundListener(IOnRDSDataFoundListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnRDSDataReceiver receiver = mOnRDSDataReceivers.get(binder);
+ if (receiver != null) {
+ mOnRDSDataReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnRDSDataFoundListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ if ((getState() >= FmReceiver.STATE_STARTED) &&
+ mOnRDSDataReceivers.isEmpty()) {
+ Log.d(TAG, "Last RDS receiver removed, switching off RDS");
+ _fm_receiver_setRDS(false);
+ }
+ }
+ }
+
+ public void addOnSignalStrengthChangedListener(IOnSignalStrengthListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnSignalStrengthReceiver receiver = mOnSignalStrengthReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnSignalStrengthReceiver(listener);
+ mOnSignalStrengthReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnSignalStrengthChangedListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnSignalStrengthChangedListener(IOnSignalStrengthListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnSignalStrengthReceiver receiver = mOnSignalStrengthReceivers.get(binder);
+ if (receiver != null) {
+ mOnSignalStrengthReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnSignalStrengthChangedListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnPlayingInStereoListener(IOnStereoListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStereoReceiver receiver = mOnStereoReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnStereoReceiver(listener);
+ mOnStereoReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnPlayingInStereoListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnPlayingInStereoListener(IOnStereoListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStereoReceiver receiver = mOnStereoReceivers.get(binder);
+ if (receiver != null) {
+ mOnStereoReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnPlayingInStereoListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnExtraCommandListener(IOnExtraCommandListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnExtraCommandReceiver receiver = mOnExtraCommandReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnExtraCommandReceiver(listener);
+ mOnExtraCommandReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnExtraCommandListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnExtraCommandListener(IOnExtraCommandListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnExtraCommandReceiver receiver = mOnExtraCommandReceivers.get(binder);
+ if (receiver != null) {
+ mOnExtraCommandReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnExtraCommandListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnAutomaticSwitchListener(IOnAutomaticSwitchListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnAutomaticSwitchReceiver receiver = mOnAutomaticSwitchReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnAutomaticSwitchReceiver(listener);
+ mOnAutomaticSwitchReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnAutomaticSwitchListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnAutomaticSwitchListener(IOnAutomaticSwitchListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_RECEIVER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_RECEIVER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnAutomaticSwitchReceiver receiver = mOnAutomaticSwitchReceivers.get(binder);
+ if (receiver != null) {
+ mOnAutomaticSwitchReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnAutomaticSwitchListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ private void notifyOnStateChanged(int oldState, int newState) {
+ synchronized (mOnStateChangedReceivers) {
+ Collection c = mOnStateChangedReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnStateChangedReceiver m = (OnStateChangedReceiver) iterator.next();
+ m.callOnStateChanged(oldState, newState);
+ }
+ }
+ }
+
+ private void notifyOnStarted() {
+ synchronized (mOnStartedReceivers) {
+ Collection c = mOnStartedReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnStartedReceiver m = (OnStartedReceiver) iterator.next();
+ m.callOnStarted();
+ }
+
+ if (mOnRDSDataReceivers.size() > 0) {
+ Log.d(TAG, "Started event with RDS receiver(s), switching on RDS");
+ _fm_receiver_setRDS(true);
+ }
+ }
+ }
+
+ private void notifyOnScan(int frequency, int signalLevel, int scanDirection, boolean aborted) {
+ synchronized (mOnScanReceivers) {
+ Collection c = mOnScanReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnScanReceiver m = (OnScanReceiver) iterator.next();
+ m.callOnScan(frequency, signalLevel, scanDirection, aborted);
+ }
+ }
+ }
+
+ private void notifyOnFullScan(int[] frequency, int[] signalLevel, boolean aborted) {
+ synchronized (mOnScanReceivers) {
+ Collection c = mOnScanReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnScanReceiver m = (OnScanReceiver) iterator.next();
+ m.callOnFullScan(frequency, signalLevel, aborted);
+ }
+ }
+ }
+
+ private void notifyOnForcedPause() {
+ synchronized (mOnForcedPauseReceivers) {
+ Collection c = mOnForcedPauseReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnForcedPauseReceiver m = (OnForcedPauseReceiver) iterator.next();
+ m.callOnForcedPause();
+ }
+ }
+ }
+
+ private void notifyOnForcedReset(int reason) {
+ synchronized (mOnForcedResetReceivers) {
+ Collection c = mOnForcedResetReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnForcedResetReceiver m = (OnForcedResetReceiver) iterator.next();
+ m.callOnForcedReset(reason);
+ }
+ }
+ }
+
+ private void notifyOnError() {
+ synchronized (mOnErrorReceivers) {
+ Collection c = mOnErrorReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnErrorReceiver m = (OnErrorReceiver) iterator.next();
+ m.callOnError();
+ }
+ }
+ }
+
+ private void notifyOnRDSDataFound(Bundle bundle, int frequency) {
+ synchronized (mOnRDSDataReceivers) {
+ Collection c = mOnRDSDataReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnRDSDataReceiver m = (OnRDSDataReceiver) iterator.next();
+ m.callOnRDSDataFound(bundle, frequency);
+ }
+ }
+ }
+
+ private void notifyOnSignalStrengthChanged(int signalStrength) {
+ synchronized (mOnSignalStrengthReceivers) {
+ Collection c = mOnSignalStrengthReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnSignalStrengthReceiver m = (OnSignalStrengthReceiver) iterator.next();
+ m.callOnSignalStrengthChanged(signalStrength);
+ }
+ }
+ }
+
+ private void notifyOnPlayingInStereo(boolean inStereo) {
+ synchronized (mOnStereoReceivers) {
+ Collection c = mOnStereoReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnStereoReceiver m = (OnStereoReceiver) iterator.next();
+ m.callOnPlayingInStereo(inStereo);
+ }
+ }
+ }
+
+ private void notifyOnExtraCommand(String response, Bundle extras) {
+ synchronized (mOnExtraCommandReceivers) {
+ Collection c = mOnExtraCommandReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnExtraCommandReceiver m = (OnExtraCommandReceiver) iterator.next();
+ m.callOnExtraCommand(response, extras);
+ }
+ }
+ }
+
+ private void notifyOnAutomaticSwitching(int newFrequency, int reason) {
+ synchronized (mOnAutomaticSwitchReceivers) {
+ Collection c = mOnAutomaticSwitchReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnAutomaticSwitchReceiver m = (OnAutomaticSwitchReceiver) iterator.next();
+ m.callOnAutomaticSwitch(newFrequency, reason);
+ }
+ }
+ }
+
+ static
+ {
+ System.loadLibrary("analogradiobroadcasting");
+ }
+
+ private native void _fm_receiver_start(int minFreq, int maxFreq, int defaultFreq, int offset);
+
+ private native void _fm_receiver_startAsync(int minFreq, int maxFreq, int defaultFreq,
+ int offset);
+
+ private native int _fm_receiver_reset();
+
+ private native void _fm_receiver_pause();
+
+ private native void _fm_receiver_resume();
+
+ private native int _fm_receiver_getState();
+
+ private native void _fm_receiver_setFrequency(int frequency);
+
+ private native int _fm_receiver_getFrequency();
+
+ private native void _fm_receiver_scanUp();
+
+ private native void _fm_receiver_scanDown();
+
+ private native void _fm_receiver_startFullScan();
+
+ private native void _fm_receiver_stopScan();
+
+ private native boolean _fm_receiver_isRDSDataSupported();
+
+ private native boolean _fm_receiver_isTunedToValidChannel();
+
+ private native void _fm_receiver_setThreshold(int threshold);
+
+ private native int _fm_receiver_getThreshold();
+
+ private native int _fm_receiver_getSignalStrength();
+
+ private native boolean _fm_receiver_isPlayingInStereo();
+
+ private native void _fm_receiver_setForceMono(boolean forceMono);
+
+ private native void _fm_receiver_setAutomaticAFSwitching(boolean automatic);
+
+ private native void _fm_receiver_setAutomaticTASwitching(boolean automatic);
+
+ private native void _fm_receiver_setRDS(boolean receiveRDS);
+
+ private native boolean _fm_receiver_sendExtraCommand(String command, String[] extras);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/FmTransmitter.java b/fmradio/java/com/stericsson/hardware/fm/FmTransmitter.java
new file mode 100644
index 0000000..9581651
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/FmTransmitter.java
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Bjorn Pileryd (bjorn.pileryd@sonyericsson.com)
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import java.io.IOException;
+
+/**
+ * The FmTransmitter controls the output of FM radio from the device. When
+ * started, the transmitter will transmit audio via FM signals. The unit for all
+ * frequencies in this class is kHz. Note that this API only controls the output
+ * of FM radio, to select the audio stream the MediaPlayer interface should be
+ * used, see code example below the state diagram.
+ * <p>
+ * The output frequency can be changed at any time using
+ * {@link #setFrequency(int)}. The transmitter also supports transmission of RDS
+ * data, see {@link #setRdsData(Bundle)}.
+ * </p>
+ * <p>
+ * Get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(String)
+ * Context.getSystemService("fm_transmitter")}.
+ * </p>
+ * <a name="StateDiagram"></a> <h3>State Diagram</h3>
+ * <p>
+ * The state machine is designed to take into account that some hardware may
+ * need time to prepare, and that it is likely to consume more power when paused
+ * and started than it does in the idle state. The hardware implementation of
+ * this interface should do the time consuming preparation procedures in the
+ * starting state. The switching between paused and started states should be
+ * fast to give a good user experience.
+ * </p>
+ * <p>
+ * <img src="../../../../images/FmTransmitter_states.gif"
+ * alt="FmTransmitter State diagram" border="0" />
+ * </p>
+ * <table border="1">
+ * <tr>
+ * <th>Method Name</th>
+ * <th>Valid States</th>
+ * <th>Invalid States</th>
+ * <th>Comments</th>
+ * </tr>
+ * <tr>
+ * <td>{@link #startAsync(FmBand)}</td>
+ * <td>{idle}</td>
+ * <td>{starting, started, paused, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the starting state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #start(FmBand)}</td>
+ * <td>{idle}</td>
+ * <td>{starting, started, paused, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the started state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #resume()}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the started state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #pause()}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the paused state. Calling this method in an invalid state throws an
+ * IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #reset()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>Successful invocation of this method transfers the object to the idle
+ * state, the object is like being just created.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #getState()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #isApiSupported(Context)}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #setFrequency(int)}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state does not change the
+ * object state. Calling this method in an invalid state throws an
+ * IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #getFrequency()}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state does not change the
+ * object state. Calling this method in an invalid state throws an
+ * IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #setRdsData(Bundle)}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state does not change the
+ * object state. Calling this method in an invalid state throws an
+ * IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #isBlockScanSupported()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>This method can be called in any state and calling it does not change the
+ * object state.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #startBlockScan(int, int)}</td>
+ * <td>{started, paused}</td>
+ * <td>{idle, starting, scanning}</td>
+ * <td>Successful invocation of this method in a valid state transfers the
+ * object to the scanning state. Calling this method in an invalid state throws
+ * an IllegalStateException.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #stopScan()}</td>
+ * <td>any</td>
+ * <td>{}</td>
+ * <td>Successful invocation of this method in a valid state tries to stop
+ * performing a scan operation. The hardware might continue the scan for an
+ * unspecified amount of time after this method is called. Once the scan has
+ * stopped, it will be notified via {@link OnScanListener}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #sendExtraCommand(String, String[])}</td>
+ * <td>vendor specific</td>
+ * <td>vendor specific</td>
+ * <td>vendor specific</td>
+ * </tr>
+ * </table>
+ * <a name="Examples"></a> <h3>Example code</h3>
+ * <pre>
+ * // prepare and start the FM transmitter
+ * FmTransmitter fmt = (FmTransmitter) getSystemService("fm_transmitter");
+ * fmt.start(new FmBand(FmBand.BAND_EU));
+ *
+ * // prepare and start playback
+ * MediaPlayer mp = new MediaPlayer();
+ * mp.setDataSource(PATH_TO_FILE);
+ * mp.prepare();
+ * mp.start();
+ * </pre>
+ * <a name="FMHandling"></a> <h3>FM receiving/transmission handling</h3>
+ * <p>
+ * In this API, FM radio cannot be received and transmitted at the same time,
+ * therefore the state machine is designed to prevent incorrect usage. The
+ * FmReceiver and FmTransmitter has a separate state machine and only one can be
+ * <i>active</i> (state other than idle).
+ * <ul>
+ * <li>If start is called on FmTransmitter and the FmReceiver is <i>active</i>,
+ * the FmReceiver MUST release resources and change state to idle.</li>
+ * <li>The FmReceiver will in that case be notified by
+ * {@link com.stericsson.hardware.fm.FmReceiver.OnForcedResetListener#onForcedReset(int)}.</li>
+ * </ul>
+ * </p>
+ * <a name="ErrorHandling"></a> <h3>Error handling</h3>
+ * <p>
+ * In general, it is up to the application that uses this API to keep track of
+ * events that could affect the FM radio user experience. The hardware
+ * implementation side of this API should only take actions when it is really
+ * necessary, e.g. if the hardware is forced to pause or reset, and notify the
+ * application by using the {@link OnForcedPauseListener},
+ * {@link OnForcedResetListener} or {@link OnErrorListener}.
+ * </p>
+ */
+public abstract class FmTransmitter {
+
+ /**
+ * The FmTransmitter had to be shut down due to a non-critical error,
+ * meaning that it is OK to attempt a restart immediately after this. An
+ * example is when the hardware was shut down in order to save power after
+ * being in the paused state for too long.
+ */
+ public static final int RESET_NON_CRITICAL = 0;
+
+ /**
+ * The FmTransmitter had to be shut down due to a critical error. The FM
+ * hardware it not guaranteed to work as expected after receiving this
+ * error.
+ */
+ public static final int RESET_CRITICAL = 1;
+
+ /**
+ * The FmReceiver was activated and therefore the FmTransmitter must be put
+ * in idle.
+ *
+ * @see FmReceiver#startAsync(FmBand)
+ */
+ public static final int RESET_RX_IN_USE = 2;
+
+ /**
+ * The radio is not allowed to be used, typically when flight mode is
+ * enabled.
+ */
+ public static final int RESET_RADIO_FORBIDDEN = 3;
+
+ /**
+ * Indicates that the FmTransmitter is in an idle state. No resources are
+ * allocated and power consumption is kept to a minimum.
+ */
+ public static final int STATE_IDLE = 0;
+
+ /**
+ * Indicates that the FmTransmitter is allocating resources and preparing to
+ * transmit FM radio.
+ */
+ public static final int STATE_STARTING = 1;
+
+ /**
+ * Indicates that the FmTransmitter is transmitting FM radio.
+ */
+ public static final int STATE_STARTED = 2;
+
+ /**
+ * Indicates that the FmTransmitter has allocated resources and is ready to
+ * instantly transmit FM radio.
+ */
+ public static final int STATE_PAUSED = 3;
+
+ /**
+ * Indicates that the FmTransmitter is scanning. FM radio will not be
+ * transmitted in this state.
+ */
+ public static final int STATE_SCANNING = 4;
+
+ /**
+ * Returns true if the FM transmitter API is supported by the system.
+ */
+ public static boolean isApiSupported(Context context) {
+ return context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_RADIO_FM_TRANSMITTER);
+ }
+
+ /**
+ * Starts reception of the FM hardware. This is an asynchronous method since
+ * different hardware can have varying startup times. When the reception is
+ * started a callback to {@link OnStartedListener#onStarted()} is made.
+ * <p>
+ * When calling this method, an FmBand parameter must be passed that
+ * describes the properties of the band that the FmTransmitter should
+ * prepare for. If the band is null, invalid or not supported, an exception
+ * will be thrown.
+ * </p>
+ * <p>
+ * If the FmReceiver is active it will be forced to reset. See
+ * {@link FmReceiver#RESET_TX_IN_USE}.
+ * </p>
+ *
+ * @param band
+ * the band to use
+ * @throws IllegalArgumentException
+ * if the band is null
+ * @throws UnsupportedOperationException
+ * if the band is not supported by the hardware
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to start
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ * @see FmBand
+ */
+ public abstract void startAsync(FmBand band) throws IOException;
+
+ /**
+ * Starts reception of the FM hardware. This is a synchronous method and the
+ * method call will block until the hardware is started.
+ * <p>
+ * When calling this method, an FmBand parameter must be passed that
+ * describes the properties of the band that the FmTransmitter should
+ * prepare for. If the band is null, invalid or not supported, an exception
+ * will be thrown.
+ * </p>
+ * <p>
+ * If the FmReceiver is active it will be forced to reset. See
+ * {@link FmReceiver#RESET_TX_IN_USE}.
+ * </p>
+ *
+ * @param band
+ * the band to use
+ * @throws IllegalArgumentException
+ * if the band is null
+ * @throws UnsupportedOperationException
+ * if the band is not supported by the hardware
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to start
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ * @see FmBand
+ */
+ public abstract void start(FmBand band) throws IOException;
+
+ /**
+ * Resumes FM transmission.
+ * <p>
+ * Calling this method when the FmTransmitter is in started state has no
+ * affect.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to resume
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void resume() throws IOException;
+
+ /**
+ * Pauses FM transmission. No signals are sent when the FmTransmitter is
+ * paused. Call {@link #resume()} to resume transmission. The hardware
+ * should be able to start transmission quickly from the paused state to
+ * give a good user experience.
+ * <p>
+ * Note that the hardware provider may choose to turn off the hardware after
+ * being paused a certain amount of time to save power. This will be
+ * reported in {@link OnForcedResetListener#onForcedReset(int)} with reason
+ * {@link #RESET_NON_CRITICAL} and the FmTransmitter will be set to the idle
+ * state.
+ * </p>
+ * <p>
+ * Calling this method when the FmTransmitter is in paused state has no
+ * affect.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to pause
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void pause() throws IOException;
+
+ /**
+ * Resets the FmTransmitter to its idle state.
+ *
+ * @throws IOException
+ * if the FM hardware failed to reset
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void reset() throws IOException;
+
+ /**
+ * Returns the state of the FmTransmitter.
+ *
+ * @return One of {@link #STATE_IDLE}, {@link #STATE_STARTING},
+ * {@link #STATE_STARTED}, {@link #STATE_PAUSED},
+ * {@link #STATE_SCANNING}
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract int getState();
+
+ /**
+ * Sets the output frequency. The frequency must be within the band that the
+ * FmTransmitter prepared for.
+ *
+ * @param frequency
+ * the output frequency to use in kHz
+ * @throws IllegalArgumentException
+ * if the frequency is not supported
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to set frequency
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void setFrequency(int frequency) throws IOException;
+
+ /**
+ * Returns the output frequency.
+ *
+ * @return the output frequency in kHz
+ *
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws IOException
+ * if the FM hardware failed to get the frequency
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract int getFrequency() throws IOException;
+
+ /**
+ * Sets the RDS data to transmit. See RDS table in FmReceiver for data that
+ * can be set.
+ *
+ * @param rdsData
+ * the RDS data to transmit, set to null to disable RDS
+ * transmission
+ * @throws IllegalArgumentException
+ * if the rdsData parameter has invalid syntax
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void setRdsData(Bundle rdsData);
+
+ /**
+ * Returns true if the hardware/implementation supports block scan. If true
+ * the {@link FmTransmitter#startBlockScan(int, int)} will work.
+ * <p>
+ * The motivation for having this function is that an application can take
+ * this capability into account when laying out its UI.
+ * </p>
+ *
+ * @return true if block scan is supported by the FmTransmitter, false
+ * otherwise
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract boolean isBlockScanSupported();
+
+ /**
+ * Starts a block scan. The tuner will scan the frequency band between
+ * startFrequency and endFrequency for unused frequencies. The application
+ * should register for callbacks using
+ * {@link #addOnScanListener(OnScanListener)} to receive a callback when
+ * frequencies are found.
+ * <p>
+ * If the application wants to stop the block scan, a call to
+ * {@link #stopScan()} should be made.
+ * </p>
+ *
+ * @param startFrequency
+ * the frequency to start the block scan
+ * @param endFrequency
+ * the frequency to end the block scan
+ * @throws IllegalArgumentException
+ * if the startFrequency or endFrequency it not within the
+ * currently used FmBand
+ * @throws UnsupportedOperationException
+ * if the hardware/implementation does not supports block scan
+ * @throws IllegalStateException
+ * if it is called in an invalid state
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void startBlockScan(int startFrequency, int endFrequency);
+
+ /**
+ * Stops performing a scan operation. The hardware might continue the scan
+ * for an unspecified amount of time after this method is called. Once the
+ * scan has stopped, it will be notified via {@link OnScanListener}.
+ * <p>
+ * Note that this method has no affect if called in other states than the
+ * scanning state.
+ * </p>
+ *
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void stopScan();
+
+ /**
+ * This method can be used to send vendor specific commands. These commands
+ * must not follow any common design for all vendors, and information about
+ * the commands that a vendor implements is out of scope in this API.
+ * <p>
+ * However, one command must be supported by all vendors that implements
+ * vendor specific commands, the <i>vendor_information</i> command. In the
+ * Bundle parameter in
+ * {@link OnExtraCommandListener#onExtraCommand(String, Bundle)} the FM
+ * radio device name and version can be extracted according to the table
+ * below.
+ * </p>
+ * <table border="1">
+ * <tr>
+ * <th>key name</th>
+ * <th>value type</th>
+ * </tr>
+ * <tr>
+ * <td>device_name</td>
+ * <td>string</td>
+ * </tr>
+ * <tr>
+ * <td>device_version</td>
+ * <td>string</td>
+ * </tr>
+ * </table>
+ *
+ * @param command
+ * the command to send
+ * @param extras
+ * extra parameters to the command
+ * @return true if the command was accepted, otherwise false
+ *
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract boolean sendExtraCommand(String command, String[] extras);
+
+ /**
+ * Register a callback to be invoked when the FmTransmitter is started.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void addOnStartedListener(OnStartedListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmTransmitter is started.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void removeOnStartedListener(OnStartedListener listener);
+
+ /**
+ * Register a callback to be invoked during a scan.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void addOnScanListener(OnScanListener listener);
+
+ /**
+ * Unregister a callback to be invoked during a scan.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void removeOnScanListener(OnScanListener listener);
+
+ /**
+ * Register a callback to be invoked when an error has happened during an
+ * asynchronous operation.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void addOnErrorListener(OnErrorListener listener);
+
+ /**
+ * Unregister a callback to be invoked when an error has happened during an
+ * asynchronous operation.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void removeOnErrorListener(OnErrorListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmTransmitter is forced to
+ * pause due to external reasons.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void addOnForcedPauseListener(OnForcedPauseListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmTransmitter is forced to
+ * pause due to external reasons.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void removeOnForcedPauseListener(OnForcedPauseListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmTransmitter is forced to
+ * reset due to external reasons.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void addOnForcedResetListener(OnForcedResetListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmTransmitter is forced to
+ * reset due to external reasons.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void removeOnForcedResetListener(OnForcedResetListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmTransmitter changes state.
+ * Having a listener registered to this method may cause frequent callbacks,
+ * hence it is good practice to only have a listener registered for this
+ * when necessary.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void addOnStateChangedListener(OnStateChangedListener listener);
+
+ /**
+ * Unregister a callback to be invoked when the FmTransmitter changes state.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void removeOnStateChangedListener(OnStateChangedListener listener);
+
+ /**
+ * Register a callback to be invoked when the FmTransmitter want's to invoke
+ * a vendor specific callback.
+ *
+ * @param listener
+ * the callback that will be run
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void addOnExtraCommandListener(OnExtraCommandListener listener);
+ /**
+ * Unregister a callback to be invoked when the FmTransmitter want's to
+ * invoke a vendor specific callback.
+ *
+ * @param listener
+ * the callback to remove
+ * @throws SecurityException
+ * if the FM_RADIO_TRANSMITTER permission is not present
+ */
+ public abstract void removeOnExtraCommandListener(OnExtraCommandListener listener);
+
+ /**
+ * Interface definition of a callback to be invoked when the FmTransmitter
+ * is started.
+ */
+ public interface OnStartedListener {
+ /**
+ * Called when the FmTransmitter is started. The FmTransmitter is now
+ * transmitting FM radio.
+ */
+ void onStarted();
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when a scan operation is
+ * complete.
+ */
+ public interface OnScanListener {
+ /**
+ * Called when the block scan is completed.
+ * <p>
+ * If the block scan is aborted with stopScan, this will be indicated
+ * with the aborted argument.
+ * <p>
+ * If an error occurs during a block scan, it will be reported via
+ * {@link OnErrorListener#onError()} and this method callback will not
+ * be invoked.
+ * </p>
+ *
+ * @param frequency
+ * the frequency in kHz where the channel was found
+ * @param signalStrength
+ * the signal strength, 0-1000
+ * @param aborted
+ * true if the block scan was aborted, false otherwise
+ */
+ void onBlockScan(int[] frequency, int[] signalStrength, boolean aborted);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when there has been an
+ * error during an asynchronous operation.
+ */
+ public interface OnErrorListener {
+ /**
+ * Called to indicate an error.
+ */
+ void onError();
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmTransmitter
+ * was forced to pause due to external reasons.
+ */
+ public interface OnForcedPauseListener {
+ /**
+ * Called when an external reason caused the FmTransmitter to pause.
+ * When this callback is received, the FmTransmitter is still able to
+ * resume transmission by calling {@link FmTransmitter#resume()}.
+ */
+ void onForcedPause();
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmTransmitter
+ * was forced to reset due to external reasons.
+ */
+ public interface OnForcedResetListener {
+ /**
+ * Called when an external reason caused the FmTransmitter to reset. The
+ * application that uses the FmTransmitter should take action according
+ * to the reason for resetting.
+ *
+ * @param reason
+ * reason why the FmTransmitter reset:
+ * <ul>
+ * <li>{@link FmTransmitter#RESET_NON_CRITICAL}
+ * <li>{@link FmTransmitter#RESET_CRITICAL}
+ * <li>{@link FmTransmitter#RESET_RX_IN_USE}
+ * <li>{@link FmTransmitter#RESET_RADIO_FORBIDDEN}
+ * </ul>
+ */
+ void onForcedReset(int reason);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmTransmitter
+ * changes state.
+ */
+ public interface OnStateChangedListener {
+ /**
+ * Called when the state is changed in the FmTransmitter. This is useful
+ * if an application want's to monitor the FmTransmitter state.
+ *
+ * @param oldState
+ * the old state of the FmTransmitter
+ * @param newState
+ * the new state of the FmTransmitter
+ */
+ void onStateChanged(int oldState, int newState);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the FmTransmitter
+ * responds to a vendor specific command.
+ */
+ public interface OnExtraCommandListener {
+ /**
+ * Called when the FmTransmitter responds to a vendor specific command.
+ *
+ * @param response
+ * the command the FmTransmitter responds to
+ * @param extras
+ * extra parameters to the command
+ */
+ void onExtraCommand(String response, Bundle extras);
+ }
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/FmTransmitterImpl.java b/fmradio/java/com/stericsson/hardware/fm/FmTransmitterImpl.java
new file mode 100644
index 0000000..0dbb005
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/FmTransmitterImpl.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Bjorn Pileryd (bjorn.pileryd@sonyericsson.com)
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ * Author: Andreas Gustafsson (andreas.a.gustafsson@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * The implementation of the FmReceiver.
+ *
+ * @hide
+ */
+public class FmTransmitterImpl extends FmTransmitter {
+
+ private static final String TAG = "FmTransmitter";
+
+ private IFmTransmitter mService;
+
+ /**
+ * Save the FmBand used to be able to validate frequencies.
+ */
+ private FmBand mBand;
+
+ /**
+ * Map from OnStateChanged to their associated ListenerTransport objects.
+ */
+ private HashMap<OnStateChangedListener, OnStateChangedListenerTransport> mOnStateChanged =
+ new HashMap<OnStateChangedListener, OnStateChangedListenerTransport>();
+
+ /**
+ * Map from OnStarted to their associated ListenerTransport objects.
+ */
+ private HashMap<OnStartedListener, OnStartedListenerTransport> mOnStarted =
+ new HashMap<OnStartedListener, OnStartedListenerTransport>();
+
+ /**
+ * Map from OnError to their associated ListenerTransport objects.
+ */
+ private HashMap<OnErrorListener, OnErrorListenerTransport> mOnError =
+ new HashMap<OnErrorListener, OnErrorListenerTransport>();
+
+ /**
+ * Map from OnBlockScan to their associated ListenerTransport objects.
+ */
+ private HashMap<OnScanListener, OnBlockScanListenerTransport> mOnBlockScan =
+ new HashMap<OnScanListener, OnBlockScanListenerTransport>();
+
+ /**
+ * Map from OnForcedPause to their associated ListenerTransport objects.
+ */
+ private HashMap<OnForcedPauseListener, OnForcedPauseListenerTransport> mOnForcedPause =
+ new HashMap<OnForcedPauseListener, OnForcedPauseListenerTransport>();
+
+ /**
+ * Map from OnForcedReset to their associated ListenerTransport objects.
+ */
+ private HashMap<OnForcedResetListener, OnForcedResetListenerTransport> mOnForcedReset =
+ new HashMap<OnForcedResetListener, OnForcedResetListenerTransport>();
+
+ /**
+ * Map from OnExtraCommand to their associated ListenerTransport objects.
+ */
+ private HashMap<OnExtraCommandListener, OnExtraCommandListenerTransport> mOnExtraCommand =
+ new HashMap<OnExtraCommandListener, OnExtraCommandListenerTransport>();
+
+ private static class OnStateChangedListenerTransport extends IOnStateChangedListener.Stub {
+ private static final int TYPE_ON_STATE_CHANGED = 1;
+
+ private OnStateChangedListener mListener;
+ private final Handler mListenerHandler;
+
+ OnStateChangedListenerTransport(OnStateChangedListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onStateChanged(int oldState, int newState) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_STATE_CHANGED;
+ Bundle b = new Bundle();
+ b.putInt("oldState", oldState);
+ b.putInt("newState", newState);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_STATE_CHANGED:
+ Bundle b = (Bundle) msg.obj;
+ int oldState = b.getInt("oldState");
+ int newState = b.getInt("newState");
+ mListener.onStateChanged(oldState, newState);
+ break;
+ }
+ }
+ }
+
+ private static class OnStartedListenerTransport extends IOnStartedListener.Stub {
+ private static final int TYPE_ON_STARTED = 1;
+
+ private OnStartedListener mListener;
+ private final Handler mListenerHandler;
+
+ OnStartedListenerTransport(OnStartedListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onStarted() {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_STARTED;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_STARTED:
+ mListener.onStarted();
+ break;
+ }
+ }
+ }
+
+ private static class OnErrorListenerTransport extends IOnErrorListener.Stub {
+ private static final int TYPE_ON_ERROR = 1;
+
+ private OnErrorListener mListener;
+ private final Handler mListenerHandler;
+
+ OnErrorListenerTransport(OnErrorListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onError() {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_ERROR;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_ERROR:
+ mListener.onError();
+ break;
+ }
+ }
+ }
+
+ private static class OnBlockScanListenerTransport extends IOnBlockScanListener.Stub {
+ private static final int TYPE_ON_BLOCKSCAN = 1;
+
+ private OnScanListener mListener;
+ private final Handler mListenerHandler;
+
+ OnBlockScanListenerTransport(OnScanListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onBlockScan(int[] frequency, int[] signalStrength, boolean aborted) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_BLOCKSCAN;
+ Bundle b = new Bundle();
+ b.putIntArray("frequency", frequency);
+ b.putIntArray("signalStrength", signalStrength);
+ b.putBoolean("aborted", aborted);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+
+ switch (msg.what) {
+ case TYPE_ON_BLOCKSCAN:
+ Bundle b = (Bundle) msg.obj;
+ int[] frequency = b.getIntArray("frequency");
+ int[] signalStrengths = b.getIntArray("signalStrength");
+ boolean aborted = b.getBoolean("aborted");
+ mListener.onBlockScan(frequency, signalStrengths, aborted);
+ break;
+ }
+ }
+ }
+
+ private static class OnForcedPauseListenerTransport extends IOnForcedPauseListener.Stub {
+ private static final int TYPE_ON_FORCEDPAUSE = 1;
+
+ private OnForcedPauseListener mListener;
+ private final Handler mListenerHandler;
+
+ OnForcedPauseListenerTransport(OnForcedPauseListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onForcedPause() {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_FORCEDPAUSE;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_FORCEDPAUSE:
+ mListener.onForcedPause();
+ break;
+ }
+ }
+ }
+
+ private static class OnForcedResetListenerTransport extends IOnForcedResetListener.Stub {
+ private static final int TYPE_ON_FORCEDRESET = 1;
+
+ private OnForcedResetListener mListener;
+ private final Handler mListenerHandler;
+
+ OnForcedResetListenerTransport(OnForcedResetListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onForcedReset(int reason) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_FORCEDRESET;
+ Bundle b = new Bundle();
+ b.putInt("reason", reason);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ switch (msg.what) {
+ case TYPE_ON_FORCEDRESET:
+ Bundle b = (Bundle) msg.obj;
+ int reason = b.getInt("reason");
+ mListener.onForcedReset(reason);
+ break;
+ }
+ }
+ }
+
+ private static class OnExtraCommandListenerTransport extends IOnExtraCommandListener.Stub {
+ private static final int TYPE_ON_EXTRA_COMMAND = 1;
+
+ private OnExtraCommandListener mListener;
+ private final Handler mListenerHandler;
+
+ OnExtraCommandListenerTransport(OnExtraCommandListener listener) {
+ mListener = listener;
+
+ mListenerHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ _handleMessage(msg);
+ }
+ };
+ }
+
+ public void onExtraCommand(String response, Bundle extras) {
+ Message msg = Message.obtain();
+ msg.what = TYPE_ON_EXTRA_COMMAND;
+ Bundle b = new Bundle();
+ b.putString("response", response);
+ b.putBundle("extras", extras);
+ msg.obj = b;
+ mListenerHandler.sendMessage(msg);
+ }
+
+ private void _handleMessage(Message msg) {
+ Bundle b;
+ boolean aborted;
+
+ switch (msg.what) {
+ case TYPE_ON_EXTRA_COMMAND:
+ b = (Bundle) msg.obj;
+ String response = b.getString("response");
+ Bundle extras = b.getBundle("extras");
+ mListener.onExtraCommand(response, extras);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Creates a new FmTransmitter instance. Applications will almost always
+ * want to use {@link android.content.Context#getSystemService
+ * Context.getSystemService()} to retrieve the standard
+ * {@link android.content.Context "fm_transmitter"}.
+ *
+ * @param service
+ * the Binder interface
+ * @hide - hide this because it takes in a parameter of type IFmReceiver,
+ * which is a system private class.
+ */
+ public FmTransmitterImpl(IFmTransmitter service) {
+ mService = service;
+ }
+
+ @Override
+ public void startAsync(FmBand band) throws IOException {
+ if (band == null) {
+ throw new IllegalArgumentException("Band cannot be null");
+ }
+ try {
+ mService.startAsync(band);
+ mBand = band;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "startAsync: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void start(FmBand band) throws IOException {
+ if (band == null) {
+ throw new IllegalArgumentException("Band cannot be null");
+ }
+ try {
+ mService.start(band);
+ mBand = band;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "start: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void resume() throws IOException {
+ try {
+ mService.resume();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "resume: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void pause() throws IOException {
+ try {
+ mService.pause();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "pause: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void reset() throws IOException {
+ try {
+ mService.reset();
+ mBand = null;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "reset: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public int getState() {
+ try {
+ return mService.getState();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getState: RemoteException", ex);
+ return STATE_IDLE;
+ }
+ }
+
+ @Override
+ public void setFrequency(int frequency) throws IOException {
+ if (mBand != null && !mBand.isFrequencyValid(frequency)) {
+ throw new IllegalArgumentException(
+ "Frequency is not valid in this band.");
+ }
+ try {
+ mService.setFrequency(frequency);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "setFrequency: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public int getFrequency() throws IOException {
+ try {
+ return mService.getFrequency();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getFrequency: RemoteException", ex);
+ return FmBand.FM_FREQUENCY_UNKNOWN;
+ }
+ }
+
+ @Override
+ public void setRdsData(Bundle rdsData) {
+ try {
+ mService.setRdsData(rdsData);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "setRdsData: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public boolean isBlockScanSupported() {
+ try {
+ return mService.isBlockScanSupported();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "isBlockScanSupported: RemoteException", ex);
+ return false;
+ }
+ }
+
+ @Override
+ public void startBlockScan(int startFrequency, int endFrequency) {
+ try {
+ mService.startBlockScan(startFrequency, endFrequency);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "startBlockScan: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void stopScan() {
+ try {
+ mService.stopScan();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "stopScan: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public boolean sendExtraCommand(String command, String[] extras) {
+ try {
+ return mService.sendExtraCommand(command, extras);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "sendExtraCommand: RemoteException", ex);
+ return false;
+ }
+ }
+
+ @Override
+ public void addOnStartedListener(OnStartedListener listener) {
+ if (mOnStarted.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnStarted) {
+ OnStartedListenerTransport transport = new OnStartedListenerTransport(listener);
+ mService.addOnStartedListener(transport);
+ mOnStarted.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnStartedListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnStartedListener(OnStartedListener listener) {
+ try {
+ OnStartedListenerTransport transport = mOnStarted.remove(listener);
+ if (transport != null) {
+ mService.removeOnStartedListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnStartedListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnScanListener(OnScanListener listener) {
+ if (mOnBlockScan.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnBlockScan) {
+ OnBlockScanListenerTransport transport = new OnBlockScanListenerTransport(listener);
+ mService.addOnBlockScanListener(transport);
+ mOnBlockScan.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnScanListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnScanListener(OnScanListener listener) {
+ try {
+ OnBlockScanListenerTransport transport = mOnBlockScan.remove(listener);
+ if (transport != null) {
+ mService.removeOnBlockScanListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnScanListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnErrorListener(OnErrorListener listener) {
+ if (mOnError.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnError) {
+ OnErrorListenerTransport transport = new OnErrorListenerTransport(listener);
+ mService.addOnErrorListener(transport);
+ mOnError.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnErrorListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnErrorListener(OnErrorListener listener) {
+ try {
+ OnErrorListenerTransport transport = mOnError.remove(listener);
+ if (transport != null) {
+ mService.removeOnErrorListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnErrorListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnForcedPauseListener(OnForcedPauseListener listener) {
+ if (mOnForcedPause.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnForcedPause) {
+ OnForcedPauseListenerTransport transport = new OnForcedPauseListenerTransport(
+ listener);
+ mService.addOnForcedPauseListener(transport);
+ mOnForcedPause.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnForcedPauseListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnForcedPauseListener(OnForcedPauseListener listener) {
+ try {
+ OnForcedPauseListenerTransport transport = mOnForcedPause.remove(listener);
+ if (transport != null) {
+ mService.removeOnForcedPauseListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnForcedPauseListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnForcedResetListener(OnForcedResetListener listener) {
+ if (mOnForcedReset.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnForcedReset) {
+ OnForcedResetListenerTransport transport = new OnForcedResetListenerTransport(
+ listener);
+ mService.addOnForcedResetListener(transport);
+ mOnForcedReset.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnForcedResetListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnForcedResetListener(OnForcedResetListener listener) {
+ try {
+ OnForcedResetListenerTransport transport = mOnForcedReset.remove(listener);
+ if (transport != null) {
+ mService.removeOnForcedResetListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnForcedResetListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnStateChangedListener(OnStateChangedListener listener) {
+ if (mOnStateChanged.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnStateChanged) {
+ OnStateChangedListenerTransport transport = new OnStateChangedListenerTransport(
+ listener);
+ mService.addOnStateChangedListener(transport);
+ mOnStateChanged.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnStateChangedListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnStateChangedListener(OnStateChangedListener listener) {
+ try {
+ OnStateChangedListenerTransport transport = mOnStateChanged.remove(listener);
+ if (transport != null) {
+ mService.removeOnStateChangedListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnStateChangedListener: DeadObjectException", ex);
+ }
+ }
+
+ @Override
+ public void addOnExtraCommandListener(OnExtraCommandListener listener) {
+ if (mOnExtraCommand.get(listener) != null) {
+ // listener is already registered
+ return;
+ }
+ try {
+ synchronized (mOnExtraCommand) {
+ OnExtraCommandListenerTransport transport = new OnExtraCommandListenerTransport(
+ listener);
+ mService.addOnExtraCommandListener(transport);
+ mOnExtraCommand.put(listener, transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "addOnExtraCommandListener: RemoteException", ex);
+ }
+ }
+
+ @Override
+ public void removeOnExtraCommandListener(OnExtraCommandListener listener) {
+ try {
+ OnExtraCommandListenerTransport transport = mOnExtraCommand.remove(listener);
+ if (transport != null) {
+ mService.removeOnExtraCommandListener(transport);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "removeOnExtraCommandListener: DeadObjectException", ex);
+ }
+ }
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/FmTransmitterService.java b/fmradio/java/com/stericsson/hardware/fm/FmTransmitterService.java
new file mode 100644
index 0000000..8434ebc
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/FmTransmitterService.java
@@ -0,0 +1,846 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * The implementation of the FM transmitter service.
+ *
+ * @hide
+ */
+
+public class FmTransmitterService extends IFmTransmitter.Stub {
+
+ private static final String TAG = "FmTransmitterService";
+
+ private Context mContext;
+
+ private final HashMap<Object, OnStateChangedReceiver> mOnStateChangedReceivers =
+ new HashMap<Object, OnStateChangedReceiver>();
+
+ private final HashMap<Object, OnStartedReceiver> mOnStartedReceivers =
+ new HashMap<Object, OnStartedReceiver>();
+
+ private final HashMap<Object, OnErrorReceiver> mOnErrorReceivers =
+ new HashMap<Object, OnErrorReceiver>();
+
+ private final HashMap<Object, OnBlockScanReceiver> mOnBlockScanReceivers =
+ new HashMap<Object, OnBlockScanReceiver>();
+
+ private final HashMap<Object, OnForcedPauseReceiver> mOnForcedPauseReceivers =
+ new HashMap<Object, OnForcedPauseReceiver>();
+
+ private final HashMap<Object, OnForcedResetReceiver> mOnForcedResetReceivers =
+ new HashMap<Object, OnForcedResetReceiver>();
+
+ private final HashMap<Object, OnExtraCommandReceiver> mOnExtraCommandReceivers =
+ new HashMap<Object, OnExtraCommandReceiver>();
+
+ private final class OnStateChangedReceiver implements IBinder.DeathRecipient {
+ final IOnStateChangedListener mListener;
+ final Object mKey;
+
+ OnStateChangedReceiver(IOnStateChangedListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnStateChangedListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnStateChanged(int oldState, int newState) {
+ try {
+ synchronized (this) {
+ mListener.onStateChanged(oldState, newState);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnStateChanged: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM transmitter listener died");
+
+ synchronized (mOnStateChangedReceivers) {
+ mOnStateChangedReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnStartedReceiver implements IBinder.DeathRecipient {
+ final IOnStartedListener mListener;
+ final Object mKey;
+
+ OnStartedReceiver(IOnStartedListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnStartedListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnStarted() {
+ try {
+ synchronized (this) {
+ mListener.onStarted();
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnStarted: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM transmitter listener died");
+
+ synchronized (mOnStartedReceivers) {
+ mOnStartedReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnErrorReceiver implements IBinder.DeathRecipient {
+ final IOnErrorListener mListener;
+ final Object mKey;
+
+ OnErrorReceiver(IOnErrorListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnErrorListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnError() {
+ try {
+ synchronized (this) {
+ mListener.onError();
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnError: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM transmitter listener died");
+
+ synchronized (mOnErrorReceivers) {
+ mOnErrorReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnBlockScanReceiver implements IBinder.DeathRecipient {
+ final IOnBlockScanListener mListener;
+
+ final Object mKey;
+
+ OnBlockScanReceiver(IOnBlockScanListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnBlockScanListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnBlockScan(int[] frequency, int[] signalLevel, boolean aborted) {
+ try {
+ synchronized (this) {
+ mListener.onBlockScan(frequency, signalLevel, aborted);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnBlockScan: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnBlockScanReceivers) {
+ mOnBlockScanReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnForcedPauseReceiver implements IBinder.DeathRecipient {
+ final IOnForcedPauseListener mListener;
+ final Object mKey;
+
+ OnForcedPauseReceiver(IOnForcedPauseListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnForcedPauseListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnForcedPause() {
+ try {
+ synchronized (this) {
+ mListener.onForcedPause();
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnForcedPause: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM transmitter listener died");
+
+ synchronized (mOnForcedPauseReceivers) {
+ mOnForcedPauseReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnForcedResetReceiver implements IBinder.DeathRecipient {
+ final IOnForcedResetListener mListener;
+ final Object mKey;
+
+ OnForcedResetReceiver(IOnForcedResetListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnForcedResetListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnForcedReset(int reason) {
+ try {
+ synchronized (this) {
+ mListener.onForcedReset(reason);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnForcedReset: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM transmitter listener died");
+
+ synchronized (mOnForcedResetReceivers) {
+ mOnForcedResetReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final class OnExtraCommandReceiver implements IBinder.DeathRecipient {
+ final IOnExtraCommandListener mListener;
+
+ final Object mKey;
+
+ OnExtraCommandReceiver(IOnExtraCommandListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public IOnExtraCommandListener getListener() {
+ return mListener;
+ }
+
+ public boolean callOnExtraCommand(String response, Bundle extras) {
+ try {
+ synchronized (this) {
+ mListener.onExtraCommand(response, extras);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "callOnExtraCommand: RemoteException", ex);
+ return false;
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ Log.d(TAG, "FM receiver listener died");
+
+ synchronized (mOnExtraCommandReceivers) {
+ mOnExtraCommandReceivers.remove(this.mKey);
+ }
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ Log.d(TAG, "onReceive:ACTION_AIRPLANE_MODE_CHANGED");
+
+ // check that airplane mode is off
+ if (!isAirplaneModeOn()) {
+ return;
+ }
+
+ // power down hardware
+ if (_fm_transmitter_reset() > FmTransmitter.STATE_IDLE) {
+ notifyOnForcedReset(FmTransmitter.RESET_RADIO_FORBIDDEN);
+ }
+ }
+ }
+
+ };
+
+ /* Returns true if airplane mode is currently on */
+ private boolean isAirplaneModeOn() {
+ return Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ public FmTransmitterService(Context context) {
+ Log.i(TAG, "FmTransmitterService created");
+ mContext = context;
+
+ // Register for airplane mode
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ filter.addAction(Intent.ACTION_DOCK_EVENT);
+
+ mContext.registerReceiver(mReceiver, filter);
+
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ public void start(FmBand band) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_start(band.getMinFrequency(), band.getMaxFrequency(), band
+ .getDefaultFrequency(), band.getChannelOffset());
+ }
+
+ public void startAsync(FmBand band) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_startAsync(band.getMinFrequency(), band.getMaxFrequency(), band
+ .getDefaultFrequency(), band.getChannelOffset());
+ }
+
+ public void reset() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_reset();
+ }
+
+ public void pause() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_pause();
+ }
+
+ public void resume() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_resume();
+ }
+
+ public int getState() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ return _fm_transmitter_getState();
+ }
+
+ public void setFrequency(int frequency) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_setFrequency(frequency);
+ }
+
+ public int getFrequency() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ return _fm_transmitter_getFrequency();
+ }
+
+ public boolean isBlockScanSupported() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ return _fm_transmitter_isBlockScanSupported();
+ }
+
+ public void startBlockScan(int startFrequency, int endFrequency) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_startBlockScan(startFrequency, endFrequency);
+ }
+
+ public void stopScan() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_stopScan();
+ }
+
+ public void setRdsData(Bundle rdsData) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ _fm_transmitter_setRdsData(rdsData);
+ }
+
+ public boolean sendExtraCommand(String command, String[] extras) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ return _fm_transmitter_sendExtraCommand(command, extras);
+ }
+
+ public void addOnStateChangedListener(IOnStateChangedListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStateChangedReceiver receiver = mOnStateChangedReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnStateChangedReceiver(listener);
+ mOnStateChangedReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnStateChangedListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnStateChangedListener(IOnStateChangedListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStateChangedReceiver receiver = mOnStateChangedReceivers.get(binder);
+ if (receiver != null) {
+ mOnStateChangedReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnStateChangedListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnStartedListener(IOnStartedListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStartedReceiver receiver = mOnStartedReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnStartedReceiver(listener);
+ mOnStartedReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnStartedListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnStartedListener(IOnStartedListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnStartedReceiver receiver = mOnStartedReceivers.get(binder);
+ if (receiver != null) {
+ mOnStartedReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnStartedListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnErrorListener(IOnErrorListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnErrorReceiver receiver = mOnErrorReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnErrorReceiver(listener);
+ mOnErrorReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnErrorListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnErrorListener(IOnErrorListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnErrorReceiver receiver = mOnErrorReceivers.get(binder);
+ if (receiver != null) {
+ mOnErrorReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnErrorListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnBlockScanListener(IOnBlockScanListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnBlockScanReceiver receiver = mOnBlockScanReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnBlockScanReceiver(listener);
+ mOnBlockScanReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnBlockScanListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnBlockScanListener(IOnBlockScanListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnBlockScanReceiver receiver = mOnBlockScanReceivers.get(binder);
+ if (receiver != null) {
+ mOnBlockScanReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnBlockScanListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnForcedPauseListener(IOnForcedPauseListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnForcedPauseReceiver receiver = mOnForcedPauseReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnForcedPauseReceiver(listener);
+ mOnForcedPauseReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnForcedPauseListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnForcedPauseListener(IOnForcedPauseListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnForcedPauseReceiver receiver = mOnForcedPauseReceivers.get(binder);
+ if (receiver != null) {
+ mOnForcedPauseReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnForcedPauseListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnForcedResetListener(IOnForcedResetListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnForcedResetReceiver receiver = mOnForcedResetReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnForcedResetReceiver(listener);
+ mOnForcedResetReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnForcedResetListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnForcedResetListener(IOnForcedResetListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnForcedResetReceiver receiver = mOnForcedResetReceivers.get(binder);
+ if (receiver != null) {
+ mOnForcedResetReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnForcedResetListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ public void addOnExtraCommandListener(IOnExtraCommandListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnExtraCommandReceiver receiver = mOnExtraCommandReceivers.get(binder);
+ if (receiver == null) {
+ receiver = new OnExtraCommandReceiver(listener);
+ mOnExtraCommandReceivers.put(binder, receiver);
+ Log.d(TAG, "addOnExtraCommandListener(), new receiver added");
+ try {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "linkToDeath failed:", ex);
+ }
+ }
+ }
+
+ public void removeOnExtraCommandListener(IOnExtraCommandListener listener) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FM_RADIO_TRANSMITTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires FM_RADIO_TRANSMITTER permission");
+ }
+
+ IBinder binder = listener.asBinder();
+ OnExtraCommandReceiver receiver = mOnExtraCommandReceivers.get(binder);
+ if (receiver != null) {
+ mOnExtraCommandReceivers.remove(receiver.mKey);
+ Log.d(TAG, "removeOnExtraCommandListener(), receiver removed");
+ receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+ }
+ }
+
+ private void notifyOnStateChanged(int oldState, int newState) {
+ synchronized (mOnStateChangedReceivers) {
+ Collection c = mOnStateChangedReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnStateChangedReceiver m = (OnStateChangedReceiver) iterator.next();
+ m.callOnStateChanged(oldState, newState);
+ }
+ }
+ }
+
+ private void notifyOnStarted() {
+ synchronized (mOnStartedReceivers) {
+ Collection c = mOnStartedReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnStartedReceiver m = (OnStartedReceiver) iterator.next();
+ m.callOnStarted();
+ }
+ }
+ }
+
+ private void notifyOnError() {
+ synchronized (mOnErrorReceivers) {
+ Collection c = mOnErrorReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnErrorReceiver m = (OnErrorReceiver) iterator.next();
+ m.callOnError();
+ }
+ }
+ }
+
+ private void notifyOnBlockScan(int[] frequency, int[] signalLevel, boolean aborted) {
+ synchronized (mOnBlockScanReceivers) {
+ Collection c = mOnBlockScanReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnBlockScanReceiver m = (OnBlockScanReceiver) iterator.next();
+ m.callOnBlockScan(frequency, signalLevel, aborted);
+ }
+ }
+ }
+
+ private void notifyOnForcedPause() {
+ synchronized (mOnForcedPauseReceivers) {
+ Collection c = mOnForcedPauseReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnForcedPauseReceiver m = (OnForcedPauseReceiver) iterator.next();
+ m.callOnForcedPause();
+ }
+ }
+ }
+
+ private void notifyOnForcedReset(int reason) {
+ synchronized (mOnForcedResetReceivers) {
+ Collection c = mOnForcedResetReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnForcedResetReceiver m = (OnForcedResetReceiver) iterator.next();
+ m.callOnForcedReset(reason);
+ }
+ }
+ }
+
+ private void notifyOnExtraCommand(String response, Bundle extras) {
+ synchronized (mOnExtraCommandReceivers) {
+ Collection c = mOnExtraCommandReceivers.values();
+ Iterator iterator = c.iterator();
+ while (iterator.hasNext()) {
+ OnExtraCommandReceiver m = (OnExtraCommandReceiver) iterator.next();
+ m.callOnExtraCommand(response, extras);
+ }
+ }
+ }
+
+ private native void _fm_transmitter_start(int minFreq, int maxFreq, int defaultFreq, int offset);
+
+ private native void _fm_transmitter_startAsync(int minFreq, int maxFreq, int defaultFreq,
+ int offset);
+
+ private native int _fm_transmitter_reset();
+
+ private native void _fm_transmitter_pause();
+
+ private native void _fm_transmitter_resume();
+
+ private native int _fm_transmitter_getState();
+
+ private native void _fm_transmitter_setFrequency(int frequency);
+
+ private native int _fm_transmitter_getFrequency();
+
+ private native boolean _fm_transmitter_isBlockScanSupported();
+
+ private native void _fm_transmitter_startBlockScan(int startFrequency, int endFrequency);
+
+ private native void _fm_transmitter_stopScan();
+
+ private native void _fm_transmitter_setRdsData(Bundle rdsData);
+
+ private native boolean _fm_transmitter_sendExtraCommand(String command, String[] extras);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IFmReceiver.aidl b/fmradio/java/com/stericsson/hardware/fm/IFmReceiver.aidl
new file mode 100644
index 0000000..edf168e
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IFmReceiver.aidl
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import com.stericsson.hardware.fm.FmBand;
+import com.stericsson.hardware.fm.IOnStateChangedListener;
+import com.stericsson.hardware.fm.IOnStartedListener;
+import com.stericsson.hardware.fm.IOnErrorListener;
+import com.stericsson.hardware.fm.IOnScanListener;
+import com.stericsson.hardware.fm.IOnForcedPauseListener;
+import com.stericsson.hardware.fm.IOnForcedResetListener;
+import com.stericsson.hardware.fm.IOnRDSDataFoundListener;
+import com.stericsson.hardware.fm.IOnSignalStrengthListener;
+import com.stericsson.hardware.fm.IOnStereoListener;
+import com.stericsson.hardware.fm.IOnExtraCommandListener;
+import com.stericsson.hardware.fm.IOnAutomaticSwitchListener;
+
+/**
+ * {@hide}
+ */
+interface IFmReceiver
+{
+ void start(in FmBand band);
+ void startAsync(in FmBand band);
+ void reset();
+ void pause();
+ void resume();
+ int getState();
+ int getFrequency();
+ void setFrequency(int frequency);
+ void scanUp();
+ void scanDown();
+ void startFullScan();
+ void stopScan();
+ boolean isRDSDataSupported();
+ boolean isTunedToValidChannel();
+ void setThreshold(int threshold);
+ int getThreshold();
+ int getSignalStrength();
+ boolean isPlayingInStereo();
+ void setForceMono(boolean forceMono);
+ void setAutomaticAFSwitching(boolean automatic);
+ void setAutomaticTASwitching(boolean automatic);
+ boolean sendExtraCommand(String command, in String[] extras);
+ void addOnStateChangedListener(in IOnStateChangedListener listener);
+ void removeOnStateChangedListener(in IOnStateChangedListener listener);
+ void addOnStartedListener(in IOnStartedListener listener);
+ void removeOnStartedListener(in IOnStartedListener listener);
+ void addOnErrorListener(in IOnErrorListener listener);
+ void removeOnErrorListener(in IOnErrorListener listener);
+ void addOnScanListener(in IOnScanListener listener);
+ void removeOnScanListener(in IOnScanListener listener);
+ void addOnForcedPauseListener(in IOnForcedPauseListener listener);
+ void removeOnForcedPauseListener(in IOnForcedPauseListener listener);
+ void addOnForcedResetListener(in IOnForcedResetListener listener);
+ void removeOnForcedResetListener(in IOnForcedResetListener listener);
+ void addOnRDSDataFoundListener(in IOnRDSDataFoundListener listener);
+ void removeOnRDSDataFoundListener(in IOnRDSDataFoundListener listener);
+ void addOnSignalStrengthChangedListener(in IOnSignalStrengthListener listener);
+ void removeOnSignalStrengthChangedListener(in IOnSignalStrengthListener listener);
+ void addOnPlayingInStereoListener(in IOnStereoListener listener);
+ void removeOnPlayingInStereoListener(in IOnStereoListener listener);
+ void addOnExtraCommandListener(in IOnExtraCommandListener listener);
+ void removeOnExtraCommandListener(in IOnExtraCommandListener listener);
+ void addOnAutomaticSwitchListener(in IOnAutomaticSwitchListener listener);
+ void removeOnAutomaticSwitchListener(in IOnAutomaticSwitchListener listener);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IFmTransmitter.aidl b/fmradio/java/com/stericsson/hardware/fm/IFmTransmitter.aidl
new file mode 100644
index 0000000..6ca0cee
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IFmTransmitter.aidl
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import com.stericsson.hardware.fm.FmBand;
+import com.stericsson.hardware.fm.IOnStateChangedListener;
+import com.stericsson.hardware.fm.IOnStartedListener;
+import com.stericsson.hardware.fm.IOnErrorListener;
+import com.stericsson.hardware.fm.IOnBlockScanListener;
+import com.stericsson.hardware.fm.IOnForcedPauseListener;
+import com.stericsson.hardware.fm.IOnForcedResetListener;
+import com.stericsson.hardware.fm.IOnExtraCommandListener;
+import android.os.Bundle;
+
+/**
+ * {@hide}
+ */
+interface IFmTransmitter
+{
+ void start(in FmBand band);
+ void startAsync(in FmBand band);
+ void reset();
+ void pause();
+ void resume();
+ int getState();
+ int getFrequency();
+ void setFrequency(int frequency);
+ boolean isBlockScanSupported();
+ void startBlockScan(int startFrequency, int endFrequency);
+ void stopScan();
+ void setRdsData(in Bundle rdsData);
+ boolean sendExtraCommand(String command, in String[] extras);
+ void addOnStateChangedListener(in IOnStateChangedListener listener);
+ void removeOnStateChangedListener(in IOnStateChangedListener listener);
+ void addOnStartedListener(in IOnStartedListener listener);
+ void removeOnStartedListener(in IOnStartedListener listener);
+ void addOnErrorListener(in IOnErrorListener listener);
+ void removeOnErrorListener(in IOnErrorListener listener);
+ void addOnBlockScanListener(in IOnBlockScanListener listener);
+ void removeOnBlockScanListener(in IOnBlockScanListener listener);
+ void addOnForcedPauseListener(in IOnForcedPauseListener listener);
+ void removeOnForcedPauseListener(in IOnForcedPauseListener listener);
+ void addOnForcedResetListener(in IOnForcedResetListener listener);
+ void removeOnForcedResetListener(in IOnForcedResetListener listener);
+ void addOnExtraCommandListener(in IOnExtraCommandListener listener);
+ void removeOnExtraCommandListener(in IOnExtraCommandListener listener);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnAutomaticSwitchListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnAutomaticSwitchListener.aidl
new file mode 100644
index 0000000..8bf88ad
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnAutomaticSwitchListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Johan Palmaeus (johan.xj.palmaeus@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnAutomaticSwitchListener
+{
+ void onAutomaticSwitch(int newFrequency, int reason);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnBlockScanListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnBlockScanListener.aidl
new file mode 100644
index 0000000..ec39dea
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnBlockScanListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnBlockScanListener
+{
+ void onBlockScan(in int[] frequency, in int[] signalLevel, boolean aborted);
+} \ No newline at end of file
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnErrorListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnErrorListener.aidl
new file mode 100644
index 0000000..558600b
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnErrorListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnErrorListener
+{
+ void onError();
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnExtraCommandListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnExtraCommandListener.aidl
new file mode 100644
index 0000000..7647832
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnExtraCommandListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.os.Bundle;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnExtraCommandListener
+{
+ void onExtraCommand(String response, in Bundle extras);
+} \ No newline at end of file
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnForcedPauseListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnForcedPauseListener.aidl
new file mode 100644
index 0000000..bb8d876
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnForcedPauseListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnForcedPauseListener
+{
+ void onForcedPause();
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnForcedResetListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnForcedResetListener.aidl
new file mode 100644
index 0000000..4514501
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnForcedResetListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnForcedResetListener
+{
+ void onForcedReset(int reason);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnRDSDataFoundListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnRDSDataFoundListener.aidl
new file mode 100644
index 0000000..31b4801
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnRDSDataFoundListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+import android.os.Bundle;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnRDSDataFoundListener
+{
+ void onRDSDataFound(in Bundle options, int frequency);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnScanListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnScanListener.aidl
new file mode 100644
index 0000000..9996c1a
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnScanListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnScanListener
+{
+ void onScan(int tunedFrequency, int signalLevel, int scanDirection, boolean aborted);
+ void onFullScan(in int[] frequency, in int[] signalLevel, boolean aborted);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnSignalStrengthListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnSignalStrengthListener.aidl
new file mode 100644
index 0000000..431952b
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnSignalStrengthListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnSignalStrengthListener
+{
+ void onSignalStrengthChanged(int signalStrength);
+} \ No newline at end of file
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnStartedListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnStartedListener.aidl
new file mode 100644
index 0000000..b15ef09
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnStartedListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnStartedListener
+{
+ void onStarted();
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnStateChangedListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnStateChangedListener.aidl
new file mode 100644
index 0000000..3a63564
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnStateChangedListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnStateChangedListener
+{
+ void onStateChanged(int oldState, int newState);
+}
diff --git a/fmradio/java/com/stericsson/hardware/fm/IOnStereoListener.aidl b/fmradio/java/com/stericsson/hardware/fm/IOnStereoListener.aidl
new file mode 100644
index 0000000..ff2c977
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/IOnStereoListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright (C) 2010 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.
+ *
+ * Author: Markus Grape (markus.grape@stericsson.com) for ST-Ericsson
+ */
+
+package com.stericsson.hardware.fm;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnStereoListener
+{
+ void onPlayingInStereo(boolean inStereo);
+} \ No newline at end of file
diff --git a/fmradio/java/com/stericsson/hardware/fm/package.html b/fmradio/java/com/stericsson/hardware/fm/package.html
new file mode 100644
index 0000000..13c4a07
--- /dev/null
+++ b/fmradio/java/com/stericsson/hardware/fm/package.html
@@ -0,0 +1,13 @@
+<HTML>
+<BODY>
+Provides classes to receive and transmit FM Radio.
+<p>
+<lu>
+<li>FM Radio can be received by using the FmReceiver to control the reception and the MediaPlayer
+to play the FM Radio stream.</li>
+<li>FM Radio can be transmitted by using the FmTransmitter to control the output and the MediaPlayer
+to select the audio stream that will be transmitted.</li>
+</lu>
+</p>
+</BODY>
+</HTML>
diff --git a/fmradio/jni/Android.mk b/fmradio/jni/Android.mk
new file mode 100644
index 0000000..1b2643d
--- /dev/null
+++ b/fmradio/jni/Android.mk
@@ -0,0 +1,41 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ android_fmradio.cpp \
+ android_fmradio_Receiver.cpp \
+ android_fmradio_Transmitter.cpp
+
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE)\
+ $(TOP)/frameworks/base/fmradio/include \
+ $(TOP)/hardware/libhardware/include/hardware
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ libhardware \
+ libhardware_legacy \
+ libnativehelper \
+ libsystem_server \
+ libutils \
+ libdl \
+ libui \
+ libmedia \
+
+ifeq ($(TARGET_SIMULATOR),true)
+ifeq ($(TARGET_OS),linux)
+ifeq ($(TARGET_ARCH),x86)
+LOCAL_LDLIBS += -lpthread -ldl -lrt
+endif
+endif
+endif
+
+ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
+ LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
+endif
+
+LOCAL_MODULE:= libanalogradiobroadcasting
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/fmradio/jni/android_fmradio.cpp b/fmradio/jni/android_fmradio.cpp
new file mode 100755
index 0000000..5dd9f72
--- /dev/null
+++ b/fmradio/jni/android_fmradio.cpp
@@ -0,0 +1,1188 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright 2010, 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.
+ *
+ * Author: johan.xj.palmaeus@stericsson.com for ST-Ericsson
+ */
+
+/*
+ * Native part of the generic TX-RX common FmRadio inteface
+ */
+
+
+#define ALOG_TAG "FmServiceNative"
+
+// #define ALOG_NDEBUG 1
+
+#include <dlfcn.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include <utils/Log.h>
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "android_fmradio.h"
+
+#ifndef LIBRARY_PATH
+#define LIBRARY_PATH "/system/lib/"
+#endif
+
+#ifndef LIBRARY_PREFIX
+#define LIBRARY_PREFIX "libfmradio."
+#endif
+
+#ifndef LIBRARY_SUFFIX
+#define LIBRARY_SUFFIX ".so"
+#endif
+
+#ifndef LIBRARY_SUFFIX_RX
+#define LIBRARY_SUFFIX_RX "_rx.so"
+#endif
+
+#ifndef LIBRARY_SUFFIX_TX
+#define LIBRARY_SUFFIX_TX "_tx.so"
+#endif
+
+/* structs for passing startup arguments to threads */
+
+struct FmRadioStartAsyncParameters {
+ int (*startFunc) (void **, const struct fmradio_vendor_callbacks_t*, int, int, int,
+ int);
+ struct FmSession_t *session_p;
+ const struct fmradio_vendor_callbacks_t *callbacks_p;
+ int lowFreq;
+ int highFreq;
+ int defaultFreq;
+ int grid;
+};
+
+struct FmRadioSendExtraCommandParameters {
+ struct FmSession_t *session_p;
+ char* c_command;
+ char ** cparams;
+};
+
+
+pthread_mutex_t rx_tx_common_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+jobject extraCommandRetList2Bundle(JNIEnv * env_p, struct bundle_descriptor_offsets_t
+ *bundleOffsets_p,
+ struct fmradio_extra_command_ret_item_t *itemList)
+{
+ struct fmradio_extra_command_ret_item_t *listIterator = itemList;
+
+ jobject bundle = env_p->NewObject(bundleOffsets_p->mClass,
+ bundleOffsets_p->mConstructor);
+
+ while (listIterator && listIterator->key != NULL) {
+ switch (listIterator->type) {
+ case FMRADIO_TYPE_INT:
+ env_p->CallVoidMethod(bundle, bundleOffsets_p->mPutInt,
+ env_p->NewStringUTF(listIterator->key),
+ listIterator->data.int_value);
+ break;
+ case FMRADIO_TYPE_STRING:
+ env_p->CallVoidMethod(bundle, bundleOffsets_p->mPutString,
+ env_p->NewStringUTF(listIterator->key),
+ env_p->NewStringUTF(listIterator->data.string_value));
+ break;
+ default:
+ ALOGE("warning. Unknown command ret item type %d\n",
+ listIterator->type);
+ break;
+ }
+
+ listIterator++;
+ }
+ return bundle;
+}
+
+void freeExtraCommandRetList(struct fmradio_extra_command_ret_item_t *itemList)
+{
+ struct fmradio_extra_command_ret_item_t *listIterator = itemList;
+
+ while (listIterator && listIterator->key != NULL) {
+ free(listIterator->key);
+ if (listIterator->type == FMRADIO_TYPE_STRING) {
+ free(listIterator->data.string_value);
+ }
+ listIterator++;
+ }
+ free(itemList);
+
+}
+
+static bool jstringArray2cstringArray(JNIEnv * env_p, jobjectArray jarray,
+ char ***carray_p)
+{
+ int arrayLength;
+
+ if (jarray != NULL &&
+ (arrayLength = env_p->GetArrayLength(jarray)) > 0) {
+ int d;
+
+ char **carray = (char **) calloc(arrayLength + 1, sizeof(*carray));
+
+ if (carray == NULL) {
+ ALOGE("malloc failed\n");
+ return false;
+ }
+
+ for (d = 0; d < arrayLength; d++) {
+ const char *itemstr = env_p->GetStringUTFChars((jstring)
+ env_p->GetObjectArrayElement
+ (jarray, d),
+ NULL);
+
+ carray[d] = strdup(itemstr);
+ env_p->ReleaseStringUTFChars((jstring)
+ env_p->GetObjectArrayElement
+ (jarray, d), itemstr);
+ if (carray[d] == NULL) {
+ ALOGE("strdup failed\n");
+ /* free any arrays already allocated, free the array pointer
+ * and exit */
+ while (--d >= 0) {
+ free(carray[d]);
+ }
+ free(carray);
+ return false;
+ }
+ }
+ carray[arrayLength] = NULL; /* to be able to detect end of array */
+ *carray_p = carray;
+ } else {
+ *carray_p = NULL;
+ }
+
+ return true;
+}
+
+static void freeCstringArray(char **cstringarray)
+{
+ if (cstringarray != NULL && *cstringarray != NULL) {
+ char **iterator = cstringarray;
+
+ while (*iterator != NULL) {
+ free(*iterator);
+ iterator++;
+ }
+ free(cstringarray);
+
+ }
+}
+
+/*
+ * Function to temporary resume a paused device for executing command
+ */
+void androidFmRadioTempResumeIfPaused(struct FmSession_t *session_p)
+{
+ if (session_p->state == FMRADIO_STATE_PAUSED) {
+ /* no need to handle error here, the main command will fail anyway */
+ (void)session_p->vendorMethods_p->resume(&session_p->vendorData_p);
+ }
+}
+
+/*
+ * Function to pause device after a temporary resume
+ */
+void androidFmRadioPauseIfTempResumed(struct FmSession_t *session_p)
+{
+ if (session_p->state == FMRADIO_STATE_PAUSED) {
+ if (session_p->vendorMethods_p->pause(&session_p->vendorData_p)
+ != FMRADIO_OK) {
+ ALOGE("androidFmRadioPauseIfTempResumed: pause failed, force "
+ "resetting");
+ /*
+ * Failed to set pause again. Since the device is supposed to be
+ * paused we can't leave it this way, our best choice is to issue
+ * a force reset to put the device in a known idle state. Temporary
+ * drop locks since we might trigger callbacks.
+ */
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_IDLE);
+ /* temporary drop lock to allow reset triggered callbacks */
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ int retval = session_p->vendorMethods_p->reset(&session_p->vendorData_p);
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ if (retval != FMRADIO_OK) {
+ ALOGE("androidFmRadioPauseIfTempResumed: CRITICAL ERROR: "
+ "can't reset device");
+ /* if we can't even reset we have a critical problem */
+ session_p->callbacks_p->onForcedReset(FMRADIO_RESET_CRITICAL);
+ } else {
+ /* now we are in known state */
+ session_p->callbacks_p->onForcedReset(FMRADIO_RESET_NON_CRITICAL);
+ }
+
+ /* unload vendor driver */
+ androidFmRadioUnLoadFmLibrary(session_p);
+ session_p->isRegistered = false;
+ }
+ }
+}
+
+bool
+androidFmRadioIsValidEventForState(struct FmSession_t *session_p,
+ enum FmRadioCommand_t event)
+{
+ bool retval;
+
+ if (!session_p->isRegistered && (event != FMRADIO_EVENT_RESET) &&
+ (event != FMRADIO_EVENT_START) && (event != FMRADIO_EVENT_START_ASYNC)) {
+ ALOGE("ERROR - library not loaded, only reset, start and start async are valid events");
+ retval = false;
+ } else if (session_p->ongoingReset && (event != FMRADIO_EVENT_RESET)) {
+ ALOGE("ERROR - ongoing reset invalidates state changes");
+ retval = false;
+ } else if (!(*session_p->validEventsForStates_p)[event][session_p->state]) {
+ ALOGE("ERROR - Invalid event %u for state %u", event,
+ session_p->state);
+ retval = false;
+ } else {
+ retval = true;
+ }
+
+ return retval;
+}
+
+void
+androidFmRadioThrowException(struct FmSession_t *session_p,
+ const char *exception,
+ const char *message, const char *file,
+ int line, const char *function)
+{
+ bool reAttached = false;
+
+ JNIEnv *env;
+
+ ALOGI("androidFmRadioThrowException, %s ('%s') @ %s %d (%s)\n",
+ exception, message, file, line, function);
+
+
+ if (session_p->jvm_p->GetEnv((void **) &env, JNI_VERSION_1_4) !=
+ JNI_OK) {
+ /* we are probably a subthread. Attach instead */
+ if (session_p->jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread\n");
+ return;
+ }
+ reAttached = true;
+ }
+
+ jniThrowException(env, exception, message);
+
+ if (reAttached) {
+ session_p->jvm_p->DetachCurrentThread();
+ }
+
+}
+
+
+/*
+ * Functions for loading and unloading the vendor dynamic library
+ * (libfmradio.xxxxxx.so)
+ */
+
+static bool
+androidFmRadioGetLibraryName(enum RadioMode_t mode,
+ char *libraryName)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ char foundName[sizeof(dp->d_name)] = "";
+
+ if ((dirp = opendir(LIBRARY_PATH)) == NULL) {
+ ALOGE("couldn't open path '%s'", LIBRARY_PATH);
+ return false;
+ }
+
+ errno = 0;
+
+ while ((dp = readdir(dirp)) != NULL) {
+ char *name = dp->d_name;
+ size_t nameLength = strlen(name);
+
+ /*
+ * since prefix end with . and suffix start with . we need to make sure
+ * we don't get a match on something that is shorter than the two
+ * strings concatinated
+ */
+ if (nameLength < sizeof(LIBRARY_PREFIX) + sizeof(LIBRARY_SUFFIX) - 2) {
+ continue;
+ }
+
+ if ((nameLength > sizeof(LIBRARY_SUFFIX) -1) &&
+ (strcmp(name + nameLength - sizeof(LIBRARY_SUFFIX) + 1,
+ LIBRARY_SUFFIX) == 0)) {
+ } else {
+ continue;
+ }
+
+ if (strncmp(name, LIBRARY_PREFIX, sizeof(LIBRARY_PREFIX) - 1) != 0) {
+ /* prefix doesn't match, this is not our file */
+ continue;
+ }
+
+
+ /* check if it is RX/TX specific or general */
+
+ if ((nameLength > sizeof(LIBRARY_SUFFIX_RX) -1) &&
+ (strcmp(name + nameLength - sizeof(LIBRARY_SUFFIX_RX) + 1,
+ LIBRARY_SUFFIX_RX) == 0)) {
+ if (mode == FMRADIO_RX) {
+ strcpy(foundName, name);
+ break;
+ }
+ } else if ((nameLength > sizeof(LIBRARY_SUFFIX_TX) -1) &&
+ (strcmp(name + nameLength - sizeof(LIBRARY_SUFFIX_TX) + 1,
+ LIBRARY_SUFFIX_TX) == 0)) {
+ if (mode == FMRADIO_TX) {
+ strcpy(foundName, name);
+ break;
+ }
+ } else {
+ strcpy(foundName, name);
+ /* do not break, if there is a rx/tx specific name we prefer it */
+ }
+ }
+
+ (void)closedir(dirp);
+
+ if (dp == NULL && errno != 0) {
+ ALOGE("error reading directory, errno = %d\n", errno);
+ return false;
+ }
+
+ if (foundName[0] == '\0') {
+ ALOGE("No matching library found in path %s\n", LIBRARY_PATH);
+ return false;
+ }
+
+ // copy name with file path
+ strcpy(libraryName, LIBRARY_PATH);
+ // add slash if not last of path
+ if (libraryName[strlen(libraryName) -1] != '/') {
+ strcat(libraryName, "/");
+ }
+ strcat(libraryName, foundName);
+ return true;
+}
+
+bool
+androidFmRadioLoadFmLibrary(struct FmSession_t * session_p,
+ enum RadioMode_t mode)
+{
+ char fmLibName[FM_LIBRARY_NAME_MAX_LENGTH + 1] = "";
+ fmradio_reg_func_t fmRegFunc = NULL;
+ unsigned int magicVal = 0;
+ bool retval = false;
+ bool missingMandatoryFunctions = false;
+
+ // read library directory and find matching library
+
+ if (!androidFmRadioGetLibraryName(mode, fmLibName)) {
+ goto funcret;
+ }
+
+ // load the library
+ session_p->fmLibrary_p = dlopen(fmLibName, RTLD_LAZY);
+ if (session_p->fmLibrary_p == NULL) {
+ ALOGI("Could not load library '%s'\n", fmLibName);
+ goto funcret;
+ } else {
+ ALOGI("Loaded library %s\n", fmLibName);
+ }
+
+ // now we have loaded the library, check for function
+ fmRegFunc = (fmradio_reg_func_t) dlsym(session_p->fmLibrary_p,
+ FMRADIO_REGISTER_FUNC);
+
+ if (fmRegFunc == NULL) {
+ ALOGE("Could not find symbol '%s' in loaded library '%s'\n",
+ FMRADIO_REGISTER_FUNC, fmLibName);
+ goto closelib;
+ }
+
+ // call function
+ if (fmRegFunc(&magicVal, session_p->vendorMethods_p) != 0) {
+ ALOGE("Loaded function '%s' returned unsuccessful\n",
+ FMRADIO_REGISTER_FUNC);
+ goto closelib;
+ }
+
+ // just to make sure correct function was called
+ if (magicVal != FMRADIO_SIGNATURE) {
+ ALOGE("Loaded function '%s' returned successful but failed setting magic value\n", FMRADIO_REGISTER_FUNC);
+ goto closelib;
+ }
+
+ // some methods are considered mandatory to implement, check them
+
+ if(mode == FMRADIO_RX && session_p->vendorMethods_p->rx_start == NULL) {
+ missingMandatoryFunctions = true;
+ ALOGE("Mandatory method rx_start is not implemented\n");
+ }
+ if(mode == FMRADIO_TX && session_p->vendorMethods_p->tx_start == NULL) {
+ missingMandatoryFunctions = true;
+ ALOGE("Mandatory method tx_start is not implemented\n");
+ }
+ if(session_p->vendorMethods_p->pause == NULL) {
+ missingMandatoryFunctions = true;
+ ALOGE("Mandatory method pause is not implemented\n");
+ }
+ if(session_p->vendorMethods_p->resume == NULL) {
+ missingMandatoryFunctions = true;
+ ALOGE("Mandatory method resume is not implemented\n");
+ }
+ if(session_p->vendorMethods_p->reset == NULL) {
+ missingMandatoryFunctions = true;
+ ALOGE("Mandatory method reset is not implemented\n");
+ }
+ if(missingMandatoryFunctions) {
+ goto closelib;
+ }
+
+ retval = true;
+ goto funcret;
+ closelib:
+ dlclose(session_p->fmLibrary_p);
+ funcret:
+ return retval;
+}
+
+void
+androidFmRadioUnLoadFmLibrary(struct FmSession_t * session_p)
+{
+
+ if (session_p->fmLibrary_p != NULL) {
+ dlclose(session_p->fmLibrary_p);
+ free(session_p->vendorMethods_p);
+ session_p->vendorMethods_p = NULL;
+ }
+}
+
+/* methods common for both RX and TX */
+
+static bool androidFmRadioStartSyncPartner(struct FmSession_t *session_p)
+{
+ /* lock is held when entering this mehod */
+ int maxTime = 5000; /* in ms */
+
+ /* if partner has ongoing reset await it finishing */
+ while (session_p->partnerSession_p->ongoingReset && maxTime > 0) {
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ usleep(250000);
+ pthread_mutex_lock(session_p->dataMutex_p);
+ maxTime -= 250;
+ }
+
+ if (session_p->partnerSession_p->ongoingReset) {
+ ALOGE("partner session stale reset");
+ return false;
+ }
+
+ /* if partner has any other state than IDLE it should be stoped */
+ if (session_p->partnerSession_p->state != FMRADIO_STATE_IDLE) {
+ /* if partner is starting it must be allowed to finish */
+
+ session_p->partnerSession_p->ongoingReset = true; /* to make sure it doesn't do anything else */
+
+ while (!session_p->ongoingReset &&
+ (session_p->partnerSession_p->state == FMRADIO_STATE_STARTING && maxTime > 0)){
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ usleep(250000);
+ pthread_mutex_lock(session_p->dataMutex_p);
+ maxTime -= 250;
+ }
+
+ session_p->partnerSession_p->ongoingReset = false;
+
+ /* if we now have a ongoing reset on our own session just exit */
+
+ if (session_p->ongoingReset) {
+ return false;
+ }
+
+ /* if partner is still starting we should exist */
+ if (session_p->partnerSession_p->state == FMRADIO_STATE_STARTING) {
+ ALOGE("time-out waiting for partner startup");
+ return false;
+ }
+
+ /* unless partner already is IDLE, reset and send forced reset */
+ if (session_p->partnerSession_p->state != FMRADIO_STATE_IDLE) {
+ /* temporary drop lock to allow reset triggered callbacks */
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ session_p->partnerSession_p->vendorMethods_p->reset(&session_p->partnerSession_p->vendorData_p);
+ pthread_mutex_lock(session_p->dataMutex_p);
+ FMRADIO_SET_STATE(session_p->partnerSession_p, FMRADIO_STATE_IDLE);
+ session_p->partnerSession_p->callbacks_p->onForcedReset(FMRADIO_RESET_OTHER_IN_USE);
+ }
+ /* unload partner vendor driver */
+ if (session_p->partnerSession_p->isRegistered) {
+ androidFmRadioUnLoadFmLibrary(session_p->partnerSession_p);
+ session_p->partnerSession_p->isRegistered = false;
+ }
+ }
+
+ return true;
+}
+
+static void *execute_androidFmRadioStartAsync(void *args)
+{
+ struct FmRadioStartAsyncParameters *inArgs_p = (struct FmRadioStartAsyncParameters *)args;
+
+ int (*startFunc) (void **, const struct fmradio_vendor_callbacks_t *, int, int, int,
+ int) = inArgs_p->startFunc;
+ struct FmSession_t *session_p = inArgs_p->session_p;
+
+ const struct fmradio_vendor_callbacks_t *callbacks_p = inArgs_p->callbacks_p;
+
+ int lowFreq = inArgs_p->lowFreq;
+ int highFreq = inArgs_p->highFreq;
+ int defaultFreq = inArgs_p->defaultFreq;
+ int grid = inArgs_p->grid;
+ int retval = 0;
+
+ free(inArgs_p);
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ /*
+ * if other session is starting should wait for them to finish unless we
+ * get a ongoing reset
+ */
+
+ if (!androidFmRadioStartSyncPartner(session_p)) {
+ retval = -1;
+ goto fix_state_and_send_retval;
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+
+ retval =
+ startFunc(&session_p->vendorData_p, callbacks_p, lowFreq, highFreq,
+ defaultFreq, grid);
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+ /* sanity check, not even reset should alter the state when starting */
+ if (session_p->state != FMRADIO_STATE_STARTING) {
+ ALOGE("state not starting when going to started...");
+ retval = -1;
+ }
+
+ fix_state_and_send_retval:
+ if (retval >= 0) {
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_STARTED);
+ } else {
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_IDLE);
+ /* unload vendor driver */
+ androidFmRadioUnLoadFmLibrary(session_p);
+ session_p->isRegistered = false;
+ }
+
+ /*
+ * these need to be called after state is updated and after the lock
+ * has been dropped
+ */
+ if (retval >= 0) {
+ session_p->callbacks_p->onStarted();
+ } else {
+ session_p->callbacks_p->onError();
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+
+ pthread_exit(NULL);
+ return NULL;
+}
+
+int
+androidFmRadioStart(struct FmSession_t *session_p, enum RadioMode_t mode,
+ const struct fmradio_vendor_callbacks_t *callbacks_p,
+ bool async, int lowFreq, int highFreq, int defaultFreq,
+ int grid)
+{
+ int retval = 0;
+
+ int (*startFunc) (void **, const struct fmradio_vendor_callbacks_t *,
+ int, int, int, int) = NULL;
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+ if (!androidFmRadioIsValidEventForState
+ (session_p, FMRADIO_EVENT_START)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+ // set our state to STARTING here to make sure the partner session
+ // can't start again after it is finished
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_STARTING);
+
+ // if we haven't registred the library yet do it
+
+ if (!session_p->isRegistered) {
+ session_p->vendorMethods_p = (fmradio_vendor_methods_t *)
+ malloc(sizeof(*session_p->vendorMethods_p));
+ if (session_p->vendorMethods_p == NULL) {
+ ALOGE("malloc failed");
+ retval = FMRADIO_IO_ERROR;
+ goto early_exit;
+ } else if (androidFmRadioLoadFmLibrary(session_p, mode)) {
+ session_p->isRegistered = true;
+ } else {
+ ALOGE("vendor registration failed");
+ free(session_p->vendorMethods_p);
+ retval = FMRADIO_IO_ERROR;
+ goto early_exit;
+ }
+ }
+
+ if (mode == FMRADIO_RX) {
+ startFunc = session_p->vendorMethods_p->rx_start;
+ } else if (mode == FMRADIO_TX) {
+ startFunc = session_p->vendorMethods_p->tx_start;
+ }
+
+ if (!startFunc) {
+ ALOGE("androidFmRadioStart - ERROR - No valid start function found.");
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ goto early_exit;
+ }
+
+ if (async) {
+ pthread_t execute_thread;
+
+ struct FmRadioStartAsyncParameters *args_p = (struct FmRadioStartAsyncParameters *)
+ malloc(sizeof(struct FmRadioStartAsyncParameters)); /* freed in created thread */
+
+ if (args_p == NULL) {
+ ALOGE("malloc failed");
+ retval = FMRADIO_IO_ERROR;
+ goto early_exit;
+ }
+
+ args_p->startFunc = startFunc;
+ args_p->session_p = session_p;
+ args_p->callbacks_p = callbacks_p;
+ args_p->lowFreq = lowFreq;
+ args_p->highFreq = highFreq;
+ args_p->defaultFreq = defaultFreq;
+ args_p->grid = grid;
+
+ // we need to create a new thread actually executing the command
+ if (pthread_create
+ (&execute_thread, NULL, execute_androidFmRadioStartAsync,
+ (void *) args_p) != 0) {
+ ALOGE("pthread_create failure...");
+ free(args_p);
+ retval = FMRADIO_IO_ERROR;
+ } else {
+ pthread_detach(execute_thread);
+ }
+ } else {
+ if (!androidFmRadioStartSyncPartner(session_p)) {
+ retval = -1;
+ goto early_exit;
+ }
+
+ /*
+ * drop lock during long time call but set state to make sure no other
+ * process tries to start while we are starting. Do not use
+ * FMRADIO_SET_STATE macro since it will trigger a onStateChanged
+ * callback
+ */
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ retval =
+ startFunc(&session_p->vendorData_p, callbacks_p, lowFreq,
+ highFreq, defaultFreq, grid);
+ /* regain lock */
+ pthread_mutex_lock(session_p->dataMutex_p);
+ /* check that nothing has happened before we regained the lock */
+ if (session_p->state != FMRADIO_STATE_STARTING) {
+ ALOGE("Error, radio not in IDLE when about to set started mode");
+ }
+ }
+
+ // if successful syncronous start update state
+ if (retval == 0 && !async) {
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_STARTED);
+ }
+
+ early_exit:
+
+ if (retval < 0) {
+ if (session_p->state != FMRADIO_STATE_IDLE) {
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_IDLE);
+ }
+
+ if (retval != FMRADIO_INVALID_STATE && session_p->isRegistered) {
+ androidFmRadioUnLoadFmLibrary(session_p);
+ session_p->isRegistered = false;
+ }
+ }
+
+ drop_lock:
+
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(session_p);
+ } else if ((retval == FMRADIO_INVALID_PARAMETER) ||
+ (retval == FMRADIO_UNSUPPORTED_OPERATION)) {
+ THROW_UNSUPPORTED_OPERATION(session_p);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(session_p);
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+
+ return retval;
+}
+
+int androidFmRadioPause(struct FmSession_t *session_p)
+{
+ int retval;
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+ if (!androidFmRadioIsValidEventForState
+ (session_p, FMRADIO_EVENT_PAUSE)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (session_p->state == FMRADIO_STATE_PAUSED) {
+ // already paused, just return
+ retval = FMRADIO_OK;
+ goto drop_lock;
+ }
+
+
+ retval = session_p->vendorMethods_p->pause(&session_p->vendorData_p);
+
+ if (retval == 0) {
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_PAUSED);
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(session_p);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(session_p);
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+
+ return retval;
+}
+
+int androidFmRadioResume(struct FmSession_t *session_p)
+{
+ int retval = 0;
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (session_p, FMRADIO_EVENT_RESUME)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (session_p->state == FMRADIO_STATE_STARTED) {
+ //already started, just return
+ retval = FMRADIO_OK;
+ goto drop_lock;
+ }
+
+ retval = session_p->vendorMethods_p->resume(&session_p->vendorData_p);
+
+ // if successful update state
+ if (retval == 0) {
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_STARTED);
+ }
+ // nothing on failure
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(session_p);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(session_p);
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ return retval;
+}
+
+int androidFmRadioReset(struct FmSession_t *session_p)
+{
+ int retval = FMRADIO_OK;
+ int oldState = session_p->state;
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (session_p, FMRADIO_EVENT_RESET)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ /* Worker threads must be cleaned up before sending reset */
+ if(session_p->state == FMRADIO_STATE_SCANNING){
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ androidFmRadioStopScan(session_p);
+ pthread_mutex_lock(session_p->dataMutex_p);
+ /* Waiting for worker thread to exit gracefully */
+ pthread_cond_wait(&session_p->sync_cond,
+ session_p->dataMutex_p);
+ }
+ /* idle or about to be reset, just return state */
+ if (session_p->ongoingReset || oldState == FMRADIO_STATE_IDLE) {
+ retval = oldState;
+ goto drop_lock;
+ }
+ session_p->ongoingReset = true;
+
+ /* if we are in starting state we must await the start finishing */
+ if (oldState == FMRADIO_STATE_STARTING) {
+ /* we need to await end of start before starting */
+ int maxTime = 5000; /* in ms */
+ do {
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ usleep(250000);
+ pthread_mutex_lock(session_p->dataMutex_p);
+ maxTime -= 250;
+ } while (maxTime > 0 && (session_p->state == FMRADIO_STATE_STARTING));
+ }
+
+ /* if we still are in STARTING state we must fail now */
+ if (session_p->state == FMRADIO_STATE_STARTING) {
+ retval = FMRADIO_IO_ERROR;
+ goto drop_ongoing_reset;
+ }
+
+ /*
+ * we need to temporary release lock since reset might trigger
+ * callbacks. Set flag to not allow any state changing command
+ */
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_IDLE);
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ retval = session_p->vendorMethods_p->
+ reset(&session_p->vendorData_p);
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ // if successful unload vendor driver
+ if (retval >= 0) {
+ retval = oldState;
+ if (session_p->isRegistered) {
+ androidFmRadioUnLoadFmLibrary(session_p);
+ session_p->isRegistered = false;
+ }
+ } else {
+ ALOGE("androidFmRadioReset failed");
+ }
+ // nothing on failure
+ drop_ongoing_reset:
+ session_p->ongoingReset = false;
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(session_p);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(session_p);
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+
+ return retval;
+}
+
+void
+androidFmRadioSetFrequency(struct FmSession_t *session_p, int frequency)
+{
+ int retval = 0;
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+ if (!androidFmRadioIsValidEventForState
+ (session_p, FMRADIO_EVENT_SET_FREQUENCY)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (session_p->vendorMethods_p->set_frequency) {
+ /* if in pause state temporary resume */
+ androidFmRadioTempResumeIfPaused(session_p);
+
+ retval =
+ session_p->vendorMethods_p->set_frequency(&session_p->
+ vendorData_p,
+ frequency);
+
+ androidFmRadioPauseIfTempResumed(session_p);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ // no state is ever updated
+ if (retval < 0) {
+ ALOGE("androidFmRadioSetFrequency failed\n");
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(session_p);
+ } else if (retval == FMRADIO_INVALID_PARAMETER) {
+ THROW_ILLEGAL_ARGUMENT(session_p);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(session_p);
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+}
+
+int androidFmRadioGetFrequency(struct FmSession_t *session_p)
+{
+ int retval = 0;
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (session_p, FMRADIO_EVENT_GET_FREQUENCY)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (session_p->vendorMethods_p->get_frequency) {
+ /* if in pause state temporary resume */
+ androidFmRadioTempResumeIfPaused(session_p);
+
+ retval =
+ session_p->vendorMethods_p->get_frequency(&session_p->
+ vendorData_p);
+
+ androidFmRadioPauseIfTempResumed(session_p);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(session_p);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(session_p);
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+
+ return retval;
+}
+
+void androidFmRadioStopScan(struct FmSession_t *session_p)
+{
+ int retval = 0;
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (session_p, FMRADIO_EVENT_STOP_SCAN)) {
+ goto drop_lock;
+ }
+
+ if (session_p->state != FMRADIO_STATE_SCANNING) {
+ /* if we're not in scanning, don't attempt anything just return */
+ goto drop_lock;
+ }
+
+ if (session_p->vendorMethods_p->stop_scan) {
+ retval =
+ session_p->vendorMethods_p->stop_scan(&session_p->vendorData_p);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ if (retval == 0) {
+ session_p->lastScanAborted = true;
+ }
+
+ retval = FMRADIO_OK;
+ drop_lock:
+ /* note - no exceptions. Just return */
+
+ if (retval != FMRADIO_OK) {
+ ALOGE("androidFmRadioStopScan failed (%d), ignored.\n", retval);
+ }
+ pthread_mutex_unlock(session_p->dataMutex_p);
+}
+
+static void *execute_androidFmRadioSendExtraCommand(void *args_p)
+{
+ struct FmRadioSendExtraCommandParameters* inArgs_p = (struct FmRadioSendExtraCommandParameters*)args_p;
+ struct FmSession_t *session_p = inArgs_p->session_p;
+ char *c_command = inArgs_p->c_command;
+ char **parameter = inArgs_p->cparams;
+ struct fmradio_extra_command_ret_item_t *returnParam = NULL;
+ int retval;
+
+ free(inArgs_p);
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ // we should be in state EXTRA_COMMAND
+ if (session_p->state != FMRADIO_STATE_EXTRA_COMMAND) {
+ ALOGE("execute_androidFmRadioSendExtraCommand - warning, state not extra commands\n");
+ }
+
+ if (session_p->vendorMethods_p->send_extra_command != NULL) {
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ retval =
+ session_p->vendorMethods_p->send_extra_command(&session_p->
+ vendorData_p,
+ c_command,
+ parameter,
+ &returnParam);
+ pthread_mutex_lock(session_p->dataMutex_p);
+ freeCstringArray(parameter);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ if (session_p->state != FMRADIO_STATE_EXTRA_COMMAND) {
+ ALOGE("State changed while executing extra commands (state now %d), keeping\n", session_p->state);
+ } else {
+ if (session_p->pendingPause) {
+ session_p->vendorMethods_p->pause(&session_p->
+ vendorData_p);
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_PAUSED);
+ } else {
+ FMRADIO_SET_STATE(session_p, session_p->oldState);
+ }
+ }
+
+ session_p->pendingPause = false;
+
+ if (retval >= 0) {
+ session_p->callbacks_p->onSendExtraCommand(c_command, returnParam);
+ } else {
+ session_p->callbacks_p->onError();
+ }
+
+ if (returnParam != NULL) {
+ freeExtraCommandRetList(returnParam);
+ }
+
+ if (c_command != NULL) {
+ free(c_command);
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+ pthread_exit(NULL);
+ return NULL;
+}
+
+void
+androidFmRadioSendExtraCommand(struct FmSession_t *session_p,
+ JNIEnv * env_p, jstring jcommand,
+ jobjectArray parameter)
+{
+ int retval = FMRADIO_OK;
+ char **cparams;
+ struct FmRadioSendExtraCommandParameters *args_p = NULL;
+ pthread_t execute_thread;
+
+ pthread_mutex_lock(session_p->dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (session_p, FMRADIO_EVENT_EXTRA_COMMAND)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (!jstringArray2cstringArray(env_p, parameter, &cparams)) {
+ retval = FMRADIO_IO_ERROR;
+ goto drop_lock;
+ }
+
+ session_p->oldState = session_p->state;
+
+ args_p = (struct FmRadioSendExtraCommandParameters *) malloc(sizeof(struct FmRadioSendExtraCommandParameters)); /* freed in created thread */
+ if (args_p == NULL) {
+ ALOGE("malloc failed\n");
+ retval = FMRADIO_IO_ERROR;
+ }
+
+ if (retval == FMRADIO_OK) {
+ const char* c_command = env_p->GetStringUTFChars(jcommand, 0);
+
+ args_p->session_p = session_p;
+ args_p->c_command = strdup(c_command);
+ args_p->cparams = cparams;
+
+ env_p->ReleaseStringUTFChars(jcommand, c_command);
+
+ // we need to create a new thread actually executing the command
+ if (pthread_create
+ (&execute_thread, NULL, execute_androidFmRadioSendExtraCommand,
+ args_p) != 0) {
+ ALOGE("pthread_create failed\n");
+ free(args_p->c_command);
+ free(args_p);
+ retval = FMRADIO_IO_ERROR;
+ } else {
+ pthread_detach(execute_thread);
+ }
+
+ }
+
+ if (retval == FMRADIO_OK) {
+ FMRADIO_SET_STATE(session_p, FMRADIO_STATE_EXTRA_COMMAND);
+ }
+
+ drop_lock:
+
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(session_p);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(session_p);
+ }
+
+ pthread_mutex_unlock(session_p->dataMutex_p);
+}
+
+
+namespace android {
+int registerAndroidFmRadioReceiver(JavaVM* vm, JNIEnv *env);
+int registerAndroidFmRadioTransmitter(JavaVM* vm, JNIEnv *env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("GetEnv failed!");
+ return result;
+ }
+ ALOG_ASSERT(env, "Could not retrieve the env!");
+
+ registerAndroidFmRadioReceiver(vm, env);
+ registerAndroidFmRadioTransmitter(vm, env);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/fmradio/jni/android_fmradio_Receiver.cpp b/fmradio/jni/android_fmradio_Receiver.cpp
new file mode 100755
index 0000000..78e3e16
--- /dev/null
+++ b/fmradio/jni/android_fmradio_Receiver.cpp
@@ -0,0 +1,1494 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright 2010, 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.
+ *
+ * Authors: johan.xj.palmaeus@stericsson.com
+ * stuart.macdonald@stericsson.com
+ * for ST-Ericsson
+ */
+
+/*
+ * Native part of the generic RX FmRadio inteface
+ */
+
+#define ALOG_TAG "FmReceiverServiceNative"
+
+// #define LOG_NDEBUG 1
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <termios.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <pthread.h>
+#include <math.h>
+
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_fmradio.h"
+#include <utils/Log.h>
+
+
+/* *INDENT-OFF* */
+namespace android {
+
+
+// state machine
+
+static const ValidEventsForStates_t IsValidRxEventForState = {
+ /* this table defines valid transitions. (turn off indent, we want this easy readable) */
+ /* FMRADIO_STATE_ IDLE,STARTING,STARTED,PAUSED,SCANNING,EXTRA_COMMAND */
+
+ /* FMRADIO_EVENT_START */ {true ,false,false,false,false,false},
+ /* FMRADIO_EVENT_START_ASYNC */ {true ,false,false,false,false,false},
+ /* FMRADIO_EVENT_PAUSE */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_RESUME */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_RESET */ {true, true, true, true, true, true },
+ /* FMRADIO_EVENT_GET_FREQUENCY */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_SET_FREQUENCY */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_SET_PARAMETER */ {false,false,true, true, true, true },
+ /* FMRADIO_EVENT_STOP_SCAN */ {true, true, true, true, true, true },
+ /* FMRADIO_EVENT_EXTRA_COMMAND */ {true, true, true, true, true, true },
+ /* Rx Only */
+ /* FMRADIO_EVENT_GET_PARAMETER */ {false,false,true, true, true, true },
+ /* FMRADIO_EVENT_GET_SIGNAL_STRENGTH */{false,false,true,true,false,false},
+ /* FMRADIO_EVENT_SCAN */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_FULL_SCAN */ {false,false,true, true, false,false},
+ // Tx Only - never allowed
+ /* FMRADIO_EVENT_BLOCK_SCAN */ {false,false,false,false,false,false},
+};
+/* *INDENT-ON* */
+
+/* Callbacks to java layer */
+
+static void androidFmRadioRxCallbackOnStateChanged(int oldState,
+ int newState);
+static void androidFmRadioRxCallbackOnError(void);
+
+static void androidFmRadioRxCallbackOnStarted(void);
+
+static void androidFmRadioRxCallbackOnScan(int foundFreq,
+ int signalStrength,
+ int scanDirection,
+ bool aborted);
+static void androidFmRadioRxCallbackOnFullScan(int noItems,
+ int *frequencies,
+ int *sigStrengths,
+ bool aborted);
+static void androidFmRadioRxCallbackOnForcedReset(enum fmradio_reset_reason_t reason);
+
+static void androidFmRadioRxCallbackOnVendorForcedReset(enum fmradio_reset_reason_t reason);
+
+static void androidFmRadioRxCallbackOnSignalStrengthChanged(int newLevel);
+
+static void androidFmRadioRxCallbackOnRDSDataFound(struct
+ fmradio_rds_bundle_t
+ *t, int frequency);
+
+static void androidFmRadioRxCallbackOnPlayingInStereo(int
+ isPlayingInStereo);
+
+static void androidFmRadioRxCallbackOnExtraCommand(char* command,
+ struct
+ fmradio_extra_command_ret_item_t
+ *retItem);
+
+static void androidFmRadioRxCallbackOnAutomaticSwitch(int newFrequency, enum fmradio_switch_reason_t reason);
+
+static const FmRadioCallbacks_t FmRadioRxCallbacks = {
+ androidFmRadioRxCallbackOnStateChanged,
+ androidFmRadioRxCallbackOnError,
+ androidFmRadioRxCallbackOnStarted,
+ androidFmRadioRxCallbackOnScan,
+ androidFmRadioRxCallbackOnFullScan,
+ NULL,
+ androidFmRadioRxCallbackOnForcedReset,
+ androidFmRadioRxCallbackOnExtraCommand,
+};
+
+/* callbacks from vendor layer */
+
+static const fmradio_vendor_callbacks_t FmRadioRxVendorCallbacks = {
+ androidFmRadioRxCallbackOnPlayingInStereo,
+ androidFmRadioRxCallbackOnRDSDataFound,
+ androidFmRadioRxCallbackOnSignalStrengthChanged,
+ androidFmRadioRxCallbackOnAutomaticSwitch,
+ androidFmRadioRxCallbackOnVendorForcedReset
+};
+
+extern struct FmSession_t fmTransmitterSession;
+
+struct FmSession_t fmReceiverSession = {
+ NULL,
+ NULL,
+ false,
+ FMRADIO_STATE_IDLE,
+ NULL,
+ &IsValidRxEventForState,
+ &FmRadioRxCallbacks,
+ NULL,
+ NULL,
+ &fmTransmitterSession,
+ NULL,
+ FMRADIO_STATE_IDLE,
+ false,
+ false,
+ false,
+ &rx_tx_common_mutex,
+ PTHREAD_COND_INITIALIZER,
+ NULL,
+};
+
+// make sure we don't refer the TransmitterSession anymore from here
+#define fmTransmitterSession ERRORDONOTUSERECEIVERSESSIONINTRANSMITTER
+
+/*
+* Implementation of callbacks from within service layer. For these the
+* mutex lock is always held on entry and need to be released before doing
+* calls to java layer (env->Call*Method) becasue these might trigger new
+* calls from java and a deadlock would occure if lock was still held.
+*/
+
+static void androidFmRadioRxCallbackOnStateChanged(int oldState,
+ int newState)
+{
+ jmethodID notifyOnStateChangedMethod;
+ JNIEnv *env;
+ jclass clazz;
+ bool reAttached = false;
+
+ ALOGI("androidFmRadioRxCallbackOnStateChanged: Old state %d, new state %d", oldState, newState);
+
+ /* since we might be both in main thread and subthread both test getenv
+ * and attach */
+ if (fmReceiverSession.jvm_p->GetEnv((void **) &env, JNI_VERSION_1_4) !=
+ JNI_OK) {
+ reAttached = true;
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attach current thread");
+ return;
+ }
+ }
+
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+
+ notifyOnStateChangedMethod =
+ env->GetMethodID(clazz, "notifyOnStateChanged", "(II)V");
+ if (notifyOnStateChangedMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj,
+ notifyOnStateChangedMethod, oldState,
+ newState);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ } else {
+ ALOGE("ERROR - JNI can't find java notifyOnStateChanged method");
+ }
+
+ if (reAttached) {
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+ }
+}
+
+static void androidFmRadioRxCallbackOnError(void)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+
+ ALOGI("androidFmRadioRxCallbackOnError");
+
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+ notifyMethod = env->GetMethodID(clazz, "notifyOnError", "()V");
+
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ } else {
+ ALOGE("ERROR - JNI can't find java notifyOnError method");
+ }
+
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+}
+
+static void androidFmRadioRxCallbackOnStarted(void)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+
+ ALOGI("androidFmRadioRxCallbackOnStarted");
+
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+ notifyMethod = env->GetMethodID(clazz, "notifyOnStarted", "()V");
+
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ } else {
+ ALOGE("ERROR - JNI can't find java notifyOnStarted method");
+ }
+
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+}
+
+
+static void androidFmRadioRxCallbackOnScan(int foundFreq,
+ int signalStrength,
+ int scanDirection,
+ bool aborted)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+
+ ALOGI("androidFmRadioRxCallbackOnScan: Callback foundFreq %d, signalStrength %d,"
+ " scanDirection %d, aborted %u", foundFreq, signalStrength, scanDirection,
+ aborted);
+
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+
+ notifyMethod = env->GetMethodID(clazz, "notifyOnScan", "(IIIZ)V");
+
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod, foundFreq, signalStrength,
+ scanDirection, aborted);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ } else {
+ ALOGE("ERROR - JNI can't find java notifyOnScan method");
+ }
+
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+}
+
+static void androidFmRadioRxCallbackOnFullScan(int noItems,
+ int *frequencies,
+ int *sigStrengths,
+ bool aborted)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+ jintArray jFreqs;
+ jintArray jSigStrengths;
+
+ int d;
+
+ ALOGI("androidFmRadioRxCallbackOnFullScan: No items %d, aborted %d",
+ noItems, aborted);
+
+ for (d = 0; d < noItems; d++) {
+ ALOGI("%d -> %d", frequencies[d], sigStrengths[d]);
+ }
+
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ jFreqs = env->NewIntArray(noItems);
+ jSigStrengths = env->NewIntArray(noItems);
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+
+ env->SetIntArrayRegion(jFreqs, 0, noItems, frequencies);
+ env->SetIntArrayRegion(jSigStrengths, 0, noItems, sigStrengths);
+
+
+ notifyMethod = env->GetMethodID(clazz, "notifyOnFullScan", "([I[IZ)V");
+
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ env->CallVoidMethod(jobj, notifyMethod,
+ jFreqs, jSigStrengths, aborted);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ } else {
+ ALOGE("ERROR - JNI can't find java notifyOnFullScan method");
+ }
+
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+}
+
+static void androidFmRadioRxCallbackOnForcedReset(enum fmradio_reset_reason_t reason)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+ bool reAttached = false;
+
+ ALOGI("androidFmRadioRxCallbackOnForcedReset");
+
+ if (fmReceiverSession.jvm_p->GetEnv((void **) &env, JNI_VERSION_1_4) !=
+ JNI_OK) {
+ reAttached = true;
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+ }
+
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+
+ notifyMethod = env->GetMethodID(clazz, "notifyOnForcedReset", "(I)V");
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod,
+ reason);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ }
+
+ if (reAttached) {
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+ }
+}
+
+static void androidFmRadioRxCallbackOnVendorForcedReset(enum fmradio_reset_reason_t reason)
+{
+
+ ALOGI("androidFmRadioRxCallbackOnVendorForcedReset");
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ if (fmReceiverSession.state != FMRADIO_STATE_IDLE) {
+ FMRADIO_SET_STATE(&fmReceiverSession, FMRADIO_STATE_IDLE);
+ androidFmRadioUnLoadFmLibrary(&fmReceiverSession);
+ fmReceiverSession.isRegistered = false;
+ }
+ fmReceiverSession.callbacks_p->onForcedReset(reason);
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+
+static void androidFmRadioRxCallbackOnExtraCommand(char* command,
+ struct
+ fmradio_extra_command_ret_item_t
+ *retList)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+
+ struct bundle_descriptor_offsets_t *bundle_p =
+ fmReceiverSession.bundleOffsets_p;
+
+ ALOGI("androidFmRadioRxCallbackOnSendExtraCommand");
+
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+ jobject retBundle = extraCommandRetList2Bundle(env, bundle_p, retList);
+ jstring jcommand = env->NewStringUTF(command);
+
+ notifyMethod =
+ env->GetMethodID(clazz, "notifyOnExtraCommand",
+ "(Ljava/lang/String;Landroid/os/Bundle;)V");
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ env->CallVoidMethod(jobj, notifyMethod, jcommand, retBundle);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ }
+
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+}
+
+/*
+* Implementation of callbacks from vendor layer. For these the mutex lock
+* is NOT held on entry and need to be taken and released before doing
+* calls to java layer (env->Call*Method) becasue these might trigger new
+* calls from java and a deadlock would occure
+*/
+
+static void
+androidFmRadioRxCallbackOnRDSDataFound(struct fmradio_rds_bundle_t *t,
+ int frequency)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+ jobject bundle;
+ jshortArray jsArr;
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ struct bundle_descriptor_offsets_t *bundle_p =
+ fmReceiverSession.bundleOffsets_p;
+
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ goto drop_lock;
+ }
+ bundle = env->NewObject(bundle_p->mClass,
+ bundle_p->mConstructor);
+ /* note, these calls are to predefined methods, no need to release lock */
+ env->CallVoidMethod(bundle, bundle_p->mPutShort,
+ env->NewStringUTF("PI"), t->pi);
+ env->CallVoidMethod(bundle, bundle_p->mPutShort,
+ env->NewStringUTF("TP"), t->tp);
+ env->CallVoidMethod(bundle, bundle_p->mPutShort,
+ env->NewStringUTF("PTY"), t->pty);
+ env->CallVoidMethod(bundle, bundle_p->mPutShort,
+ env->NewStringUTF("TA"), t->ta);
+ env->CallVoidMethod(bundle, bundle_p->mPutShort,
+ env->NewStringUTF("M/S"), t->ms);
+
+ if (t->num_afs > 0 && t->num_afs < RDS_MAX_AFS) {
+ jintArray jArr = env->NewIntArray(t->num_afs);
+ env->SetIntArrayRegion(jArr, 0, t->num_afs, t->af);
+ env->CallVoidMethod(bundle, bundle_p->mPutIntArray,
+ env->NewStringUTF("AF"), jArr);
+ }
+ env->CallVoidMethod(bundle, bundle_p->mPutString,
+ env->NewStringUTF("PSN"),
+ env->NewStringUTF(t->psn));
+ env->CallVoidMethod(bundle, bundle_p->mPutString,
+ env->NewStringUTF("RT"),
+ env->NewStringUTF(t->rt));
+ env->CallVoidMethod(bundle, bundle_p->mPutString,
+ env->NewStringUTF("CT"),
+ env->NewStringUTF(t->ct));
+ env->CallVoidMethod(bundle, bundle_p->mPutString,
+ env->NewStringUTF("PTYN"),
+ env->NewStringUTF(t->ptyn));
+
+ jsArr = env->NewShortArray(3);
+
+ env->SetShortArrayRegion(jsArr, 0, 3, t->tmc);
+ env->CallVoidMethod(bundle, bundle_p->mPutShortArray,
+ env->NewStringUTF("TMC"), jsArr);
+
+ env->CallVoidMethod(bundle, bundle_p->mPutInt,
+ env->NewStringUTF("TAF"), t->taf);
+
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+
+ notifyMethod =
+ env->GetMethodID(clazz, "notifyOnRDSDataFound",
+ "(Landroid/os/Bundle;I)V");
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod,
+ bundle, frequency);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ }
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+
+ drop_lock:
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+static void androidFmRadioRxCallbackOnSignalStrengthChanged(int newLevel)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ goto drop_lock;
+ }
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+ notifyMethod =
+ env->GetMethodID(clazz, "notifyOnSignalStrengthChanged", "(I)V");
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod,
+ newLevel);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ }
+
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+ drop_lock:
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+static void androidFmRadioRxCallbackOnPlayingInStereo(int
+ isPlayingInStereo)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+
+ ALOGI("androidFmRadioRxCallbackOnPlayingInStereo (%d)",
+ isPlayingInStereo);
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ goto drop_lock;
+ }
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+ notifyMethod =
+ env->GetMethodID(clazz, "notifyOnPlayingInStereo", "(Z)V");
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod,
+ (bool) isPlayingInStereo);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ }
+
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+ drop_lock:
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+/*
+ * currently frequency changed event is not supported by interface, to be
+ * implemented quite soon...
+ */
+
+static void androidFmRadioRxCallbackOnAutomaticSwitch(int newFrequency, enum fmradio_switch_reason_t reason)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+
+ ALOGI("androidFmRadioRxCallbackOnAutomaticSwitch: new frequency %d, reason %d",
+ newFrequency, (int) reason);
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ if (fmReceiverSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ goto drop_lock;
+ }
+ clazz = env->GetObjectClass(fmReceiverSession.jobj);
+ notifyMethod =
+ env->GetMethodID(clazz, "notifyOnAutomaticSwitching", "(II)V");
+ if (notifyMethod != NULL) {
+ jobject jobj = fmReceiverSession.jobj;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod, (jint)newFrequency,
+ (jint)reason);
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ }
+
+
+ fmReceiverSession.jvm_p->DetachCurrentThread();
+ drop_lock:
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+/*
+ * function calls from java layer.
+ */
+
+static jint androidFmRadioRxGetState(JNIEnv * env, jobject obj)
+{
+ FmRadioState_t state;
+
+ ALOGI("androidFmRadioRxGetState, state\n");
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ state = fmReceiverSession.state;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ return state;
+}
+
+/* common ones with tx, just forward to the generic androidFmRadioxxxxx version */
+
+static void
+androidFmRadioRxStart(JNIEnv * env, jobject obj, int lowFreq,
+ int highFreq, int defaultFreq, int grid)
+{
+ ALOGI("androidFmRadioRxStart. LowFreq %d, HighFreq %d, DefaultFreq %d, grid %d.", lowFreq, highFreq, defaultFreq, grid);
+
+ if (fmReceiverSession.jobj == NULL)
+ fmReceiverSession.jobj = env->NewGlobalRef(obj);
+ (void) androidFmRadioStart(&fmReceiverSession, FMRADIO_RX,
+ &FmRadioRxVendorCallbacks, false, lowFreq,
+ highFreq, defaultFreq, grid);
+}
+
+
+static void
+androidFmRadioRxStartAsync(JNIEnv * env, jobject obj, int lowFreq,
+ int highFreq, int defaultFreq, int grid)
+{
+ ALOGI("androidFmRadioRxStartAsync...");
+
+ if (fmReceiverSession.jobj == NULL)
+ fmReceiverSession.jobj = env->NewGlobalRef(obj);
+ (void) androidFmRadioStart(&fmReceiverSession, FMRADIO_RX,
+ &FmRadioRxVendorCallbacks, true, lowFreq,
+ highFreq, defaultFreq, grid);
+}
+
+static void androidFmRadioRxPause(JNIEnv * env, jobject obj)
+{
+ ALOGI("androidFmRadioRxPause\n");
+
+ (void)androidFmRadioPause(&fmReceiverSession);
+}
+
+static void androidFmRadioRxResume(JNIEnv * env, jobject obj)
+{
+ ALOGI("androidFmRadioRxResume\n");
+ (void)androidFmRadioResume(&fmReceiverSession);
+}
+
+static jint androidFmRadioRxReset(JNIEnv * env, jobject obj)
+{
+ int retval = 0;
+
+ ALOGI("androidFmRadioRxReset");
+ retval = androidFmRadioReset(&fmReceiverSession);
+
+ if (retval >= 0 && fmReceiverSession.state == FMRADIO_STATE_IDLE &&
+ fmReceiverSession.jobj != NULL) {
+ env->DeleteGlobalRef(fmReceiverSession.jobj);
+ fmReceiverSession.jobj = NULL;
+ }
+
+ return retval;
+}
+
+static void
+androidFmRadioRxSetFrequency(JNIEnv * env, jobject obj, jint frequency)
+{
+ ALOGI("androidFmRadioRxSetFrequency tuneTo:%d\n", (int) frequency);
+ return androidFmRadioSetFrequency(&fmReceiverSession, (int) frequency);
+}
+
+static jint androidFmRadioRxGetFrequency(JNIEnv * env, jobject obj)
+{
+ ALOGI("androidFmRadioRxGetFrequency:\n");
+ return androidFmRadioGetFrequency(&fmReceiverSession);
+}
+
+static void androidFmRadioRxStopScan(JNIEnv * env, jobject obj)
+{
+ ALOGI("androidFmRadioRxStopScan\n");
+ androidFmRadioStopScan(&fmReceiverSession);
+}
+
+/* the rest of the calls are specific for RX */
+
+static jint androidFmRadioRxGetSignalStrength(JNIEnv * env, jobject obj)
+{
+ int retval = SIGNAL_STRENGTH_UNKNOWN;
+
+ ALOGI("androidFmRadioRxGetSignalStrength\n");
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_GET_SIGNAL_STRENGTH)) {
+ goto drop_lock;
+ }
+
+ if (fmReceiverSession.vendorMethods_p->get_signal_strength) {
+ /* if in pause state temporary resume */
+ androidFmRadioTempResumeIfPaused(&fmReceiverSession);
+
+ retval =
+ fmReceiverSession.vendorMethods_p->
+ get_signal_strength(&fmReceiverSession.vendorData_p);
+
+ if (retval < 0) {
+ retval = SIGNAL_STRENGTH_UNKNOWN;
+ } else if (retval > SIGNAL_STRENGTH_MAX) {
+ retval = SIGNAL_STRENGTH_MAX;
+ }
+ androidFmRadioPauseIfTempResumed(&fmReceiverSession);
+ }
+
+ drop_lock:
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ return retval;
+}
+
+static jboolean
+androidFmRadioRxIsPlayingInStereo(JNIEnv * env, jobject obj)
+{
+ bool retval;
+
+ ALOGI("androidFmRadioRxIsPlayingInStereo:\n");
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ /* if we haven't register we don't know yet */
+ if (!fmReceiverSession.isRegistered) {
+ retval = false;
+ goto drop_lock;
+ }
+ // valid in all states
+ if (fmReceiverSession.vendorMethods_p->is_playing_in_stereo != NULL) {
+ retval =
+ fmReceiverSession.vendorMethods_p->
+ is_playing_in_stereo(&fmReceiverSession.vendorData_p);
+ } else {
+ retval = false;
+ }
+
+ drop_lock:
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ return retval;
+}
+
+static jboolean
+androidFmRadioRxIsRDSDataSupported(JNIEnv * env, jobject obj)
+{
+ bool retval;
+
+ ALOGI("androidFmRadioRxIsRDSDataSupported:\n");
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ /* if we haven't register we don't know yet */
+ if (!fmReceiverSession.isRegistered) {
+ retval = false;
+ goto drop_lock;
+ }
+ // valid in all states
+ if (fmReceiverSession.vendorMethods_p->is_rds_data_supported != NULL) {
+ retval =
+ fmReceiverSession.vendorMethods_p->
+ is_rds_data_supported(&fmReceiverSession.vendorData_p);
+ } else {
+ retval = false;
+ }
+
+ drop_lock:
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ return retval;
+}
+
+static jboolean
+androidFmRadioRxIsTunedToValidChannel(JNIEnv * env, jobject obj)
+{
+ bool retval;
+
+ ALOGI("androidFmRadioRxIsTunedToValidChannel:\n");
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ /* if we haven't register we don't know yet */
+ if (!fmReceiverSession.isRegistered) {
+ retval = false;
+ goto drop_lock;
+ }
+ // valid in all states
+ if (fmReceiverSession.vendorMethods_p->is_tuned_to_valid_channel != NULL) {
+ retval =
+ fmReceiverSession.vendorMethods_p->
+ is_tuned_to_valid_channel(&fmReceiverSession.vendorData_p);
+ } else {
+ retval = false;
+ }
+
+ drop_lock:
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ return retval;
+}
+
+static void *execute_androidFmRadioRxScan(void *args)
+{
+ enum fmradio_seek_direction_t scanDirection =
+ *(enum fmradio_seek_direction_t *) args;
+ int signalStrength = -1;
+ int retval;
+ enum FmRadioState_t oldState;
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ free(args);
+ // we should still be in SCANNING mode, but we can't be 100.00 % sure since main thread released lock
+ // before we could run
+
+ if (fmReceiverSession.state != FMRADIO_STATE_SCANNING) {
+ ALOGE("execute_androidFmRadioRxScan - warning, state not scanning");
+ }
+
+ /*
+ * if mode has been changed to IDLE in the mean time by main thread,
+ * exit the worker thread gracefully
+ */
+ if (fmReceiverSession.state == FMRADIO_STATE_IDLE) {
+ goto drop_lock;
+ }
+
+ oldState = fmReceiverSession.oldState;
+
+ // temporary resume chip if sleeping
+ if (oldState == FMRADIO_STATE_PAUSED) {
+ (void) fmReceiverSession.
+ vendorMethods_p->resume(&fmReceiverSession.vendorData_p);
+ }
+
+ if (pthread_cond_signal(&fmReceiverSession.sync_cond) != 0) {
+ ALOGE("execute_androidFmRadioRxScan - warning, signal failed");
+ }
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ retval =
+ fmReceiverSession.vendorMethods_p->scan(&fmReceiverSession.
+ vendorData_p,
+ scanDirection);
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (retval >= 0) {
+ // also get signal strength (if supported)
+ if (fmReceiverSession.vendorMethods_p->get_signal_strength)
+ signalStrength =
+ fmReceiverSession.vendorMethods_p->
+ get_signal_strength(&fmReceiverSession.vendorData_p);
+ }
+ /*
+ * if state has changed we should keep it, probably a forced reset
+ */
+ if (fmReceiverSession.state != FMRADIO_STATE_SCANNING) {
+ ALOGI("State changed while scanning (state now %d), keeping",
+ fmReceiverSession.state);
+ retval = -1;
+ } else {
+ // put back to sleep if we did a temporary wake-up
+ if ((oldState == FMRADIO_STATE_PAUSED
+ || fmReceiverSession.pendingPause))
+ (void) fmReceiverSession.
+ vendorMethods_p->pause(&fmReceiverSession.vendorData_p);
+ if (fmReceiverSession.pendingPause) {
+ FMRADIO_SET_STATE(&fmReceiverSession, FMRADIO_STATE_PAUSED);
+ } else {
+ FMRADIO_SET_STATE(&fmReceiverSession, oldState);
+ }
+
+ // if we failed but we have a pending abort just read the current frequency to give a proper
+ // onScan return
+
+ if (retval < 0 && fmReceiverSession.lastScanAborted &&
+ fmReceiverSession.vendorMethods_p->get_frequency) {
+ retval = fmReceiverSession.vendorMethods_p->get_frequency(&fmReceiverSession.vendorData_p);
+ }
+ }
+
+ fmReceiverSession.pendingPause = false;
+
+ if (retval >= 0) {
+ fmReceiverSession.callbacks_p->onScan(retval,
+ signalStrength,
+ scanDirection,
+ fmReceiverSession.
+ lastScanAborted);
+ } else {
+ fmReceiverSession.callbacks_p->onError();
+ }
+ drop_lock:
+ /* Wake up the main thread if it is currently waiting on the condition variable */
+ if (pthread_cond_signal(&fmReceiverSession.sync_cond) != 0) {
+ ALOGE("execute_androidFmRadioRxScan - signal failed\n");
+ }
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ pthread_exit(NULL);
+ return NULL;
+}
+
+
+static void androidFmRadioRxScan(enum fmradio_seek_direction_t scanDirection)
+{
+ int retval = 0;
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_SCAN)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (fmReceiverSession.vendorMethods_p->scan) {
+ enum fmradio_seek_direction_t *scanDirectionParam_p =
+ (enum fmradio_seek_direction_t *)
+ malloc(sizeof(*scanDirectionParam_p));
+
+ pthread_t execute_thread;
+
+ // we need to create a new thread actually executing the command
+
+ fmReceiverSession.oldState = fmReceiverSession.state;
+ FMRADIO_SET_STATE(&fmReceiverSession, FMRADIO_STATE_SCANNING);
+ *scanDirectionParam_p = scanDirection;
+
+ fmReceiverSession.lastScanAborted = false;
+
+ if (pthread_create
+ (&execute_thread, NULL, execute_androidFmRadioRxScan,
+ (void *) scanDirectionParam_p) != 0) {
+
+ ALOGE("pthread_create failure...\n");
+ free(scanDirectionParam_p);
+ FMRADIO_SET_STATE(&fmReceiverSession, fmReceiverSession.oldState);
+ retval = FMRADIO_IO_ERROR;
+ } else {
+ /* await thread startup, THREAD_WAIT_TIMEOUT_S sec timeout */
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += THREAD_WAIT_TIMEOUT_S;
+ if (pthread_cond_timedwait(&fmReceiverSession.sync_cond,
+ fmReceiverSession.dataMutex_p,
+ &ts) != 0) {
+ ALOGE("androidFmRadioRxScan: warning, wait failure\n");
+ }
+ pthread_detach(execute_thread);
+
+ }
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(&fmReceiverSession);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(&fmReceiverSession);
+ }
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ if (retval < 0) {
+ ALOGE("androidFmRadioRxScan failed\n");
+ }
+}
+
+static void
+androidFmRadioRxScanUp(JNIEnv * env, jobject obj, jlong * frequency)
+{
+ ALOGI("androidFmRadioRxScanUp\n");
+
+ androidFmRadioRxScan(FMRADIO_SEEK_UP);
+}
+
+static void
+androidFmRadioRxScanDown(JNIEnv * env, jobject obj, jlong * frequency)
+{
+ ALOGI("androidFmRadioRxScanDown\n");
+
+ androidFmRadioRxScan(FMRADIO_SEEK_DOWN);
+}
+
+static void *execute_androidFmRadioRxFullScan(void *args)
+{
+ int retval;
+ enum FmRadioState_t oldState = fmReceiverSession.oldState;
+ int *frequencies_p = NULL;
+ int *rssi_p = NULL;
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ // we should still be in SCANNING mode, but we can't be 100.00 % sure since main thread released lock
+ // before we could run
+
+ if (fmReceiverSession.state != FMRADIO_STATE_SCANNING) {
+ ALOGE("execute_androidFmRadioRxFullScan - warning, state not scanning\n");
+ }
+
+ /*
+ * if mode has been changed to IDLE in the mean time by main thread,
+ * exit the worker thread gracefully
+ */
+ if (fmReceiverSession.state == FMRADIO_STATE_IDLE) {
+ goto drop_lock;
+ }
+ // temporary resume chip if sleeping
+ if (oldState == FMRADIO_STATE_PAUSED) {
+ (void) fmReceiverSession.
+ vendorMethods_p->resume(&fmReceiverSession.vendorData_p);
+ }
+
+ if (pthread_cond_signal(&fmReceiverSession.sync_cond) != 0) {
+ ALOGE("execute_androidFmRadioRxFullScan - warning, signal failed\n");
+ }
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ retval =
+ fmReceiverSession.vendorMethods_p->full_scan(&fmReceiverSession.
+ vendorData_p,
+ &frequencies_p,
+ &rssi_p);
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ /*
+ * if state has changed we should keep it, probably a forced pause or
+ * forced reset
+ */
+ if (fmReceiverSession.state != FMRADIO_STATE_SCANNING) {
+ ALOGI("State changed while scanning (state now %d), keeping\n",
+ fmReceiverSession.state);
+ retval = -1;
+ } else {
+ if (fmReceiverSession.pendingPause) {
+ FMRADIO_SET_STATE(&fmReceiverSession, FMRADIO_STATE_PAUSED);
+ } else {
+ FMRADIO_SET_STATE(&fmReceiverSession, oldState);
+ }
+
+ fmReceiverSession.pendingPause = false;
+ }
+
+ if (retval >= 0) {
+ fmReceiverSession.callbacks_p->onFullScan(retval,
+ frequencies_p,
+ rssi_p,
+ fmReceiverSession.
+ lastScanAborted);
+ } else {
+ fmReceiverSession.callbacks_p->onError();
+ }
+
+ if (frequencies_p != NULL) {
+ free(frequencies_p);
+ }
+
+ if (rssi_p != NULL) {
+ free(rssi_p);
+ }
+
+ drop_lock:
+ /* Wake up the main thread if it is currently waiting on the condition variable */
+ if (pthread_cond_signal(&fmReceiverSession.sync_cond) != 0) {
+ ALOGE("execute_androidFmRadioRxFullScan - signal failed\n");
+ }
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static void androidFmRadioRxStartFullScan(JNIEnv * env, jobject obj)
+{
+ ALOGI("androidFmRadioRxStartFullScan\n");
+ int retval = 0;
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_FULL_SCAN)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+
+ if (fmReceiverSession.vendorMethods_p->full_scan) {
+ pthread_t execute_thread;
+
+ fmReceiverSession.oldState = fmReceiverSession.state;
+ FMRADIO_SET_STATE(&fmReceiverSession, FMRADIO_STATE_SCANNING);
+ fmReceiverSession.lastScanAborted = false;
+
+ if (pthread_create
+ (&execute_thread, NULL, execute_androidFmRadioRxFullScan,
+ NULL) != 0) {
+
+ ALOGE("pthread_create failure...\n");
+ FMRADIO_SET_STATE(&fmReceiverSession, fmReceiverSession.oldState);
+ retval = FMRADIO_IO_ERROR;
+ } else {
+ /* await thread startup, THREAD_WAIT_TIMEOUT_S sec timeout */
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += THREAD_WAIT_TIMEOUT_S;
+ if (pthread_cond_timedwait(&fmReceiverSession.sync_cond,
+ fmReceiverSession.dataMutex_p,
+ &ts) != 0) {
+ ALOGE("androidFmRadioRxStartFullScan: warning, wait failure\n");
+ }
+ pthread_detach(execute_thread);
+ }
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(&fmReceiverSession);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(&fmReceiverSession);
+ }
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+static void androidFmRadioRxSetAutomaticAFSwitching(JNIEnv * env,
+ jobject obj,
+ jboolean automatic)
+{
+ int retval = -1;
+
+ ALOGI("androidFmRadioRxSetAutomaticAFSwitching\n");
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_SET_PARAMETER)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+
+ if (fmReceiverSession.vendorMethods_p->set_automatic_af_switching) {
+ retval =
+ fmReceiverSession.vendorMethods_p->
+ set_automatic_af_switching(&fmReceiverSession.vendorData_p, automatic);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(&fmReceiverSession);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(&fmReceiverSession);
+ }
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+static void androidFmRadioRxSetAutomaticTASwitching(JNIEnv * env, jobject obj,
+ jboolean automatic)
+{
+ int retval = -1;
+
+ ALOGI("androidFmRadioRxSetAutomaticTASwitching\n");
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_SET_PARAMETER)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+
+ if (fmReceiverSession.vendorMethods_p->set_automatic_ta_switching) {
+ retval =
+ fmReceiverSession.vendorMethods_p->
+ set_automatic_ta_switching(&fmReceiverSession.vendorData_p, automatic);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(&fmReceiverSession);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(&fmReceiverSession);
+ }
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+static void androidFmRadioRxSetForceMono(JNIEnv * env, jobject obj,
+ jboolean forceMono)
+{
+ int retval = -1;
+
+ ALOGI("androidFmRadioRxSetForceMono\n");
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_SET_PARAMETER)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+
+ if (fmReceiverSession.vendorMethods_p->set_force_mono) {
+ /* if in pause state temporary resume */
+ androidFmRadioTempResumeIfPaused(&fmReceiverSession);
+
+ retval =
+ fmReceiverSession.vendorMethods_p->
+ set_force_mono(&fmReceiverSession.vendorData_p, forceMono);
+
+ androidFmRadioPauseIfTempResumed(&fmReceiverSession);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(&fmReceiverSession);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(&fmReceiverSession);
+ }
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+static void
+androidFmRadioRxSetThreshold(JNIEnv * env, jobject obj, jint threshold)
+{
+ int retval;
+
+ ALOGI("androidFmRadioRxSetThreshold threshold:%d\n", (int) threshold);
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_SET_PARAMETER)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+
+ if (fmReceiverSession.vendorMethods_p->set_threshold) {
+ /* if in pause state temporary resume */
+ androidFmRadioTempResumeIfPaused(&fmReceiverSession);
+
+ retval =
+ fmReceiverSession.
+ vendorMethods_p->set_threshold(&fmReceiverSession.vendorData_p,
+ threshold);
+ /* if in pause state temporary resume */
+ androidFmRadioPauseIfTempResumed(&fmReceiverSession);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(&fmReceiverSession);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(&fmReceiverSession);
+ }
+
+ drop_lock:
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+static jint androidFmRadioRxGetThreshold(JNIEnv * env, jobject obj)
+{
+ int retval;
+
+ ALOGI("androidFmRadioRxGetThreshold\n");
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_GET_PARAMETER)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (fmReceiverSession.vendorMethods_p->get_threshold) {
+ /* if in pause state temporary resume */
+ androidFmRadioTempResumeIfPaused(&fmReceiverSession);
+ retval =
+ fmReceiverSession.
+ vendorMethods_p->get_threshold(&fmReceiverSession.vendorData_p);
+ androidFmRadioPauseIfTempResumed(&fmReceiverSession);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+ drop_lock:
+
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(&fmReceiverSession);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(&fmReceiverSession);
+ }
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+
+ return retval;
+}
+
+static void androidFmRadioRxSetRDS(JNIEnv * env, jobject obj,
+ jboolean receiveRDS)
+{
+ int retval = -1;
+
+ ALOGI("androidFmRadioRxSetRDS(%d)", (int)receiveRDS);
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmReceiverSession, FMRADIO_EVENT_SET_PARAMETER)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (fmReceiverSession.vendorMethods_p->set_rds_reception) {
+ /* if in pause state temporary resume */
+ androidFmRadioTempResumeIfPaused(&fmReceiverSession);
+
+ retval = fmReceiverSession.vendorMethods_p->
+ set_rds_reception(&fmReceiverSession.vendorData_p, receiveRDS);
+
+ androidFmRadioPauseIfTempResumed(&fmReceiverSession);
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ drop_lock:
+ /*
+ * Set rds is not executed by explicit command but rather triggered
+ * on startup and on adding and removal of listeners. Because of this
+ * it should not trigger exceptions, just ALOG any failure.
+ */
+
+ if (retval != FMRADIO_OK) {
+ ALOGE("androidFmRadioRxSetRDS failed, retval = %d.", retval);
+ }
+
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+}
+
+static jboolean androidFmRadioRxSendExtraCommand(JNIEnv * env, jobject obj,
+ jstring command,
+ jobjectArray parameters)
+{
+ ALOGI("androidFmRadioRxSendExtraCommand");
+
+/* we need to set jobj since this might be called before start */
+
+ if (fmReceiverSession.jobj == NULL)
+ fmReceiverSession.jobj = env->NewGlobalRef(obj);
+
+ androidFmRadioSendExtraCommand(&fmReceiverSession, env, command,
+ parameters);
+
+ return true;
+}
+
+
+static JNINativeMethod gMethods[] = {
+ {(char *)"_fm_receiver_getState", (char *)"()I",
+ (void *) androidFmRadioRxGetState},
+ {(char *)"_fm_receiver_start", (char *)"(IIII)V",
+ (void *) androidFmRadioRxStart},
+ {(char *)"_fm_receiver_startAsync", (char *)"(IIII)V",
+ (void *) androidFmRadioRxStartAsync},
+ {(char *)"_fm_receiver_pause", (char *)"()V",
+ (void *) androidFmRadioRxPause},
+ {(char *)"_fm_receiver_resume", (char *)"()V",
+ (void *) androidFmRadioRxResume},
+ {(char *)"_fm_receiver_reset", (char *)"()I",
+ (void *) androidFmRadioRxReset},
+ {(char *)"_fm_receiver_setFrequency", (char *)"(I)V",
+ (void *) androidFmRadioRxSetFrequency},
+ {(char *)"_fm_receiver_getFrequency", (char *)"()I",
+ (void *) androidFmRadioRxGetFrequency},
+ {(char *)"_fm_receiver_getSignalStrength", (char *)"()I",
+ (void *) androidFmRadioRxGetSignalStrength},
+ {(char *)"_fm_receiver_scanUp", (char *)"()V",
+ (void *) androidFmRadioRxScanUp},
+ {(char *)"_fm_receiver_scanDown", (char *)"()V",
+ (void *) androidFmRadioRxScanDown},
+ {(char *)"_fm_receiver_startFullScan", (char *)"()V",
+ (void *) androidFmRadioRxStartFullScan},
+ {(char *)"_fm_receiver_isPlayingInStereo", (char *)"()Z",
+ (void *) androidFmRadioRxIsPlayingInStereo},
+ {(char *)"_fm_receiver_isRDSDataSupported", (char *)"()Z",
+ (void *) androidFmRadioRxIsRDSDataSupported},
+ {(char *)"_fm_receiver_isTunedToValidChannel", (char *)"()Z",
+ (void *) androidFmRadioRxIsTunedToValidChannel},
+ {(char *)"_fm_receiver_stopScan", (char *)"()V",
+ (void *) androidFmRadioRxStopScan},
+ {(char *)"_fm_receiver_setAutomaticAFSwitching", (char *)"(Z)V",
+ (void *) androidFmRadioRxSetAutomaticAFSwitching},
+ {(char *)"_fm_receiver_setAutomaticTASwitching", (char *)"(Z)V",
+ (void *) androidFmRadioRxSetAutomaticTASwitching},
+ {(char *)"_fm_receiver_setForceMono", (char *)"(Z)V",
+ (void *) androidFmRadioRxSetForceMono},
+ {(char *)"_fm_receiver_sendExtraCommand",
+ (char *)"(Ljava/lang/String;[Ljava/lang/String;)Z",
+ (void *) androidFmRadioRxSendExtraCommand},
+ {(char *)"_fm_receiver_getThreshold", (char *)"()I",
+ (void *) androidFmRadioRxGetThreshold},
+ {(char *)"_fm_receiver_setThreshold", (char *)"(I)V",
+ (void *) androidFmRadioRxSetThreshold},
+ {(char *)"_fm_receiver_setRDS", (char *)"(Z)V",
+ (void *) androidFmRadioRxSetRDS},
+};
+
+
+
+
+int registerAndroidFmRadioReceiver(JavaVM * vm, JNIEnv * env)
+{
+ ALOGI("registerAndroidFmRadioReceiver\n");
+ jclass clazz;
+
+ pthread_mutex_lock(fmReceiverSession.dataMutex_p);
+ fmReceiverSession.jvm_p = vm;
+
+ struct bundle_descriptor_offsets_t *bundle_p =
+ (struct bundle_descriptor_offsets_t *)
+ malloc(sizeof(struct bundle_descriptor_offsets_t));
+
+ clazz = env->FindClass("android/os/Bundle");
+ bundle_p->mClass = (jclass) env->NewGlobalRef(clazz);
+ bundle_p->mConstructor = env->GetMethodID(clazz, "<init>", "()V");
+ bundle_p->mPutInt =
+ env->GetMethodID(clazz, "putInt", "(Ljava/lang/String;I)V");
+ bundle_p->mPutShort =
+ env->GetMethodID(clazz, "putShort", "(Ljava/lang/String;S)V");
+ bundle_p->mPutIntArray =
+ env->GetMethodID(clazz, "putIntArray", "(Ljava/lang/String;[I)V");
+ bundle_p->mPutShortArray =
+ env->GetMethodID(clazz, "putShortArray",
+ "(Ljava/lang/String;[S)V");
+ bundle_p->mPutString =
+ env->GetMethodID(clazz, "putString",
+ "(Ljava/lang/String;Ljava/lang/String;)V");
+
+ fmReceiverSession.bundleOffsets_p = bundle_p;
+ pthread_mutex_unlock(fmReceiverSession.dataMutex_p);
+ return jniRegisterNativeMethods(env,
+ "com/stericsson/hardware/fm/FmReceiverService",
+ gMethods, NELEM(gMethods));
+}
+
+/* *INDENT-OFF* */
+}; // namespace android
diff --git a/fmradio/jni/android_fmradio_Transmitter.cpp b/fmradio/jni/android_fmradio_Transmitter.cpp
new file mode 100755
index 0000000..b8a7e48
--- /dev/null
+++ b/fmradio/jni/android_fmradio_Transmitter.cpp
@@ -0,0 +1,1098 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Copyright 2010, 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.
+ *
+ * Authors: johan.xj.palmaeus@stericsson.com
+ * stuart.macdonald@stericsson.com
+ * for ST-Ericsson
+ */
+
+/*
+ * Native part of the generic TX FmRadio inteface
+ */
+
+#define ALOG_TAG "FmTransmitterServiceNative"
+
+// #define LOG_NDEBUG 1
+
+#include <stdio.h>
+#include <unistd.h>
+#include <termios.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdarg.h>
+#include <pthread.h>
+#include <media/AudioSystem.h>
+#include <system/audio.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_fmradio.h"
+#include <utils/Log.h>
+
+
+/* *INDENT-OFF* */
+namespace android {
+
+// RDS Fields
+
+static const char* rds_field_names[] = {
+ "PI",
+ "TP",
+ "PTY",
+ "TA",
+ "M/S",
+ "AF",
+ "numAFs",
+ "PSN",
+ "RT",
+ "CT",
+ "PTYN",
+ "TMC",
+ "TAF",
+ NULL
+};
+
+
+// state machine
+
+static const ValidEventsForStates_t IsValidTxEventForState = {
+/* this table defines valid transitions. (turn off indent, we want this easy readable) */
+ /* FMRADIO_STATE_ IDLE,STARTING,STARTED,PAUSED,SCANNING,EXTRA_COMMAND */
+
+ /* FMRADIO_EVENT_START */ {true, false,false,false,false,false},
+ /* FMRADIO_EVENT_START_ASYNC */ {true, false,false,false,false,false},
+ /* FMRADIO_EVENT_PAUSE */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_RESUME */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_RESET */ {true, true, true, true, true, true },
+ /* FMRADIO_EVENT_GET_FREQUENCY */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_SET_FREQUENCY */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_SET_PARAMETER */ {false,false,true, true, false,false},
+ /* FMRADIO_EVENT_STOP_SCAN */ {true, true, true, true, true, true },
+ /* FMRADIO_EVENT_EXTRA_COMMAND */ {true, true, true, true, true, true },
+ /* Rx Only - never allowed */
+ /* FMRADIO_EVENT_GET_PARAMETER */ {false,false,false,false,false,false},
+ /* FMRADIO_EVENT_GET_SIGNAL_STRENGTH */{false,false,false,false,false,false},
+ /* FMRADIO_EVENT_SCAN */ {false,false,false,false,false,false},
+ /* FMRADIO_EVENT_FULL_SCAN */ {false,false,false,false,false,false},
+ /* Tx Only */
+ /* FMRADIO_EVENT_BLOCK_SCAN */ {false,false,true, true, false,false},
+};
+/* *INDENT-ON* */
+
+static void androidFmRadioTxCallbackOnStateChanged(int oldState,
+ int newState);
+
+static void androidFmRadioTxCallbackOnError(void);
+
+static void androidFmRadioTxCallbackOnStarted(void);
+
+static void androidFmRadioTxCallbackOnBlockScan(int noValues,
+ int *freqs,
+ int *sigStrengths,
+ bool aborted);
+static void androidFmRadioTxCallbackOnForcedReset(enum fmradio_reset_reason_t reason);
+
+static void androidFmRadioTxCallbackOnVendorForcedReset(enum fmradio_reset_reason_t reason);
+
+static void androidFmRadioTxCallbackOnExtraCommand(char* command,
+ struct
+ fmradio_extra_command_ret_item_t
+ *retList);
+
+static const FmRadioCallbacks_t FmRadioTxCallbacks = {
+ androidFmRadioTxCallbackOnStateChanged,
+ androidFmRadioTxCallbackOnError,
+ androidFmRadioTxCallbackOnStarted,
+ NULL,
+ NULL,
+ androidFmRadioTxCallbackOnBlockScan,
+ androidFmRadioTxCallbackOnForcedReset,
+ androidFmRadioTxCallbackOnExtraCommand,
+};
+
+
+/* callbacks from vendor layer */
+
+static const fmradio_vendor_callbacks_t FmRadioTxVendorCallbacks = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ androidFmRadioTxCallbackOnVendorForcedReset
+};
+
+extern struct FmSession_t fmReceiverSession;
+
+struct FmSession_t fmTransmitterSession = {
+ NULL,
+ NULL,
+ false,
+ FMRADIO_STATE_IDLE,
+ NULL,
+ &IsValidTxEventForState,
+ &FmRadioTxCallbacks,
+ NULL,
+ NULL,
+ &fmReceiverSession,
+ NULL,
+ FMRADIO_STATE_IDLE,
+ false,
+ false,
+ false,
+ &rx_tx_common_mutex,
+ PTHREAD_COND_INITIALIZER,
+ NULL,
+};
+
+struct FmRadioBlockScanParameters {
+ int startFreq;
+ int endFreq;
+};
+
+// make sure we don't refere the ReceiverSession anymore from here
+#define fmReceiverSession ERRORDONOTUSERECEIVERSESSIONINTRANSMITTER
+
+/*
+* Implementation of callbacks from within service layer. For these the
+* mutex lock is always held on entry and need to be released before doing
+* calls to java layer (env->Call*Method) becasue these calls might trigger
+* new calls from java and a deadlock would occure if lock was still held.
+*/
+
+
+static void androidFmRadioTxCallbackOnStateChanged(int oldState,
+ int newState)
+{
+ jmethodID notifyOnStateChangedMethod;
+ JNIEnv *env;
+ jclass clazz;
+ bool reAttached = false;
+
+ ALOGI("androidFmRadioTxCallbackOnStateChanged: Old state %d, new state %d", oldState, newState);
+
+ /* since we might be both in main thread and subthread both test getenv
+ * and attach */
+ if (fmTransmitterSession.jvm_p->
+ GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ reAttached = true;
+ if (fmTransmitterSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attach current thread");
+ return;
+ }
+ }
+
+ clazz = env->GetObjectClass(fmTransmitterSession.jobj);
+
+ notifyOnStateChangedMethod =
+ env->GetMethodID(clazz, "notifyOnStateChanged", "(II)V");
+ if (notifyOnStateChangedMethod != NULL) {
+ jobject jobj = fmTransmitterSession.jobj;
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+ env->CallVoidMethod(jobj,
+ notifyOnStateChangedMethod, oldState,
+ newState);
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ }
+ if (reAttached) {
+ fmTransmitterSession.jvm_p->DetachCurrentThread();
+ }
+}
+
+static void androidFmRadioTxCallbackOnError(void)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+
+ ALOGI("androidFmRadioTxCallbackOnError");
+
+
+ if (fmTransmitterSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ clazz = env->GetObjectClass(fmTransmitterSession.jobj);
+ notifyMethod = env->GetMethodID(clazz, "notifyOnError", "()V");
+
+ if (notifyMethod != NULL) {
+ jobject jobj = fmTransmitterSession.jobj;
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod);
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ } else {
+ ALOGE("ERROR - JNI can't find java notifyOnError method");
+ }
+
+ fmTransmitterSession.jvm_p->DetachCurrentThread();
+}
+
+static void androidFmRadioTxCallbackOnStarted(void)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+ status_t err;
+
+ ALOGI("androidFmRadioTxCallbackOnStarted: Callback");
+
+ if (fmTransmitterSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ clazz = env->GetObjectClass(fmTransmitterSession.jobj);
+ notifyMethod = env->GetMethodID(clazz, "notifyOnStarted", "()V");
+
+ if (notifyMethod != NULL) {
+ jobject jobj = fmTransmitterSession.jobj;
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod);
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ } else {
+ ALOGE("ERROR - JNI can't find java notifyOnStarted method");
+ }
+
+ // err =
+ //AudioSystem::
+ // setDeviceConnectionState(AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
+
+ if (err != OK) {
+ ALOGE("ERROR - Unable to set audio output device to FM Radio TX");
+ // AudioSystem::
+ // setDeviceConnectionState(AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+ // "");
+ }
+
+ fmTransmitterSession.jvm_p->DetachCurrentThread();
+}
+
+static void androidFmRadioTxCallbackOnBlockScan(int noItems,
+ int *freqs,
+ int *sigStrengths,
+ bool aborted)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+ jintArray jFreqs;
+ jintArray jSigStrengths;
+ int d;
+
+ ALOGI("androidFmRadioTxCallbackOnBlockScan: No items %d, aborted %d",
+ noItems, aborted);
+
+ for (d = 0; d < noItems; d++) {
+ ALOGI("%d->%d", freqs[d], sigStrengths[d]);
+ }
+
+ if (fmTransmitterSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ clazz = env->GetObjectClass(fmTransmitterSession.jobj);
+
+ jFreqs = env->NewIntArray(noItems);
+ jSigStrengths = env->NewIntArray(noItems);
+
+ env->SetIntArrayRegion(jFreqs, 0, noItems, freqs);
+ env->SetIntArrayRegion(jSigStrengths, 0, noItems, sigStrengths);
+
+ notifyMethod =
+ env->GetMethodID(clazz, "notifyOnBlockScan", "([I[IZ)V");
+
+
+ if (notifyMethod != NULL) {
+ jobject jobj = fmTransmitterSession.jobj;
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod,
+ jFreqs, jSigStrengths, aborted);
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ } else {
+ ALOGE("ERROR - JNI can't find java notifyOnBlockScan method");
+ }
+
+ fmTransmitterSession.jvm_p->DetachCurrentThread();
+}
+
+static void androidFmRadioTxCallbackOnForcedReset(enum fmradio_reset_reason_t reason)
+{
+ jmethodID notifyMethod;
+ JNIEnv *env;
+ jclass clazz;
+ bool reAttached = false;
+
+ ALOGI("androidFmRadioTxCallbackOnForcedReset");
+
+ if (fmTransmitterSession.jvm_p->
+ GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ reAttached = true;
+ if (fmTransmitterSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+ }
+
+ clazz = env->GetObjectClass(fmTransmitterSession.jobj);
+
+ notifyMethod = env->GetMethodID(clazz, "notifyOnForcedReset", "(I)V");
+ if (notifyMethod != NULL) {
+ jobject jobj = fmTransmitterSession.jobj;
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod, reason);
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ }
+
+ //AudioSystem::
+ // setDeviceConnectionState(AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+ // "");
+
+ if (reAttached) {
+ fmTransmitterSession.jvm_p->DetachCurrentThread();
+ }
+}
+
+static void androidFmRadioTxCallbackOnVendorForcedReset(enum fmradio_reset_reason_t reason)
+{
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ if (fmTransmitterSession.state != FMRADIO_STATE_IDLE) {
+ FMRADIO_SET_STATE(&fmTransmitterSession, FMRADIO_STATE_IDLE);
+ androidFmRadioUnLoadFmLibrary(&fmTransmitterSession);
+ fmTransmitterSession.isRegistered = false;
+ }
+ fmTransmitterSession.callbacks_p->onForcedReset(reason);
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+}
+
+static void androidFmRadioTxCallbackOnExtraCommand(char* command,
+ struct
+ fmradio_extra_command_ret_item_t
+ *retList)
+{
+ jmethodID notifyMethod;
+
+ JNIEnv *env;
+
+ jclass clazz;
+
+ struct bundle_descriptor_offsets_t *bundle_p =
+ fmTransmitterSession.bundleOffsets_p;
+ ALOGI("androidFmRadioTxCallbackOnExtraCommand");
+
+ if (fmTransmitterSession.jvm_p->AttachCurrentThread(&env, NULL) != JNI_OK) {
+ ALOGE("Error, can't attch current thread");
+ return;
+ }
+
+ clazz = env->GetObjectClass(fmTransmitterSession.jobj);
+
+ jobject retBundle = extraCommandRetList2Bundle(env, bundle_p, retList);
+ jstring jcommand = env->NewStringUTF(command);
+
+ notifyMethod =
+ env->GetMethodID(clazz, "notifyOnExtraCommand",
+ "(Ljava/lang/String;Landroid/os/Bundle;)V");
+ if (notifyMethod != NULL) {
+ jobject jobj = fmTransmitterSession.jobj;
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+ env->CallVoidMethod(jobj, notifyMethod,
+ jcommand, retBundle);
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ }
+
+ fmTransmitterSession.jvm_p->DetachCurrentThread();
+}
+
+/*
+ * function calls from java layer
+ */
+
+static jint androidFmRadioTxGetState(JNIEnv * env, jobject obj)
+{
+ FmRadioState_t state;
+
+ ALOGI("androidFmRadioTxGetState\n");
+
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ state = fmTransmitterSession.state;
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+
+ return state;
+}
+
+/* common ones with rx, just forward to the generic androidFmRadioxxxxx version */
+
+static void
+androidFmRadioTxStart(JNIEnv * env, jobject obj, int lowFreq,
+ int highFreq, int defaultFreq, int grid)
+{
+ int retval;
+
+ status_t err;
+
+ ALOGI("androidFmRadioTxStart...");
+
+ if (fmTransmitterSession.jobj == NULL)
+ fmTransmitterSession.jobj = env->NewGlobalRef(obj);
+
+ retval =
+ androidFmRadioStart(&fmTransmitterSession, FMRADIO_TX,
+ &FmRadioTxVendorCallbacks, false, lowFreq,
+ highFreq, defaultFreq, grid);
+ if (retval >= 0) {
+ //err =
+ // AudioSystem::
+ // setDeviceConnectionState(AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
+ // "");
+
+ if (err != OK) {
+ ALOGE("ERROR - Unable to set audio output device to FM Radio TX");
+ // (void) AudioSystem::setDeviceConnectionState
+ // (AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
+ }
+ }
+
+}
+
+static void
+androidFmRadioTxStartAsync(JNIEnv * env, jobject obj, int lowFreq,
+ int highFreq, int defaultFreq, int grid)
+{
+ ALOGI("androidFmRadioTxStartAsync...");
+
+
+ if (fmTransmitterSession.jobj == NULL)
+ fmTransmitterSession.jobj = env->NewGlobalRef(obj);
+
+ androidFmRadioStart(&fmTransmitterSession, FMRADIO_TX,
+ &FmRadioTxVendorCallbacks, true, lowFreq, highFreq,
+ defaultFreq, grid);
+}
+
+static void androidFmRadioTxPause(JNIEnv * env, jobject obj)
+{
+ int retval;
+
+ ALOGI("androidFmRadioTxPause\n");
+
+ retval = androidFmRadioPause(&fmTransmitterSession);
+
+ if (retval >= 0) {
+ //AudioSystem::
+ // setDeviceConnectionState(AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+ // "");
+ }
+}
+
+static void androidFmRadioTxResume(JNIEnv * env, jobject obj)
+{
+ int retval;
+
+ ALOGI("androidFmResumeTxResume\n");
+ retval = androidFmRadioResume(&fmTransmitterSession);
+
+ if (retval >= 0) {
+ // status_t err =
+ // AudioSystem::
+ //setDeviceConnectionState(AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
+
+ //if (err != OK) {
+ ALOGE("ERROR - Unable to set audio output device to FM Radio TX\n");
+ //AudioSystem::
+ // setDeviceConnectionState(AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+ // "");
+ // }
+ }
+}
+
+static jint androidFmRadioTxReset(JNIEnv * env, jobject obj)
+{
+ int retval;
+
+ ALOGI("androidFmRadioTxReset");
+
+ retval = androidFmRadioReset(&fmTransmitterSession);
+
+ if (retval >= 0) {
+ if (retval != FMRADIO_STATE_IDLE) {
+ // (void) AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_FM_TX,
+ // AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+ // "");
+ }
+
+
+ if (fmTransmitterSession.state == FMRADIO_STATE_IDLE &&
+ fmTransmitterSession.jobj != NULL) {
+ env->DeleteGlobalRef(fmTransmitterSession.jobj);
+ fmTransmitterSession.jobj = NULL;
+ }
+ }
+
+ return retval;
+}
+
+static void
+androidFmRadioTxSetFrequency(JNIEnv * env, jobject obj, jlong frequency)
+{
+ ALOGI("androidFmRadioTxSetFrequency tuneTo:%d\n", (int) frequency);
+ androidFmRadioSetFrequency(&fmTransmitterSession, (int) frequency);
+}
+
+static void
+androidFmRadioTxSetRDSData(JNIEnv * env, jobject obj, jobject bundle)
+{
+ ALOGI("androidFmRadioTxSetRDSData start");
+
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmTransmitterSession, FMRADIO_EVENT_SET_PARAMETER)) {
+ THROW_INVALID_STATE(&fmTransmitterSession);
+ goto drop_lock;
+ }
+
+ /* if in pause state temporary resume */
+ androidFmRadioTempResumeIfPaused(&fmTransmitterSession);
+
+ if (bundle == NULL) {
+ /* just shut down RDS transmission and leave */
+ fmTransmitterSession.vendorMethods_p->
+ set_rds_data(&fmTransmitterSession.vendorData_p, NULL, NULL);
+ goto resume_and_drop_lock;
+ };
+
+ /* new block to control variable life time */
+ {
+ struct bundle_descriptor_offsets_t *bundle_p =
+ fmTransmitterSession.bundleOffsets_p;
+ jobject keys_set = env->CallObjectMethod(bundle, bundle_p->mKeySet);
+ jclass setClass = env->FindClass("java/util/Set");
+ jclass entryClass = env->FindClass("java/lang/String");
+ jmethodID iterator =
+ env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
+ jobject iter = env->CallObjectMethod(keys_set, iterator);
+ jobject iter2 = env->CallObjectMethod(keys_set, iterator);
+ jclass iteratorClass = env->FindClass("java/util/Iterator");
+ jmethodID hasNext = env->GetMethodID(iteratorClass, "hasNext", "()Z");
+ jmethodID next =
+ env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
+ jmethodID getString =
+ env->GetMethodID(entryClass, "toString", "()Ljava/lang/String;");
+
+ while (env->CallBooleanMethod(iter, hasNext)) {
+ int i;
+ jobject entry = env->CallObjectMethod(iter, next);
+ jstring string = (jstring) env->CallObjectMethod(entry, getString);
+ const char *str = env->GetStringUTFChars(string, NULL);
+ int found = 0;
+
+ if (!str) { // Out of memory
+ THROW_IO_ERROR(&fmTransmitterSession); /* excecution will continue to cleanup */
+ env->DeleteLocalRef(entry);
+ env->DeleteLocalRef(string);
+ goto free_up_and_leave;
+ }
+
+ for (i = 0; rds_field_names[i] != NULL; i++) {
+ if (!strcmp(rds_field_names[i], str)) {
+ found = 1;
+ break;
+ }
+ }
+
+ env->DeleteLocalRef(entry);
+ env->ReleaseStringUTFChars(string, str);
+ env->DeleteLocalRef(string);
+
+ if (!found) {
+ ALOGE("androidFmRadioTxSetRDSData: Error, invalid key");
+ THROW_ILLEGAL_ARGUMENT(&fmTransmitterSession); /* excecution will continue to cleanup */
+ goto free_up_and_leave;
+ }
+ }
+
+ while (env->CallBooleanMethod(iter2, hasNext)) {
+ jobject entry = env->CallObjectMethod(iter2, next);
+ jstring string = (jstring) env->CallObjectMethod(entry, getString);
+ char *str = (char *) env->GetStringUTFChars(string, NULL);
+ int rv = -1;
+
+ if (!str) { // Out of memory
+ ALOGE("androidFmRadioTxSetRDSData: out of memory");
+ THROW_IO_ERROR(&fmTransmitterSession); /* excecution will continue to cleanup */
+ rv = -2; /* not -1 since we already thrown exception */
+ } else if ((strcmp(str, "PI") == 0) ||
+ (strcmp(str, "TP") == 0) ||
+ (strcmp(str, "PTY") == 0) ||
+ (strcmp(str, "TA") == 0) ||
+ (strcmp(str, "M/S") == 0)) {
+ /* types setting numeric (short) value */
+ int passedval = 0;
+ short value = env->CallShortMethod(bundle, bundle_p->mGetShort,
+ env->NewStringUTF(str));
+
+ passedval = (int) value;
+ rv = fmTransmitterSession.
+ vendorMethods_p->set_rds_data(&fmTransmitterSession.
+ vendorData_p, str,
+ &passedval);
+ } else if (!strcmp(str, "TAF")) {
+ /* type setting numeric (int) value */
+ int value = env->CallIntMethod(bundle, bundle_p->mGetInt,
+ env->NewStringUTF(str));
+
+ rv = fmTransmitterSession.
+ vendorMethods_p->set_rds_data(&fmTransmitterSession.
+ vendorData_p, str,
+ &value);
+ } else if (strcmp(str, "AF") == 0) {
+ /* type setting array of ints */
+ jintArray value = (jintArray) env->CallObjectMethod(bundle,
+ bundle_p->mGetIntArray,
+ env->NewStringUTF
+ (str));
+
+ int numInts = (value ? env->GetArrayLength(value) : 0);
+
+ if (numInts == 0) {
+ env->DeleteLocalRef(entry);
+ env->ReleaseStringUTFChars(string, str);
+ env->DeleteLocalRef(string);
+ goto free_up_and_leave;
+ }
+
+ int *temparray = env->GetIntArrayElements(value, NULL);
+ int *array = (int *) malloc((numInts + 1) * sizeof(*temparray));
+
+
+ if (array != NULL) {
+ // Place a 0 after the final entry
+ memcpy(array, temparray, numInts * sizeof(*temparray));
+ array[numInts] = 0;
+ rv = fmTransmitterSession.vendorMethods_p->
+ set_rds_data(&fmTransmitterSession.vendorData_p, str, array);
+ free(array);
+ } else {
+ ALOGE("android_setRdsData:malloc failed");
+ rv = -1;
+ }
+ env->ReleaseIntArrayElements(value, temparray, 0);
+ } else if (strcmp(str, "TMC") == 0) {
+ /* type setting array of shorts */
+ jshortArray value = (jshortArray) env->CallObjectMethod(bundle,
+ bundle_p->mGetShortArray,
+ env->NewStringUTF
+ ("TMC"));
+ int numShorts = (value ? env->GetArrayLength(value) : 0);
+ short *temparray = env->GetShortArrayElements(value, NULL);
+ short *array = (short *) malloc((numShorts + 1) * sizeof(*temparray));
+
+ if (array != NULL) {
+ // Place a 0 after the final entry
+ memcpy(array, temparray, numShorts * sizeof(*temparray));
+ array[numShorts] = 0;
+ rv = fmTransmitterSession.vendorMethods_p->
+ set_rds_data(&fmTransmitterSession.vendorData_p, str, array);
+ free(array);
+ } else {
+ ALOGE("android_setRdsData:malloc failed");
+ rv = -1;
+ }
+ env->ReleaseShortArrayElements(value, temparray, 0);
+ /* types setting string */
+ } else if ((strcmp(str, "PSN") == 0) ||
+ (strcmp(str, "RT") == 0) ||
+ (strcmp(str, "CT") == 0) ||
+ (strcmp(str, "PTYN") == 0)) {
+ unsigned int maxLength = 0;
+
+ if (strcmp(str, "PSN") == 0) {
+ maxLength = RDS_PSN_MAX_LENGTH;
+ } else if (strcmp(str, "RT") == 0) {
+ maxLength = RDS_RT_MAX_LENGTH;
+ } else if (strcmp(str, "CT") == 0) {
+ maxLength = RDS_CT_MAX_LENGTH;
+ } else if (strcmp(str, "PTYN") == 0) {
+ maxLength = RDS_PTYN_MAX_LENGTH;
+ }
+
+ jstring value = (jstring) env->CallObjectMethod(bundle,
+ bundle_p->
+ mGetString,
+ env->
+ NewStringUTF
+ (str));
+
+ if (value == NULL) {
+ ALOGI("android_setRdsData:No key found for %s", str);
+ rv = -1;
+ } else {
+ const char *cvalue = env->GetStringUTFChars(value, NULL);
+
+ // May need to add termination char
+ if (strlen(cvalue) > maxLength) {
+ ALOGE("android_setRdsData:%s - Too long value.", str);
+ rv = -1;
+ } else {
+ rv = fmTransmitterSession.vendorMethods_p->
+ set_rds_data(&fmTransmitterSession.vendorData_p, str, (char *) cvalue);
+ }
+ env->ReleaseStringUTFChars(value, cvalue);
+ }
+ }
+ if (rv == FMRADIO_UNSUPPORTED_OPERATION) {
+ ALOGE("android_setRdsData: key '%s' unsupported by vendor.", str);
+ } else if (rv < 0){
+ ALOGE("Error processing key '%s'", str);
+ THROW_ILLEGAL_ARGUMENT(&fmTransmitterSession); /* execution will continue to cleanup */
+ }
+ env->DeleteLocalRef(entry);
+ if (str != NULL) {
+ env->ReleaseStringUTFChars(string, str);
+ }
+ env->DeleteLocalRef(string);
+ if (rv < 0 && rv != FMRADIO_UNSUPPORTED_OPERATION) {
+ break;
+ }
+ }
+ free_up_and_leave:
+ env->DeleteLocalRef(entryClass);
+ env->DeleteLocalRef(iteratorClass);
+ env->DeleteLocalRef(iter);
+ env->DeleteLocalRef(iter2);
+ env->DeleteLocalRef(setClass);
+ env->DeleteLocalRef(keys_set);
+ }
+ resume_and_drop_lock:
+ androidFmRadioPauseIfTempResumed(&fmTransmitterSession);
+ drop_lock:
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+}
+
+static jint androidFmRadioTxGetFrequency(JNIEnv * env, jobject obj)
+{
+ ALOGI("androidFmRadioTxGetFrequency \n");
+ return androidFmRadioGetFrequency(&fmTransmitterSession);
+}
+
+static void androidFmRadioTxStopScan(JNIEnv * env, jobject obj)
+{
+ ALOGI("androidFmRadioTxStopScan\n");
+
+ androidFmRadioStopScan(&fmTransmitterSession);
+}
+
+static jboolean
+androidFmRadioTxIsBlockScanSupported(JNIEnv * env, jobject obj)
+{
+ bool retval;
+
+ ALOGI("androidFmRadioTxIsBlockScanSupported:\n");
+
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+
+ /* if we haven't register we don't know yet */
+ if (!fmTransmitterSession.isRegistered) {
+ retval = false;
+ goto drop_lock;
+ }
+ // valid in all states
+ if (fmTransmitterSession.vendorMethods_p->block_scan != NULL) {
+ retval = true;
+ } else {
+ retval = false;
+ }
+
+ drop_lock:
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+ return retval;
+}
+
+static void *execute_androidFmRadioTxBlockScan(void *args_p)
+{
+ struct FmRadioBlockScanParameters *inArgs_p = (struct FmRadioBlockScanParameters *) args_p;
+ int startFreq = inArgs_p->startFreq;
+ int endFreq = inArgs_p->endFreq;
+ int retval;
+ enum FmRadioState_t oldState = fmTransmitterSession.oldState;
+ int *rssi_p = NULL;
+ int *freqs_p = NULL;
+
+ free(inArgs_p);
+
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+
+ /*
+ * we should still be in SCANNING mode, but we can't be 100.00 % sure since
+ * main thread released lock before we could run
+ *
+ */
+
+ if (fmTransmitterSession.state != FMRADIO_STATE_SCANNING) {
+ ALOGE("execute_androidFmRadioTxBlockScan - warning, state not scanning\n");
+ }
+
+ /*
+ * if mode has been changed to IDLE in the mean time by main thread,
+ * exit the worker thread gracefully
+ */
+ if (fmTransmitterSession.state == FMRADIO_STATE_IDLE) {
+ goto drop_lock;
+ }
+ // temporary resume chip if sleeping
+ if (oldState == FMRADIO_STATE_PAUSED) {
+ (void) fmTransmitterSession.vendorMethods_p->
+ resume(&fmTransmitterSession.vendorData_p);
+ }
+
+ if (pthread_cond_signal(&fmTransmitterSession.sync_cond) != 0) {
+ ALOGE("execute_androidFmRadioTxBlockScan - warning, signal failed\n");
+ }
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+
+ retval =
+ fmTransmitterSession.
+ vendorMethods_p->block_scan(&fmTransmitterSession.vendorData_p,
+ startFreq, endFreq, &freqs_p, &rssi_p);
+
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+
+ /*
+ * if state has changed we should keep it, probably a forced pause or
+ * forced reset
+ */
+ if (fmTransmitterSession.state != FMRADIO_STATE_SCANNING) {
+ ALOGI("State changed while scanning (state now %d), keeping\n",
+ fmTransmitterSession.state);
+ retval = -1;
+ } else {
+ if (fmTransmitterSession.pendingPause) {
+ FMRADIO_SET_STATE(&fmTransmitterSession, FMRADIO_STATE_PAUSED);
+ } else {
+ FMRADIO_SET_STATE(&fmTransmitterSession, oldState);
+ }
+
+ fmTransmitterSession.pendingPause = false;
+ }
+
+ if (retval >= 0) {
+ fmTransmitterSession.callbacks_p->onBlockScan(retval, freqs_p,
+ rssi_p,
+ fmTransmitterSession.
+ lastScanAborted);
+ } else {
+ fmTransmitterSession.callbacks_p->onError();
+ }
+
+ drop_lock:
+
+ if (rssi_p != NULL) {
+ free(rssi_p);
+ }
+
+ if (freqs_p != NULL) {
+ free(freqs_p);
+ }
+
+ /* Wake up the main thread if it is currently waiting on the condition variable */
+ if (pthread_cond_signal(&fmTransmitterSession.sync_cond) != 0) {
+ ALOGE("execute_androidFmRadioTxBlockScan - signal failed\n");
+ }
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+
+ pthread_exit(NULL);
+
+ return NULL;
+}
+
+static void
+androidFmRadioTxStartBlockScan(JNIEnv * env, jobject obj,
+ int startFreq, int endFreq)
+{
+ int retval = 0;
+
+ ALOGI("androidFmRadioTxStartBlockScan, From = %d, To = %d\n",
+ startFreq, endFreq);
+
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+
+ if (!androidFmRadioIsValidEventForState
+ (&fmTransmitterSession, FMRADIO_EVENT_BLOCK_SCAN)) {
+ retval = FMRADIO_INVALID_STATE;
+ goto drop_lock;
+ }
+
+ if (fmTransmitterSession.vendorMethods_p->block_scan) {
+ struct FmRadioBlockScanParameters* args_p = (struct FmRadioBlockScanParameters*) malloc(sizeof(struct FmRadioBlockScanParameters));
+
+ pthread_t execute_thread;
+
+ args_p->startFreq = startFreq;
+ args_p->endFreq = endFreq;
+
+ fmTransmitterSession.oldState = fmTransmitterSession.state;
+
+ FMRADIO_SET_STATE(&fmTransmitterSession, FMRADIO_STATE_SCANNING);
+
+ fmTransmitterSession.lastScanAborted = false;
+
+ if (pthread_create
+ (&execute_thread, NULL, execute_androidFmRadioTxBlockScan,
+ args_p) != 0) {
+
+ ALOGE("pthread_create failure...\n");
+ free(args_p);
+
+ FMRADIO_SET_STATE(&fmTransmitterSession, fmTransmitterSession.oldState);
+ retval = FMRADIO_IO_ERROR;
+ } else {
+ /* await thread startup, THREAD_WAIT_TIMEOUT_S sec timeout */
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += THREAD_WAIT_TIMEOUT_S;
+ if (pthread_cond_timedwait(&fmTransmitterSession.sync_cond,
+ fmTransmitterSession.dataMutex_p,
+ &ts) != 0) {
+ ALOGE("androidFmRadioTxStartBlockScan: warning, wait failure\n");
+ }
+ pthread_detach(execute_thread);
+ }
+ } else {
+ retval = FMRADIO_UNSUPPORTED_OPERATION;
+ }
+
+ drop_lock:
+ if (retval == FMRADIO_INVALID_STATE) {
+ THROW_INVALID_STATE(&fmTransmitterSession);
+ } else if (retval < 0) {
+ THROW_IO_ERROR(&fmTransmitterSession);
+ }
+
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+}
+
+static jboolean androidFmRadioTxSendExtraCommand(JNIEnv * env, jobject obj,
+ jstring command,
+ jobjectArray parameters)
+{
+ ALOGI("androidFmRadioTxSendExtraCommand");
+
+ /* we need to set jobj since this might be called before start */
+
+
+ if (fmTransmitterSession.jobj == NULL)
+ fmTransmitterSession.jobj = env->NewGlobalRef(obj);
+
+ androidFmRadioSendExtraCommand(&fmTransmitterSession, env, command,
+ parameters);
+
+ return true;
+}
+
+static JNINativeMethod gMethods[] = {
+ {(char*)"_fm_transmitter_getState", (char*)"()I",
+ (void *) androidFmRadioTxGetState},
+ {(char*)"_fm_transmitter_start", (char*)"(IIII)V",
+ (void *) androidFmRadioTxStart},
+ {(char*)"_fm_transmitter_startAsync", (char*)"(IIII)V",
+ (void *) androidFmRadioTxStartAsync},
+ {(char*)"_fm_transmitter_pause", (char*)"()V",
+ (void *) androidFmRadioTxPause},
+ {(char*)"_fm_transmitter_resume", (char*)"()V",
+ (void *) androidFmRadioTxResume},
+ {(char*)"_fm_transmitter_reset", (char*)"()I",
+ (void *) androidFmRadioTxReset},
+ {(char*)"_fm_transmitter_setFrequency", (char*)"(I)V",
+ (void *) androidFmRadioTxSetFrequency},
+ {(char*)"_fm_transmitter_getFrequency", (char*)"()I",
+ (void *) androidFmRadioTxGetFrequency},
+ {(char*)"_fm_transmitter_isBlockScanSupported", (char*)"()Z",
+ (void *) androidFmRadioTxIsBlockScanSupported},
+ {(char*)"_fm_transmitter_startBlockScan", (char*)"(II)V",
+ (void *) androidFmRadioTxStartBlockScan},
+ {(char*)"_fm_transmitter_stopScan", (char*)"()V",
+ (void *) androidFmRadioTxStopScan},
+ {(char*)"_fm_transmitter_setRdsData", (char*)"(Landroid/os/Bundle;)V",
+ (void *) androidFmRadioTxSetRDSData},
+ {(char*)"_fm_transmitter_sendExtraCommand",
+ (char*)"(Ljava/lang/String;[Ljava/lang/String;)Z",
+ (void *) androidFmRadioTxSendExtraCommand},
+
+};
+
+int registerAndroidFmRadioTransmitter(JavaVM * vm, JNIEnv * env)
+{
+
+ ALOGI("registerAndroidFmRadioTransmitter\n");
+ pthread_mutex_lock(fmTransmitterSession.dataMutex_p);
+ fmTransmitterSession.jvm_p = vm;
+ // setRDS bundle handling
+ jclass clazz = env->FindClass("android/os/Bundle");
+
+
+ struct bundle_descriptor_offsets_t *bundle_p =
+ (struct bundle_descriptor_offsets_t *)
+ malloc(sizeof(struct bundle_descriptor_offsets_t));
+
+ bundle_p->mSize = env->GetMethodID(clazz, "size", "()I");
+ bundle_p->mGetInt =
+ env->GetMethodID(clazz, "getInt", "(Ljava/lang/String;)I");
+ bundle_p->mGetIntArray =
+ env->GetMethodID(clazz, "getIntArray", "(Ljava/lang/String;)[I");
+ bundle_p->mGetShort =
+ env->GetMethodID(clazz, "getShort", "(Ljava/lang/String;)S");
+ bundle_p->mGetShortArray =
+ env->GetMethodID(clazz, "getShortArray", "(Ljava/lang/String;)[S");
+ bundle_p->mGetString =
+ env->GetMethodID(clazz, "getString",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+ bundle_p->mContainsKey =
+ env->GetMethodID(clazz, "containsKey", "(Ljava/lang/String;)Z");
+ bundle_p->mKeySet =
+ env->GetMethodID(clazz, "keySet", "()Ljava/util/Set;");
+ bundle_p->mClass = (jclass) env->NewGlobalRef(clazz);
+ bundle_p->mConstructor = env->GetMethodID(clazz, "<init>", "()V");
+ bundle_p->mPutInt =
+ env->GetMethodID(clazz, "putInt", "(Ljava/lang/String;I)V");
+ bundle_p->mPutIntArray =
+ env->GetMethodID(clazz, "putIntArray", "(Ljava/lang/String;[I)V");
+ bundle_p->mPutShortArray =
+ env->GetMethodID(clazz, "putShortArray",
+ "(Ljava/lang/String;[S)V");
+ bundle_p->mPutString =
+ env->GetMethodID(clazz, "putString",
+ "(Ljava/lang/String;Ljava/lang/String;)V");
+
+ fmTransmitterSession.bundleOffsets_p = bundle_p;
+ pthread_mutex_unlock(fmTransmitterSession.dataMutex_p);
+
+ return jniRegisterNativeMethods(env,
+ "com/stericsson/hardware/fm/FmTransmitterService",
+ gMethods, NELEM(gMethods));
+}
+
+/* *INDENT-OFF* */
+}; // namespace android
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fe04911..e35446e89 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -76,6 +76,8 @@ import dalvik.system.Zygote;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
+import com.stericsson.hardware.fm.FmReceiverService;
+import com.stericsson.hardware.fm.FmTransmitterService;
class ServerThread extends Thread {
private static final String TAG = "SystemServer";
@@ -550,6 +552,21 @@ class ServerThread extends Thread {
}
try {
+ Slog.i(TAG, "FM receiver Service");
+ ServiceManager.addService("fm_receiver",
+ new FmReceiverService(context));
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting FM receiver Service", e);
+ }
+
+ try {
+ Slog.i(TAG, "FM transmitter Service");
+ ServiceManager.addService("fm_transmitter",
+ new FmTransmitterService(context));
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting FM transmitter Service", e);
+ }
+ try {
Slog.i(TAG, "UpdateLock Service");
ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
new UpdateLockService(context));