/* * 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); } } }; }