diff options
Diffstat (limited to 'src/com/android/car/stream/radio/RadioStreamProducer.java')
-rw-r--r-- | src/com/android/car/stream/radio/RadioStreamProducer.java | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/com/android/car/stream/radio/RadioStreamProducer.java b/src/com/android/car/stream/radio/RadioStreamProducer.java new file mode 100644 index 0000000..4c36650 --- /dev/null +++ b/src/com/android/car/stream/radio/RadioStreamProducer.java @@ -0,0 +1,326 @@ +/* + * 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.stream.radio; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +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 com.android.car.stream.R; +import com.android.car.stream.StreamProducer; + +/** + * A {@link StreamProducer} that will connect to the {@link IRadioManager} and produce cards + * corresponding to the currently playing radio station. + */ +public class RadioStreamProducer extends StreamProducer { + private static final String TAG = "RadioStreamProducer"; + + /** + * The amount of time to wait before re-trying to connect to {@link IRadioManager}. + */ + private static final int SERVICE_CONNECTION_RETRY_DELAY_MS = 5000; + + // Radio actions that are used by broadcasts that occur on interaction with the radio card. + static final int ACTION_SEEK_FORWARD = 1; + static final int ACTION_SEEK_BACKWARD = 2; + static final int ACTION_PAUSE = 3; + static final int ACTION_PLAY = 4; + static final int ACTION_STOP = 5; + + /** + * The action in an {@link Intent} that is meant to effect certain radio actions. + */ + static final String RADIO_INTENT_ACTION = + "com.android.car.stream.radio.RADIO_INTENT_ACTION"; + + /** + * The extra within the {@link Intent} that points to the specific action to be taken on the + * radio. + */ + static final String RADIO_ACTION_EXTRA = "radio_action_extra"; + + private final Handler mHandler = new Handler(); + + private IRadioManager mRadioManager; + private RadioActionReceiver mReceiver; + private final RadioConverter mConverter; + + /** + * The number of times that this stream producer has attempted to reconnect to the + * {@link IRadioManager} after a failure to bind. + */ + private int mConnectionRetryCount; + + private int mCurrentChannelNumber; + private int mCurrentBand; + + public RadioStreamProducer(Context context) { + super(context); + mConverter = new RadioConverter(context); + } + + @Override + public void start() { + super.start(); + + mReceiver = new RadioActionReceiver(); + mContext.registerReceiver(mReceiver, new IntentFilter(RADIO_INTENT_ACTION)); + + bindRadioService(); + } + + @Override + public void stop() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "stop()"); + } + + mHandler.removeCallbacks(mServiceConnectionRetry); + + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + + mContext.unbindService(mServiceConnection); + super.stop(); + } + + /** + * Binds to the RadioService and returns {@code true} if the connection was successful. + */ + private boolean bindRadioService() { + Intent radioService = new Intent(); + radioService.setComponent(new ComponentName( + mContext.getString(R.string.car_radio_component_package), + mContext.getString(R.string.car_radio_component_service))); + + boolean bound = + !mContext.bindService(radioService, mServiceConnection, Context.BIND_AUTO_CREATE); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "bindRadioService(). Connected to radio service: " + bound); + } + + return bound; + } + + /** + * A {@link BroadcastReceiver} that listens for Intents that have the action + * {@link #RADIO_INTENT_ACTION} and corresponding parses the action event within it to effect + * radio playback. + */ + private class RadioActionReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (mRadioManager == null || !RADIO_INTENT_ACTION.equals(intent.getAction())) { + return; + } + + int radioAction = intent.getIntExtra(RADIO_ACTION_EXTRA, -1); + if (radioAction == -1) { + return; + } + + switch (radioAction) { + case ACTION_SEEK_FORWARD: + try { + mRadioManager.seekForward(); + } catch (RemoteException e) { + Log.e(TAG, "Seek forward exception: " + e.getMessage()); + } + break; + + case ACTION_SEEK_BACKWARD: + try { + mRadioManager.seekBackward(); + } catch (RemoteException e) { + Log.e(TAG, "Seek backward exception: " + e.getMessage()); + } + break; + + case ACTION_PLAY: + try { + mRadioManager.unMute(); + } catch (RemoteException e) { + Log.e(TAG, "Radio play exception: " + e.getMessage()); + } + break; + + case ACTION_STOP: + case ACTION_PAUSE: + try { + mRadioManager.mute(); + } catch (RemoteException e) { + Log.e(TAG, "Radio pause exception: " + e.getMessage()); + } + break; + + default: + // Do nothing. + } + } + } + + /** + * A {@link IRadioCallback} that will be notified of various state changes in the radio station. + * Upon these changes, it will push a new {@link com.android.car.stream.StreamCard} to the + * Stream service. + */ + private final IRadioCallback.Stub mCallback = new IRadioCallback.Stub() { + @Override + public void onRadioStationChanged(RadioStation station) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onRadioStationChanged: " + station); + } + + mCurrentBand = station.getRadioBand(); + mCurrentChannelNumber = station.getChannelNumber(); + + if (mRadioManager == null) { + return; + } + + try { + boolean isPlaying = !mRadioManager.isMuted(); + postCard(mConverter.convert(station, isPlaying)); + } catch (RemoteException e) { + Log.e(TAG, "Post radio station changed error: " + e.getMessage()); + } + } + + @Override + public void onRadioMetadataChanged(RadioRds rds) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onRadioMetadataChanged: " + rds); + } + + // Ignore metadata changes because this will overwhelm the notifications. Instead, + // Only display the metadata that is retrieved in onRadioStationChanged(). + } + + @Override + public void onRadioBandChanged(int radioBand) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onRadioBandChanged: " + radioBand); + } + + if (mRadioManager == null) { + return; + } + + try { + RadioStation station = new RadioStation(mCurrentChannelNumber, + 0 /* subChannelNumber */, mCurrentBand, null /* rds */); + boolean isPlaying = !mRadioManager.isMuted(); + + postCard(mConverter.convert(station, isPlaying)); + } catch (RemoteException e) { + Log.e(TAG, "Post radio station changed error: " + e.getMessage()); + } + } + + @Override + public void onRadioMuteChanged(boolean isMuted) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onRadioMuteChanged(): " + isMuted); + } + + RadioStation station = new RadioStation(mCurrentChannelNumber, + 0 /* subChannelNumber */, mCurrentBand, null /* rds */); + + postCard(mConverter.convert(station, !isMuted)); + } + + @Override + public void onError(int status) { + Log.e(TAG, "Radio error: " + status); + } + }; + + private ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + mConnectionRetryCount = 0; + + mRadioManager = IRadioManager.Stub.asInterface(binder); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onSeviceConnected(): " + mRadioManager); + } + + try { + mRadioManager.addRadioTunerCallback(mCallback); + + if (mRadioManager.isInitialized() && mRadioManager.hasFocus()) { + boolean isPlaying = !mRadioManager.isMuted(); + postCard(mConverter.convert(mRadioManager.getCurrentRadioStation(), isPlaying)); + } + } catch (RemoteException e) { + Log.e(TAG, "addRadioTunerCallback() error: " + e.getMessage()); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onServiceDisconnected(): " + name); + } + mRadioManager = null; + + // If the service has been disconnected, attempt to reconnect. + mHandler.removeCallbacks(mServiceConnectionRetry); + mHandler.postDelayed(mServiceConnectionRetry, SERVICE_CONNECTION_RETRY_DELAY_MS); + } + }; + + /** + * A {@link Runnable} that is responsible for attempting to reconnect to {@link IRadioManager}. + */ + private Runnable mServiceConnectionRetry = new Runnable() { + @Override + public void run() { + if (mRadioManager != null) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "RadioService rebound by framework, no need to bind again"); + } + return; + } + + mConnectionRetryCount++; + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Rebinding disconnected RadioService, retry count: " + + mConnectionRetryCount); + } + + if (!bindRadioService()) { + mHandler.postDelayed(mServiceConnectionRetry, + mConnectionRetryCount * SERVICE_CONNECTION_RETRY_DELAY_MS); + } + } + }; +} |