• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2016, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.stream.radio;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.ServiceConnection;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.util.Log;
29 import com.android.car.radio.service.IRadioCallback;
30 import com.android.car.radio.service.IRadioManager;
31 import com.android.car.radio.service.RadioRds;
32 import com.android.car.radio.service.RadioStation;
33 import com.android.car.stream.R;
34 import com.android.car.stream.StreamProducer;
35 
36 /**
37  * A {@link StreamProducer} that will connect to the {@link IRadioManager} and produce cards
38  * corresponding to the currently playing radio station.
39  */
40 public class RadioStreamProducer extends StreamProducer {
41     private static final String TAG = "RadioStreamProducer";
42 
43     /**
44      * The amount of time to wait before re-trying to connect to {@link IRadioManager}.
45      */
46     private static final int SERVICE_CONNECTION_RETRY_DELAY_MS = 5000;
47 
48     // Radio actions that are used by broadcasts that occur on interaction with the radio card.
49     static final int ACTION_SEEK_FORWARD = 1;
50     static final int ACTION_SEEK_BACKWARD = 2;
51     static final int ACTION_PAUSE = 3;
52     static final int ACTION_PLAY = 4;
53     static final int ACTION_STOP = 5;
54 
55     /**
56      * The action in an {@link Intent} that is meant to effect certain radio actions.
57      */
58     static final String RADIO_INTENT_ACTION =
59             "com.android.car.stream.radio.RADIO_INTENT_ACTION";
60 
61     /**
62      * The extra within the {@link Intent} that points to the specific action to be taken on the
63      * radio.
64      */
65     static final String RADIO_ACTION_EXTRA = "radio_action_extra";
66 
67     private final Handler mHandler = new Handler();
68 
69     private IRadioManager mRadioManager;
70     private RadioActionReceiver mReceiver;
71     private final RadioConverter mConverter;
72 
73     /**
74      * The number of times that this stream producer has attempted to reconnect to the
75      * {@link IRadioManager} after a failure to bind.
76      */
77     private int mConnectionRetryCount;
78 
79     private int mCurrentChannelNumber;
80     private int mCurrentBand;
81 
RadioStreamProducer(Context context)82     public RadioStreamProducer(Context context) {
83         super(context);
84         mConverter = new RadioConverter(context);
85     }
86 
87     @Override
start()88     public void start() {
89         super.start();
90 
91         mReceiver = new RadioActionReceiver();
92         mContext.registerReceiver(mReceiver, new IntentFilter(RADIO_INTENT_ACTION));
93 
94         bindRadioService();
95     }
96 
97     @Override
stop()98     public void stop() {
99         if (Log.isLoggable(TAG, Log.DEBUG)) {
100             Log.d(TAG, "stop()");
101         }
102 
103         mHandler.removeCallbacks(mServiceConnectionRetry);
104 
105         mContext.unregisterReceiver(mReceiver);
106         mReceiver = null;
107 
108         mContext.unbindService(mServiceConnection);
109         super.stop();
110     }
111 
112     /**
113      * Binds to the RadioService and returns {@code true} if the connection was successful.
114      */
bindRadioService()115     private boolean bindRadioService() {
116         Intent radioService = new Intent();
117         radioService.setComponent(new ComponentName(
118                 mContext.getString(R.string.car_radio_component_package),
119                 mContext.getString(R.string.car_radio_component_service)));
120 
121         boolean bound =
122                 !mContext.bindService(radioService, mServiceConnection, Context.BIND_AUTO_CREATE);
123 
124         if (Log.isLoggable(TAG, Log.DEBUG)) {
125             Log.d(TAG, "bindRadioService(). Connected to radio service: " + bound);
126         }
127 
128         return bound;
129     }
130 
131     /**
132      * A {@link BroadcastReceiver} that listens for Intents that have the action
133      * {@link #RADIO_INTENT_ACTION} and corresponding parses the action event within it to effect
134      * radio playback.
135      */
136     private class RadioActionReceiver extends BroadcastReceiver {
137         @Override
onReceive(Context context, Intent intent)138         public void onReceive(Context context, Intent intent) {
139             if (mRadioManager == null || !RADIO_INTENT_ACTION.equals(intent.getAction())) {
140                 return;
141             }
142 
143             int radioAction = intent.getIntExtra(RADIO_ACTION_EXTRA, -1);
144             if (radioAction == -1) {
145                 return;
146             }
147 
148             switch (radioAction) {
149                 case ACTION_SEEK_FORWARD:
150                     try {
151                         mRadioManager.seekForward();
152                     } catch (RemoteException e) {
153                         Log.e(TAG, "Seek forward exception: " + e.getMessage());
154                     }
155                     break;
156 
157                 case ACTION_SEEK_BACKWARD:
158                     try {
159                         mRadioManager.seekBackward();
160                     } catch (RemoteException e) {
161                         Log.e(TAG, "Seek backward exception: " + e.getMessage());
162                     }
163                     break;
164 
165                 case ACTION_PLAY:
166                     try {
167                         mRadioManager.unMute();
168                     } catch (RemoteException e) {
169                         Log.e(TAG, "Radio play exception: " + e.getMessage());
170                     }
171                     break;
172 
173                 case ACTION_STOP:
174                 case ACTION_PAUSE:
175                     try {
176                         mRadioManager.mute();
177                     } catch (RemoteException e) {
178                         Log.e(TAG, "Radio pause exception: " + e.getMessage());
179                     }
180                     break;
181 
182                 default:
183                     // Do nothing.
184             }
185         }
186     }
187 
188     /**
189      * A {@link IRadioCallback} that will be notified of various state changes in the radio station.
190      * Upon these changes, it will push a new {@link com.android.car.stream.StreamCard} to the
191      * Stream service.
192      */
193     private final IRadioCallback.Stub mCallback = new IRadioCallback.Stub() {
194         @Override
195         public void onRadioStationChanged(RadioStation station) {
196             if (Log.isLoggable(TAG, Log.DEBUG)) {
197                 Log.d(TAG, "onRadioStationChanged: " + station);
198             }
199 
200             mCurrentBand = station.getRadioBand();
201             mCurrentChannelNumber = station.getChannelNumber();
202 
203             if (mRadioManager == null) {
204                 return;
205             }
206 
207             try {
208                 boolean isPlaying = !mRadioManager.isMuted();
209                 postCard(mConverter.convert(station, isPlaying));
210             } catch (RemoteException e) {
211                 Log.e(TAG, "Post radio station changed error: " + e.getMessage());
212             }
213         }
214 
215         @Override
216         public void onRadioMetadataChanged(RadioRds rds) {
217             if (Log.isLoggable(TAG, Log.DEBUG)) {
218                 Log.d(TAG, "onRadioMetadataChanged: " + rds);
219             }
220 
221             // Ignore metadata changes because this will overwhelm the notifications. Instead,
222             // Only display the metadata that is retrieved in onRadioStationChanged().
223         }
224 
225         @Override
226         public void onRadioBandChanged(int radioBand) {
227             if (Log.isLoggable(TAG, Log.DEBUG)) {
228                 Log.d(TAG, "onRadioBandChanged: " + radioBand);
229             }
230 
231             if (mRadioManager == null) {
232                 return;
233             }
234 
235             try {
236                 RadioStation station = new RadioStation(mCurrentChannelNumber,
237                         0 /* subChannelNumber */, mCurrentBand, null /* rds */);
238                 boolean isPlaying = !mRadioManager.isMuted();
239 
240                 postCard(mConverter.convert(station, isPlaying));
241             } catch (RemoteException e) {
242                 Log.e(TAG, "Post radio station changed error: " + e.getMessage());
243             }
244         }
245 
246         @Override
247         public void onRadioMuteChanged(boolean isMuted) {
248             if (Log.isLoggable(TAG, Log.DEBUG)) {
249                 Log.d(TAG, "onRadioMuteChanged(): " + isMuted);
250             }
251 
252             RadioStation station = new RadioStation(mCurrentChannelNumber,
253                     0 /* subChannelNumber */, mCurrentBand, null /* rds */);
254 
255             postCard(mConverter.convert(station, !isMuted));
256         }
257 
258         @Override
259         public void onError(int status) {
260             Log.e(TAG, "Radio error: " + status);
261         }
262     };
263 
264     private ServiceConnection mServiceConnection = new ServiceConnection() {
265         @Override
266         public void onServiceConnected(ComponentName name, IBinder binder) {
267             mConnectionRetryCount = 0;
268 
269             mRadioManager = IRadioManager.Stub.asInterface(binder);
270 
271             if (Log.isLoggable(TAG, Log.DEBUG)) {
272                 Log.d(TAG, "onSeviceConnected(): " + mRadioManager);
273             }
274 
275             try {
276                 mRadioManager.addRadioTunerCallback(mCallback);
277 
278                 if (mRadioManager.isInitialized() && mRadioManager.hasFocus()) {
279                     boolean isPlaying = !mRadioManager.isMuted();
280                     postCard(mConverter.convert(mRadioManager.getCurrentRadioStation(), isPlaying));
281                 }
282             } catch (RemoteException e) {
283                 Log.e(TAG, "addRadioTunerCallback() error: " + e.getMessage());
284             }
285         }
286 
287         @Override
288         public void onServiceDisconnected(ComponentName name) {
289             if (Log.isLoggable(TAG, Log.DEBUG)) {
290                 Log.d(TAG, "onServiceDisconnected(): " + name);
291             }
292             mRadioManager = null;
293 
294             // If the service has been disconnected, attempt to reconnect.
295             mHandler.removeCallbacks(mServiceConnectionRetry);
296             mHandler.postDelayed(mServiceConnectionRetry, SERVICE_CONNECTION_RETRY_DELAY_MS);
297         }
298     };
299 
300     /**
301      * A {@link Runnable} that is responsible for attempting to reconnect to {@link IRadioManager}.
302      */
303     private Runnable mServiceConnectionRetry = new Runnable() {
304         @Override
305         public void run() {
306             if (mRadioManager != null) {
307                 if (Log.isLoggable(TAG, Log.DEBUG)) {
308                     Log.d(TAG, "RadioService rebound by framework, no need to bind again");
309                 }
310                 return;
311             }
312 
313             mConnectionRetryCount++;
314 
315             if (Log.isLoggable(TAG, Log.DEBUG)) {
316                 Log.d(TAG, "Rebinding disconnected RadioService, retry count: "
317                         + mConnectionRetryCount);
318             }
319 
320             if (!bindRadioService()) {
321                 mHandler.postDelayed(mServiceConnectionRetry,
322                         mConnectionRetryCount * SERVICE_CONNECTION_RETRY_DELAY_MS);
323             }
324         }
325     };
326 }
327