blob: 876c9d432efc65a9769d43531d93bfbce4042bbf [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.car.radio;
import android.app.Service;
import android.car.hardware.radio.CarRadioManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.support.annotation.Nullable;
import android.support.car.Car;
import android.support.car.CarNotConnectedException;
import android.support.car.CarConnectionCallback;
import android.support.car.media.CarAudioManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.car.radio.demo.RadioDemo;
import com.android.car.radio.service.IRadioCallback;
import com.android.car.radio.service.IRadioManager;
import com.android.car.radio.service.RadioRds;
import com.android.car.radio.service.RadioStation;
import java.util.ArrayList;
import java.util.List;
/**
* A persistent {@link Service} that is responsible for opening and closing a {@link RadioTuner}.
* All radio operations should be delegated to this class. To be notified of any changes in radio
* metadata, register as a {@link android.hardware.radio.RadioTuner.Callback} on this Service.
*
* <p>Utilize the {@link RadioBinder} to perform radio operations.
*/
public class RadioService extends Service implements AudioManager.OnAudioFocusChangeListener {
private static String TAG = "Em.RadioService";
/**
* The amount of time to wait before re-trying to open the {@link #mRadioTuner}.
*/
private static final int RADIO_TUNER_REOPEN_DELAY_MS = 5000;
private int mReOpenRadioTunerCount = 0;
private final Handler mHandler = new Handler();
private Car mCarApi;
private RadioTuner mRadioTuner;
private boolean mRadioSuccessfullyInitialized;
private int mCurrentRadioBand = RadioManager.BAND_FM;
private int mCurrentRadioChannel = RadioStorage.INVALID_RADIO_CHANNEL;
private String mCurrentChannelInfo;
private String mCurrentArtist;
private String mCurrentSongTitle;
private RadioManager mRadioManager;
private RadioBackgroundScanner mBackgroundScanner;
private RadioManager.FmBandDescriptor mFmDescriptor;
private RadioManager.AmBandDescriptor mAmDescriptor;
private RadioManager.FmBandConfig mFmConfig;
private RadioManager.AmBandConfig mAmConfig;
private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
private CarAudioManager mCarAudioManager;
private AudioAttributes mRadioAudioAttributes;
/**
* Whether or not this {@link RadioService} currently has audio focus, meaning it is the
* primary driver of media. Usually, interaction with the radio will be prefaced with an
* explicit request for audio focus. However, this is not ideal when muting the radio, so this
* state needs to be tracked.
*/
private boolean mHasAudioFocus;
/**
* An internal {@link android.hardware.radio.RadioTuner.Callback} that will listen for
* changes in radio metadata and pass these method calls through to
* {@link #mRadioTunerCallbacks}.
*/
private RadioTuner.Callback mInternalRadioTunerCallback = new InternalRadioCallback();
private List<IRadioCallback> mRadioTunerCallbacks = new ArrayList<>();
@Override
public IBinder onBind(Intent intent) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onBind(); Intent: " + intent);
}
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onCreate()");
}
// Connection to car services does not work for non-automotive yet, so this call needs to
// be guarded.
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
mCarApi = Car.createCar(this /* context */, mCarConnectionCallback);
mCarApi.connect();
}
if (SystemProperties.getBoolean(RadioDemo.DEMO_MODE_PROPERTY, false)) {
initializeDemo();
} else {
initialze();
}
}
/**
* Initializes this service to use a demo {@link IRadioManager}.
*
* @see {@link RadioDemo}
*/
private void initializeDemo() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "initializeDemo()");
}
mBinder = RadioDemo.getInstance(this /* context */).createDemoManager();
}
/**
* Connects to the {@link RadioManager}.
*/
private void initialze() {
mRadioManager = (RadioManager) getSystemService(Context.RADIO_SERVICE);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "initialze(); mRadioManager: " + mRadioManager);
}
if (mRadioManager == null) {
Log.w(TAG, "RadioManager could not be loaded.");
return;
}
int status = mRadioManager.listModules(mModules);
if (status != RadioManager.STATUS_OK) {
Log.w(TAG, "Load modules failed with status: " + status);
return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "initialze(); listModules complete: " + mModules);
}
if (mModules.size() == 0) {
Log.w(TAG, "No radio modules on device.");
return;
}
boolean isDebugLoggable = Log.isLoggable(TAG, Log.DEBUG);
// Load the possible radio bands. For now, just accept FM and AM bands.
for (RadioManager.BandDescriptor band : mModules.get(0).getBands()) {
if (isDebugLoggable) {
Log.d(TAG, "loading band: " + band.toString());
}
if (mFmDescriptor == null && band.getType() == RadioManager.BAND_FM) {
mFmDescriptor = (RadioManager.FmBandDescriptor) band;
}
if (mAmDescriptor == null && band.getType() == RadioManager.BAND_AM) {
mAmDescriptor = (RadioManager.AmBandDescriptor) band;
}
}
if (mFmDescriptor == null && mAmDescriptor == null) {
Log.w(TAG, "No AM and FM radio bands could be loaded.");
return;
}
// TODO: Make stereo configurable depending on device.
mFmConfig = new RadioManager.FmBandConfig.Builder(mFmDescriptor)
.setStereo(true)
.build();
mAmConfig = new RadioManager.AmBandConfig.Builder(mAmDescriptor)
.setStereo(true)
.build();
// If there is a second tuner on the device, then set it up as the background scanner.
if (mModules.size() >= 2) {
if (isDebugLoggable) {
Log.d(TAG, "Second tuner detected on device; setting up background scanner");
}
mBackgroundScanner = new RadioBackgroundScanner(this /* context */, mRadioManager,
mAmConfig, mFmConfig, mModules.get(1));
}
mRadioSuccessfullyInitialized = true;
}
@Override
public void onDestroy() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onDestroy()");
}
close();
if (mCarApi != null) {
mCarApi.disconnect();
}
super.onDestroy();
}
/**
* Opens the current radio band. Currently, this only supports FM and AM bands.
*
* @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
* {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
* @return {@link RadioManager#STATUS_OK} if successful; otherwise,
* {@link RadioManager#STATUS_ERROR}.
*/
private int openRadioBandInternal(int radioBand) {
if (requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.e(TAG, "openRadioBandInternal() audio focus request fail");
return RadioManager.STATUS_ERROR;
}
mCurrentRadioBand = radioBand;
RadioManager.BandConfig config = getRadioConfig(radioBand);
if (config == null) {
Log.w(TAG, "Cannot create config for radio band: " + radioBand);
return RadioManager.STATUS_ERROR;
}
if (mRadioTuner != null) {
mRadioTuner.setConfiguration(config);
} else {
mRadioTuner = mRadioManager.openTuner(mModules.get(0).getId(), config, true,
mInternalRadioTunerCallback, null /* handler */);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "openRadioBandInternal() STATUS_OK");
}
if (mBackgroundScanner != null) {
mBackgroundScanner.onRadioBandChanged(radioBand);
}
// Reset the counter for exponential backoff each time the radio tuner has been successfully
// opened.
mReOpenRadioTunerCount = 0;
return RadioManager.STATUS_OK;
}
/**
* Returns a {@link RadioRds} object that holds all the current radio metadata. If all the
* metadata is empty, then {@code null} is returned.
*/
@Nullable
private RadioRds createCurrentRadioRds() {
if (TextUtils.isEmpty(mCurrentChannelInfo) && TextUtils.isEmpty(mCurrentArtist)
&& TextUtils.isEmpty(mCurrentSongTitle)) {
return null;
}
return new RadioRds(mCurrentChannelInfo, mCurrentArtist, mCurrentSongTitle);
}
/**
* Creates a {@link RadioStation} that encapsulates all the information about the current
* radio station.
*/
private RadioStation createCurrentRadioStation() {
// mCurrentRadioChannel can possibly be invalid if this class never receives a callback
// for onProgramInfoChanged(). As a result, manually retrieve the information for the
// current station from RadioTuner if this is the case.
if (mCurrentRadioChannel == RadioStorage.INVALID_RADIO_CHANNEL && mRadioTuner != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "createCurrentRadioStation(); invalid current radio channel. "
+ "Calling getProgramInformation for valid station");
}
// getProgramInformation() expects an array of size 1.
RadioManager.ProgramInfo[] info = new RadioManager.ProgramInfo[1];
int status = mRadioTuner.getProgramInformation(info);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "getProgramInformation() status: " + status + "; info: " + info[0]);
}
if (status == RadioManager.STATUS_OK && info[0] != null) {
mCurrentRadioChannel = info[0].getChannel();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "program info channel: " + mCurrentRadioChannel);
}
}
}
return new RadioStation(mCurrentRadioChannel, 0 /* subChannelNumber */,
mCurrentRadioBand, createCurrentRadioRds());
}
/**
* Returns the proper {@link android.hardware.radio.RadioManager.BandConfig} for the given
* radio band. {@code null} is returned if the band is not suppored.
*/
@Nullable
private RadioManager.BandConfig getRadioConfig(int selectedRadioBand) {
switch (selectedRadioBand) {
case RadioManager.BAND_AM:
return mAmConfig;
case RadioManager.BAND_FM:
return mFmConfig;
// TODO: Support BAND_FM_HD and BAND_AM_HD.
default:
return null;
}
}
private int requestAudioFocus() {
int status = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
try {
status = mCarAudioManager.requestAudioFocus(this, mRadioAudioAttributes,
AudioManager.AUDIOFOCUS_GAIN, 0);
} catch (CarNotConnectedException e) {
Log.e(TAG, "requestAudioFocus() failed", e);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "requestAudioFocus status: " + status);
}
if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
mHasAudioFocus = true;
// Receiving audio focus means that the radio is un-muted.
for (IRadioCallback callback : mRadioTunerCallbacks) {
try {
callback.onRadioMuteChanged(false);
} catch (RemoteException e) {
Log.e(TAG, "requestAudioFocus(); onRadioMuteChanged() notify failed: "
+ e.getMessage());
}
}
}
return status;
}
private void abandonAudioFocus() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "abandonAudioFocus()");
}
if (mCarAudioManager == null) {
return;
}
mCarAudioManager.abandonAudioFocus(this, mRadioAudioAttributes);
mHasAudioFocus = false;
for (IRadioCallback callback : mRadioTunerCallbacks) {
try {
callback.onRadioMuteChanged(true);
} catch (RemoteException e) {
Log.e(TAG, "abandonAudioFocus(); onRadioMutechanged() notify failed: "
+ e.getMessage());
}
}
}
/**
* Closes any active {@link RadioTuner}s and releases audio focus.
*/
private void close() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "close()");
}
abandonAudioFocus();
if (mRadioTuner != null) {
mRadioTuner.close();
mRadioTuner = null;
}
}
@Override
public void onAudioFocusChange(int focusChange) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "focus change: " + focusChange);
}
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
mHasAudioFocus = true;
openRadioBandInternal(mCurrentRadioBand);
break;
// For a transient loss, just allow the focus to be released. The radio will stop
// itself automatically. There is no need for an explicit abandon audio focus call
// because this removes the AudioFocusChangeListener.
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
mHasAudioFocus = false;
break;
case AudioManager.AUDIOFOCUS_LOSS:
close();
break;
default:
// Do nothing for all other cases.
}
}
/**
* {@link CarConnectionCallback} that retrieves the {@link CarRadioManager}.
*/
private final CarConnectionCallback mCarConnectionCallback =
new CarConnectionCallback() {
@Override
public void onConnected(Car car) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Car service connected.");
}
try {
// The CarAudioManager only needs to be retrieved once.
if (mCarAudioManager == null) {
mCarAudioManager = (CarAudioManager) mCarApi.getCarManager(
android.car.Car.AUDIO_SERVICE);
mRadioAudioAttributes = mCarAudioManager.getAudioAttributesForCarUsage(
CarAudioManager.CAR_AUDIO_USAGE_RADIO);
}
} catch (CarNotConnectedException e) {
//TODO finish
Log.e(TAG, "Car not connected");
}
}
@Override
public void onDisconnected(Car car) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Car service disconnected.");
}
}
};
private IRadioManager.Stub mBinder = new IRadioManager.Stub() {
/**
* Tunes the radio to the given frequency. To be notified of a successful tune, register
* as a {@link android.hardware.radio.RadioTuner.Callback}.
*/
@Override
public void tune(RadioStation radioStation) {
if (mRadioManager == null || radioStation == null
|| requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return;
}
if (mRadioTuner == null || radioStation.getRadioBand() != mCurrentRadioBand) {
int radioStatus = openRadioBandInternal(radioStation.getRadioBand());
if (radioStatus == RadioManager.STATUS_ERROR) {
return;
}
}
int status = mRadioTuner.tune(radioStation.getChannelNumber(), 0 /* subChannel */);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Tuning to station: " + radioStation + "\n\tstatus: " + status);
}
}
/**
* Seeks the radio forward. To be notified of a successful tune, register as a
* {@link android.hardware.radio.RadioTuner.Callback}.
*/
@Override
public void seekForward() {
if (mRadioManager == null
|| requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return;
}
if (mRadioTuner == null) {
int radioStatus = openRadioBandInternal(mCurrentRadioBand);
if (radioStatus == RadioManager.STATUS_ERROR) {
return;
}
}
mRadioTuner.scan(RadioTuner.DIRECTION_UP, true);
}
/**
* Seeks the radio backwards. To be notified of a successful tune, register as a
* {@link android.hardware.radio.RadioTuner.Callback}.
*/
@Override
public void seekBackward() {
if (mRadioManager == null
|| requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return;
}
if (mRadioTuner == null) {
int radioStatus = openRadioBandInternal(mCurrentRadioBand);
if (radioStatus == RadioManager.STATUS_ERROR) {
return;
}
}
mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
}
/**
* Mutes the radio.
*
* @return {@code true} if the mute was successful.
*/
@Override
public boolean mute() {
if (mRadioManager == null) {
return false;
}
if (mCarAudioManager == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "mute() called, but not connected to CarAudioManager");
}
return false;
}
// If the radio does not currently have focus, then no need to do anything because the
// radio won't be playing any sound.
if (!mHasAudioFocus) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "mute() called, but radio does not currently have audio focus; "
+ "ignoring.");
}
return false;
}
boolean muteSuccessful = false;
try {
muteSuccessful = mCarAudioManager.setMediaMute(true);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "setMediaMute(true) status: " + muteSuccessful);
}
} catch (CarNotConnectedException e) {
Log.e(TAG, "mute() failed: " + e.getMessage());
e.printStackTrace();
}
if (muteSuccessful && mRadioTunerCallbacks.size() > 0) {
for (IRadioCallback callback : mRadioTunerCallbacks) {
try {
callback.onRadioMuteChanged(true);
} catch (RemoteException e) {
Log.e(TAG, "mute() notify failed: " + e.getMessage());
}
}
}
return muteSuccessful;
}
/**
* Un-mutes the radio and causes audio to play.
*
* @return {@code true} if the un-mute was successful.
*/
@Override
public boolean unMute() {
if (mRadioManager == null) {
return false;
}
if (mCarAudioManager == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "toggleMute() called, but not connected to CarAudioManager");
}
return false;
}
// Requesting audio focus will automatically un-mute the radio if it had been muted.
return requestAudioFocus() == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
/**
* Returns {@code true} if the radio is currently muted.
*/
@Override
public boolean isMuted() {
if (!mHasAudioFocus) {
return true;
}
if (mRadioManager == null) {
return true;
}
if (mCarAudioManager == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "isMuted() called, but not connected to CarAudioManager");
}
return true;
}
boolean isMuted = false;
try {
isMuted = mCarAudioManager.isMediaMuted();
} catch (CarNotConnectedException e) {
Log.e(TAG, "isMuted() failed: " + e.getMessage());
e.printStackTrace();
}
return isMuted;
}
/**
* Opens the radio for the given band.
*
* @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
* {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
* @return {@link RadioManager#STATUS_OK} if successful; otherwise,
* {@link RadioManager#STATUS_ERROR}.
*/
@Override
public int openRadioBand(int radioBand) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "openRadioBand() for band: " + radioBand);
}
if (mRadioManager == null) {
return RadioManager.STATUS_ERROR;
}
return openRadioBandInternal(radioBand);
}
/**
* Adds the given {@link android.hardware.radio.RadioTuner.Callback} to be notified
* of any radio metadata changes.
*/
@Override
public void addRadioTunerCallback(IRadioCallback callback) {
if (callback == null) {
return;
}
mRadioTunerCallbacks.add(callback);
}
/**
* Removes the given {@link android.hardware.radio.RadioTuner.Callback} from receiving
* any radio metadata chagnes.
*/
@Override
public void removeRadioTunerCallback(IRadioCallback callback) {
if (callback == null) {
return;
}
mRadioTunerCallbacks.remove(callback);
}
/**
* Returns a {@link RadioStation} that encapsulates the information about the current
* station the radio is tuned to.
*/
@Override
public RadioStation getCurrentRadioStation() {
return createCurrentRadioStation();
}
/**
* Returns {@code true} if the radio was able to successfully initialize. A value of
* {@code false} here could mean that the {@code RadioService} was not able to connect to
* the {@link RadioManager} or there were no radio modules on the current device.
*/
@Override
public boolean isInitialized() {
return mRadioSuccessfullyInitialized;
}
/**
* Returns {@code true} if the radio currently has focus and is therefore the application
* that is supplying music.
*/
@Override
public boolean hasFocus() {
return mHasAudioFocus;
}
/**
* Returns {@code true} if the current radio module has dual tuners, meaning that a tuner
* is available to scan for stations in the background.
*/
@Override
public boolean hasDualTuners() {
return mModules.size() >= 2;
}
};
/**
* A extension of {@link android.hardware.radio.RadioTuner.Callback} that delegates to a
* callback registered on this service.
*/
private class InternalRadioCallback extends RadioTuner.Callback {
@Override
public void onProgramInfoChanged(RadioManager.ProgramInfo info) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onProgramInfoChanged(); info: " + info);
}
clearMetadata();
if (info != null) {
mCurrentRadioChannel = info.getChannel();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onProgramInfoChanged(); info channel: " + mCurrentRadioChannel);
}
}
RadioStation station = createCurrentRadioStation();
try {
for (IRadioCallback callback : mRadioTunerCallbacks) {
callback.onRadioStationChanged(station);
}
} catch (RemoteException e) {
Log.e(TAG, "onProgramInfoChanged(); "
+ "Failed to notify IRadioCallbacks: " + e.getMessage());
}
}
@Override
public void onMetadataChanged(RadioMetadata metadata) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onMetadataChanged(); metadata: " + metadata);
}
clearMetadata();
updateMetadata(metadata);
RadioRds radioRds = createCurrentRadioRds();
try {
for (IRadioCallback callback : mRadioTunerCallbacks) {
callback.onRadioMetadataChanged(radioRds);
}
} catch (RemoteException e) {
Log.e(TAG, "onMetadataChanged(); "
+ "Failed to notify IRadioCallbacks: " + e.getMessage());
}
}
@Override
public void onConfigurationChanged(RadioManager.BandConfig config) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConfigurationChanged(): config: " + config);
}
clearMetadata();
if (config != null) {
mCurrentRadioBand = config.getType();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConfigurationChanged(): config type: " + mCurrentRadioBand);
}
}
try {
for (IRadioCallback callback : mRadioTunerCallbacks) {
callback.onRadioBandChanged(mCurrentRadioBand);
}
} catch (RemoteException e) {
Log.e(TAG, "onConfigurationChanged(); "
+ "Failed to notify IRadioCallbacks: " + e.getMessage());
}
}
@Override
public void onError(int status) {
Log.e(TAG, "onError(); status: " + status);
// If there is a hardware failure or the radio service died, then this requires a
// re-opening of the radio tuner.
if (status == RadioTuner.ERROR_HARDWARE_FAILURE
|| status == RadioTuner.ERROR_SERVER_DIED) {
if (mRadioTuner != null) {
mRadioTuner.close();
mRadioTuner = null;
}
// Attempt to re-open the RadioTuner. Each time the radio tuner fails to open, the
// mReOpenRadioTunerCount will be incremented.
mHandler.removeCallbacks(mOpenRadioTunerRunnable);
mHandler.postDelayed(mOpenRadioTunerRunnable,
mReOpenRadioTunerCount * RADIO_TUNER_REOPEN_DELAY_MS);
mReOpenRadioTunerCount++;
}
try {
for (IRadioCallback callback : mRadioTunerCallbacks) {
callback.onError(status);
}
} catch (RemoteException e) {
Log.e(TAG, "onError(); Failed to notify IRadioCallbacks: " + e.getMessage());
}
}
@Override
public void onControlChanged(boolean control) {
// If the radio loses control of the RadioTuner, then close it and allow it to be
// re-opened when control has been gained.
if (!control) {
close();
return;
}
if (mRadioTuner == null) {
openRadioBandInternal(mCurrentRadioBand);
}
}
/**
* Sets all metadata fields to {@code null}.
*/
private void clearMetadata() {
mCurrentChannelInfo = null;
mCurrentArtist = null;
mCurrentSongTitle = null;
}
/**
* Retrieves the relevant information off the given {@link RadioMetadata} object and
* sets them correspondingly on {@link #mCurrentChannelInfo}, {@link #mCurrentArtist}
* and {@link #mCurrentSongTitle}.
*/
private void updateMetadata(RadioMetadata metadata) {
if (metadata != null) {
mCurrentChannelInfo = metadata.getString(RadioMetadata.METADATA_KEY_RDS_PS);
mCurrentArtist = metadata.getString(RadioMetadata.METADATA_KEY_ARTIST);
mCurrentSongTitle = metadata.getString(RadioMetadata.METADATA_KEY_TITLE);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, String.format("updateMetadata(): [channel info: %s, artist: %s, "
+ "song title: %s]", mCurrentChannelInfo, mCurrentArtist,
mCurrentSongTitle));
}
}
}
}
private final Runnable mOpenRadioTunerRunnable = () -> openRadioBandInternal(mCurrentRadioBand);
}