• 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 package com.android.car.radio.demo;
17 
18 import android.car.hardware.radio.CarRadioManager;
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.hardware.radio.RadioManager;
22 import android.media.AudioAttributes;
23 import android.media.AudioManager;
24 import android.os.RemoteException;
25 import android.os.SystemProperties;
26 import android.support.car.Car;
27 import android.support.car.CarNotConnectedException;
28 import android.support.car.CarConnectionCallback;
29 import android.support.car.media.CarAudioManager;
30 import android.util.Log;
31 import com.android.car.radio.service.IRadioCallback;
32 import com.android.car.radio.service.IRadioManager;
33 import com.android.car.radio.service.RadioStation;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /**
39  * A demo {@link IRadiomanager} that has a fixed set of AM and FM stations.
40  */
41 public class RadioDemo implements AudioManager.OnAudioFocusChangeListener {
42     private static final String TAG = "RadioDemo";
43 
44     /**
45      * The property name to enable demo mode.
46      */
47     public static final String DEMO_MODE_PROPERTY = "com.android.car.radio.demo";
48 
49     /**
50      * The property name to enable the radio in demo mode with dual tuners.
51      */
52     public static final String DUAL_DEMO_MODE_PROPERTY = "com.android.car.radio.demo.dual";
53 
54     private static RadioDemo sInstance;
55     private List<IRadioCallback> mCallbacks = new ArrayList<>();
56 
57     private List<RadioStation> mCurrentStations = new ArrayList<>();
58     private int mCurrentRadioBand = RadioManager.BAND_FM;
59 
60     private Car mCarApi;
61     private CarAudioManager mCarAudioManager;
62     private AudioAttributes mRadioAudioAttributes;
63 
64     private boolean mHasAudioFocus;
65 
66     private int mCurrentIndex;
67     private boolean mIsMuted;
68 
RadioDemo(Context context)69     private RadioDemo(Context context) {
70         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
71             mCarApi = Car.createCar(context, mCarConnectionCallback);
72             mCarApi.connect();
73         }
74     }
75 
76     /**
77      * Returns a mock {@link IRadioManager} to use for demo purposes. The returned class will have
78      * a fixed list of AM and FM changegs and support all the IRadioManager's functionality.
79      */
createDemoManager()80     public IRadioManager.Stub createDemoManager() {
81         return new IRadioManager.Stub() {
82             @Override
83             public void tune(RadioStation station) throws RemoteException {
84                 if (station == null || !requestAudioFocus()) {
85                     return;
86                 }
87 
88                 if (station.getRadioBand() != mCurrentRadioBand) {
89                     switchRadioBand(station.getRadioBand());
90                 }
91 
92                 boolean found = false;
93 
94                 for (int i = 0, size = mCurrentStations.size(); i < size; i++) {
95                     RadioStation storedStation = mCurrentStations.get(i);
96 
97                     if (storedStation.equals(station)) {
98                         found = true;
99                         mCurrentIndex = i;
100                         break;
101                     }
102                 }
103 
104                 // If not found, then insert it into the list, sorted by the channel.
105                 if (!found) {
106                     int indexToInsert = 0;
107 
108                     for (int i = 0, size = mCurrentStations.size(); i < size; i++) {
109                         RadioStation storedStation = mCurrentStations.get(i);
110 
111                         if (station.getChannelNumber() >= storedStation.getChannelNumber()) {
112                             indexToInsert = i + 1;
113                             break;
114                         }
115                     }
116 
117                     RadioStation stationToInsert = new RadioStation(station.getChannelNumber(),
118                             0 /* subChannel */, station.getRadioBand(), null /* rds */);
119                     mCurrentStations.add(indexToInsert, stationToInsert);
120 
121                     mCurrentIndex = indexToInsert;
122                 }
123 
124                 notifyCallbacks(station);
125             }
126 
127             @Override
128             public void seekForward() throws RemoteException {
129                 if (!requestAudioFocus()) {
130                     return;
131                 }
132 
133                 if (++mCurrentIndex >= mCurrentStations.size()) {
134                     mCurrentIndex = 0;
135                 }
136 
137                 notifyCallbacks(mCurrentStations.get(mCurrentIndex));
138             }
139 
140             @Override
141             public void seekBackward() throws RemoteException {
142                 if (!requestAudioFocus()) {
143                     return;
144                 }
145 
146                 if (--mCurrentIndex < 0){
147                     mCurrentIndex = mCurrentStations.size() - 1;
148                 }
149 
150                 notifyCallbacks(mCurrentStations.get(mCurrentIndex));
151             }
152 
153             @Override
154             public boolean mute() throws RemoteException {
155                 mIsMuted = true;
156                 notifyCallbacksMuteChanged(mIsMuted);
157                 return mIsMuted;
158             }
159 
160             @Override
161             public boolean unMute() throws RemoteException {
162                 requestAudioFocus();
163 
164                 if (mHasAudioFocus) {
165                     mIsMuted = false;
166                 }
167 
168                 notifyCallbacksMuteChanged(mIsMuted);
169                 return !mIsMuted;
170             }
171 
172             @Override
173             public boolean isMuted() throws RemoteException {
174                 return mIsMuted;
175             }
176 
177             @Override
178             public int openRadioBand(int radioBand) throws RemoteException {
179                 if (!requestAudioFocus()) {
180                     return RadioManager.STATUS_ERROR;
181                 }
182 
183                 switchRadioBand(radioBand);
184                 notifyCallbacks(radioBand);
185                 return RadioManager.STATUS_OK;
186             }
187 
188             @Override
189             public void addRadioTunerCallback(IRadioCallback callback) throws RemoteException {
190                 mCallbacks.add(callback);
191             }
192 
193             @Override
194             public void removeRadioTunerCallback(IRadioCallback callback) throws RemoteException {
195                 mCallbacks.remove(callback);
196             }
197 
198             @Override
199             public RadioStation getCurrentRadioStation() throws RemoteException {
200                 return mCurrentStations.get(mCurrentIndex);
201             }
202 
203             @Override
204             public boolean isInitialized() throws RemoteException {
205                 return true;
206             }
207 
208             @Override
209             public boolean hasFocus() {
210                 return mHasAudioFocus;
211             }
212 
213             @Override
214             public boolean hasDualTuners() throws RemoteException {
215                 return SystemProperties.getBoolean(RadioDemo.DUAL_DEMO_MODE_PROPERTY, false);
216             }
217         };
218     }
219 
220     @Override
221     public void onAudioFocusChange(int focusChange) {
222         if (Log.isLoggable(TAG, Log.DEBUG)) {
223             Log.d(TAG, "focus change: " + focusChange);
224         }
225 
226         switch (focusChange) {
227             case AudioManager.AUDIOFOCUS_GAIN:
228                 mHasAudioFocus = true;
229                 break;
230 
231             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
232             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
233                 mHasAudioFocus = false;
234                 break;
235 
236             case AudioManager.AUDIOFOCUS_LOSS:
237                 abandonAudioFocus();
238                 break;
239 
240             default:
241                 // Do nothing for all other cases.
242         }
243     }
244 
245     /**
246      * Requests audio focus for the current application.
247      *
248      * @return {@code true} if the request succeeded.
249      */
250     private boolean requestAudioFocus() {
251         if (mCarAudioManager == null) {
252             return false;
253         }
254 
255         int status = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
256         try {
257             status = mCarAudioManager.requestAudioFocus(this, mRadioAudioAttributes,
258                     AudioManager.AUDIOFOCUS_GAIN, 0);
259         } catch (CarNotConnectedException e) {
260             Log.e(TAG, "requestAudioFocus() failed", e);
261         }
262 
263         if (Log.isLoggable(TAG, Log.DEBUG)) {
264             Log.d(TAG, "requestAudioFocus status: " + status);
265         }
266 
267         if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
268             mHasAudioFocus = true;
269         }
270 
271         return status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
272     }
273 
274     /**
275      * Abandons audio focus for the current application.
276      *
277      * @return {@code true} if the request succeeded.
278      */
279     private void abandonAudioFocus() {
280         if (Log.isLoggable(TAG, Log.DEBUG)) {
281             Log.d(TAG, "abandonAudioFocus()");
282         }
283 
284         if (mCarAudioManager == null) {
285             return;
286         }
287 
288         mCarAudioManager.abandonAudioFocus(this, mRadioAudioAttributes);
289     }
290 
291     /**
292      * {@link CarConnectionCallback} that retrieves the {@link CarRadioManager}.
293      */
294     private final CarConnectionCallback mCarConnectionCallback =
295             new CarConnectionCallback() {
296                 @Override
297                 public void onConnected(Car car) {
298                     if (Log.isLoggable(TAG, Log.DEBUG)) {
299                         Log.d(TAG, "Car service connected.");
300                     }
301                     try {
302                         // The CarAudioManager only needs to be retrieved once.
303                         if (mCarAudioManager == null) {
304                             mCarAudioManager = (CarAudioManager) mCarApi.getCarManager(
305                                     android.car.Car.AUDIO_SERVICE);
306 
307                             mRadioAudioAttributes = mCarAudioManager.getAudioAttributesForCarUsage(
308                                     CarAudioManager.CAR_AUDIO_USAGE_RADIO);
309                         }
310                     } catch (CarNotConnectedException e) {
311                         //TODO finish
312                         Log.e(TAG, "Car not connected");
313                     }
314                 }
315 
316                 @Override
317                 public void onDisconnected(Car car) {
318                     if (Log.isLoggable(TAG, Log.DEBUG)) {
319                         Log.d(TAG, "Car service disconnected.");
320                     }
321                 }
322             };
323 
324     /**
325      * Switches to the corresponding radio band. This will update the list of current stations
326      * as well as notify any callbacks.
327      */
328     private void switchRadioBand(int radioBand) {
329         switch (radioBand) {
330             case RadioManager.BAND_AM:
331                 mCurrentStations = DemoRadioStations.getAmStations();
332                 break;
333             case RadioManager.BAND_FM:
334                 mCurrentStations = DemoRadioStations.getFmStations();
335                 break;
336             default:
337                 mCurrentStations = new ArrayList<>();
338         }
339 
340         mCurrentRadioBand = radioBand;
341         mCurrentIndex = 0;
342 
343         notifyCallbacks(mCurrentRadioBand);
344         notifyCallbacks(mCurrentStations.get(mCurrentIndex));
345     }
346 
347     /**
348      * Notifies any {@link IRadioCallback} that the mute state of the radio has changed.
349      */
350     private void notifyCallbacksMuteChanged(boolean isMuted) {
351         for (IRadioCallback callback : mCallbacks) {
352             try {
353                 callback.onRadioMuteChanged(isMuted);
354             } catch (RemoteException e) {
355                 // Ignore.
356             }
357         }
358     }
359 
360     /**
361      * Notifies any {@link IRadioCallback}s that the radio band has changed.
362      */
363     private void notifyCallbacks(int radioBand) {
364         for (IRadioCallback callback : mCallbacks) {
365             try {
366                 callback.onRadioBandChanged(radioBand);
367             } catch (RemoteException e) {
368                 // Ignore.
369             }
370         }
371     }
372 
373     /**
374      * Notifies any {@link IRadioCallback}s that the radio station has been changed to the given
375      * {@link RadioStation}.
376      */
377     private void notifyCallbacks(RadioStation station) {
378         for (IRadioCallback callback : mCallbacks) {
379             try {
380                 callback.onRadioStationChanged(station);
381             } catch (RemoteException e) {
382                 // Ignore.
383             }
384         }
385     }
386 
387     public static RadioDemo getInstance(Context context) {
388         if (sInstance == null) {
389             sInstance = new RadioDemo(context);
390         }
391 
392         return sInstance;
393     }
394 }
395