diff options
author | Anurag Gupta <anurag.gupta@stericsson.com> | 2012-07-18 16:18:35 +0530 |
---|---|---|
committer | Gerrit Code Review <gerrit@review.cyanogenmod.com> | 2013-02-09 10:39:42 -0800 |
commit | 44f2f8d10e87fd315abeb267a3159f2e00ce8e73 (patch) | |
tree | 538615c69de88d72173a6e1683744288a9e34978 /fmradio | |
parent | c26fe2aa66679a2f64d9fb8fd28d7149293fe421 (diff) | |
download | frameworks_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
Diffstat (limited to 'fmradio')
28 files changed, 10962 insertions, 0 deletions
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("fmradio://rx"); + * 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 |