144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer/*
244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * Copyright (c) 2016, The Android Open Source Project
344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer *
444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * Licensed under the Apache License, Version 2.0 (the "License");
544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * you may not use this file except in compliance with the License.
644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * You may obtain a copy of the License at
744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer *
844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer *     http://www.apache.org/licenses/LICENSE-2.0
944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer *
1044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * Unless required by applicable law or agreed to in writing, software
1144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * distributed under the License is distributed on an "AS IS" BASIS,
1244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * See the License for the specific language governing permissions and
1444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * limitations under the License.
1544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer */
1644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerpackage com.android.car.radio.demo;
1744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
1844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.car.hardware.radio.CarRadioManager;
1944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.content.Context;
2044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.content.pm.PackageManager;
2144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioManager;
2244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.media.AudioAttributes;
2344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.media.AudioManager;
2444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.RemoteException;
2544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.SystemProperties;
2644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.support.car.Car;
2744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.support.car.CarNotConnectedException;
289dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrupimport android.support.car.CarConnectionCallback;
2944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.support.car.media.CarAudioManager;
3044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.util.Log;
3144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.IRadioCallback;
3244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.IRadioManager;
3344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.RadioStation;
3444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.ArrayList;
3644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.List;
3744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer/**
3944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * A demo {@link IRadiomanager} that has a fixed set of AM and FM stations.
4044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer */
4144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerpublic class RadioDemo implements AudioManager.OnAudioFocusChangeListener {
4244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private static final String TAG = "RadioDemo";
4344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
4444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
4544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * The property name to enable demo mode.
4644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
4744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public static final String DEMO_MODE_PROPERTY = "com.android.car.radio.demo";
4844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
4944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
5044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * The property name to enable the radio in demo mode with dual tuners.
5144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
5244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public static final String DUAL_DEMO_MODE_PROPERTY = "com.android.car.radio.demo.dual";
5344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
5444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private static RadioDemo sInstance;
5544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private List<IRadioCallback> mCallbacks = new ArrayList<>();
5644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
5744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private List<RadioStation> mCurrentStations = new ArrayList<>();
5844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int mCurrentRadioBand = RadioManager.BAND_FM;
5944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
6044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private Car mCarApi;
6144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private CarAudioManager mCarAudioManager;
6244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private AudioAttributes mRadioAudioAttributes;
6344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
6444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mHasAudioFocus;
6544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
6644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int mCurrentIndex;
6744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mIsMuted;
6844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
6944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioDemo(Context context) {
7044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
719dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup            mCarApi = Car.createCar(context, mCarConnectionCallback);
7244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mCarApi.connect();
7344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
7444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
7544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
7744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Returns a mock {@link IRadioManager} to use for demo purposes. The returned class will have
7844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * a fixed list of AM and FM changegs and support all the IRadioManager's functionality.
7944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
8044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public IRadioManager.Stub createDemoManager() {
8144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return new IRadioManager.Stub() {
8244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
8344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public void tune(RadioStation station) throws RemoteException {
8444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (station == null || !requestAudioFocus()) {
8544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
8644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
8744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
8844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (station.getRadioBand() != mCurrentRadioBand) {
8944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    switchRadioBand(station.getRadioBand());
9044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
9144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
9244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                boolean found = false;
9344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
9444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (int i = 0, size = mCurrentStations.size(); i < size; i++) {
9544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    RadioStation storedStation = mCurrentStations.get(i);
9644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
9744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    if (storedStation.equals(station)) {
9844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        found = true;
9944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        mCurrentIndex = i;
10044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        break;
10144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
10244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
10344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
10444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // If not found, then insert it into the list, sorted by the channel.
10544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (!found) {
10644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    int indexToInsert = 0;
10744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
10844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    for (int i = 0, size = mCurrentStations.size(); i < size; i++) {
10944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        RadioStation storedStation = mCurrentStations.get(i);
11044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        if (station.getChannelNumber() >= storedStation.getChannelNumber()) {
11244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            indexToInsert = i + 1;
11344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            break;
11444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        }
11544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
11644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    RadioStation stationToInsert = new RadioStation(station.getChannelNumber(),
11844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            0 /* subChannel */, station.getRadioBand(), null /* rds */);
11944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    mCurrentStations.add(indexToInsert, stationToInsert);
12044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    mCurrentIndex = indexToInsert;
12244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
12344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                notifyCallbacks(station);
12544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
12644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
12844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public void seekForward() throws RemoteException {
12944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (!requestAudioFocus()) {
13044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
13144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
13244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
13344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (++mCurrentIndex >= mCurrentStations.size()) {
13444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    mCurrentIndex = 0;
13544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
13644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
13744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                notifyCallbacks(mCurrentStations.get(mCurrentIndex));
13844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
13944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
14044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
14144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public void seekBackward() throws RemoteException {
14244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (!requestAudioFocus()) {
14344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
14444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
14544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
14644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (--mCurrentIndex < 0){
14744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    mCurrentIndex = mCurrentStations.size() - 1;
14844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
14944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                notifyCallbacks(mCurrentStations.get(mCurrentIndex));
15144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
15244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
15444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public boolean mute() throws RemoteException {
15544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mIsMuted = true;
15644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                notifyCallbacksMuteChanged(mIsMuted);
15744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return mIsMuted;
15844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
15944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
16144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public boolean unMute() throws RemoteException {
16244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                requestAudioFocus();
16344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (mHasAudioFocus) {
16544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    mIsMuted = false;
16644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
16744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                notifyCallbacksMuteChanged(mIsMuted);
16944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return !mIsMuted;
17044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
17144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
17244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
17344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public boolean isMuted() throws RemoteException {
17444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return mIsMuted;
17544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
17644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
17744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
17844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public int openRadioBand(int radioBand) throws RemoteException {
17944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (!requestAudioFocus()) {
18044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return RadioManager.STATUS_ERROR;
18144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
18244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
18344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                switchRadioBand(radioBand);
18444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                notifyCallbacks(radioBand);
18544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return RadioManager.STATUS_OK;
18644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
18744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
18844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
18944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public void addRadioTunerCallback(IRadioCallback callback) throws RemoteException {
19044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCallbacks.add(callback);
19144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
19244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
19344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
19444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public void removeRadioTunerCallback(IRadioCallback callback) throws RemoteException {
19544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCallbacks.remove(callback);
19644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
19744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
19844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
19944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public RadioStation getCurrentRadioStation() throws RemoteException {
20044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return mCurrentStations.get(mCurrentIndex);
20144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
20244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
20344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
20444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public boolean isInitialized() throws RemoteException {
20544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return true;
20644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
20744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
20844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
20944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public boolean hasFocus() {
21044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return mHasAudioFocus;
21144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
21244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
21344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            @Override
21444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            public boolean hasDualTuners() throws RemoteException {
21544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return SystemProperties.getBoolean(RadioDemo.DUAL_DEMO_MODE_PROPERTY, false);
21644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
21744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        };
21844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
21944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
22044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
22144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onAudioFocusChange(int focusChange) {
22244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
22344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "focus change: " + focusChange);
22444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
22544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
22644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        switch (focusChange) {
22744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_GAIN:
22844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHasAudioFocus = true;
22944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
23044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
23144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
23244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
23344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHasAudioFocus = false;
23444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
23544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
23644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS:
23744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                abandonAudioFocus();
23844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
23944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
24044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            default:
24144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Do nothing for all other cases.
24244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
24344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
24444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
24544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
24644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Requests audio focus for the current application.
24744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *
24844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @return {@code true} if the request succeeded.
24944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
25044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean requestAudioFocus() {
25144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mCarAudioManager == null) {
25244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return false;
25344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
25444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
25544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        int status = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
25644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        try {
25744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            status = mCarAudioManager.requestAudioFocus(this, mRadioAudioAttributes,
25844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    AudioManager.AUDIOFOCUS_GAIN, 0);
25944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        } catch (CarNotConnectedException e) {
26044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "requestAudioFocus() failed", e);
26144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
26244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
26344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
26444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "requestAudioFocus status: " + status);
26544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
26644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
26744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
26844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mHasAudioFocus = true;
26944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
27044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
27244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
27344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
27544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Abandons audio focus for the current application.
27644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *
27744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @return {@code true} if the request succeeded.
27844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
27944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void abandonAudioFocus() {
28044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
28144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "abandonAudioFocus()");
28244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
28344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
28444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mCarAudioManager == null) {
28544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return;
28644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
28744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
28844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mCarAudioManager.abandonAudioFocus(this, mRadioAudioAttributes);
28944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
29044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
29144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
2929dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup     * {@link CarConnectionCallback} that retrieves the {@link CarRadioManager}.
29344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
2949dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup    private final CarConnectionCallback mCarConnectionCallback =
2959dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup            new CarConnectionCallback() {
29644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                @Override
2979dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup                public void onConnected(Car car) {
29844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    if (Log.isLoggable(TAG, Log.DEBUG)) {
29944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        Log.d(TAG, "Car service connected.");
30044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
30144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    try {
30244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        // The CarAudioManager only needs to be retrieved once.
30344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        if (mCarAudioManager == null) {
30444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            mCarAudioManager = (CarAudioManager) mCarApi.getCarManager(
30544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                                    android.car.Car.AUDIO_SERVICE);
30644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
30744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            mRadioAudioAttributes = mCarAudioManager.getAudioAttributesForCarUsage(
30844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                                    CarAudioManager.CAR_AUDIO_USAGE_RADIO);
30944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        }
31044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    } catch (CarNotConnectedException e) {
31144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        //TODO finish
31244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        Log.e(TAG, "Car not connected");
31344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
31444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
31544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
31644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                @Override
3179dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup                public void onDisconnected(Car car) {
31844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    if (Log.isLoggable(TAG, Log.DEBUG)) {
31944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        Log.d(TAG, "Car service disconnected.");
32044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
32144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
32244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            };
32344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
32444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
32544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Switches to the corresponding radio band. This will update the list of current stations
32644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * as well as notify any callbacks.
32744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
32844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void switchRadioBand(int radioBand) {
32944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        switch (radioBand) {
33044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case RadioManager.BAND_AM:
33144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentStations = DemoRadioStations.getAmStations();
33244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
33344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case RadioManager.BAND_FM:
33444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentStations = DemoRadioStations.getFmStations();
33544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
33644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            default:
33744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentStations = new ArrayList<>();
33844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
33944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
34044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mCurrentRadioBand = radioBand;
34144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mCurrentIndex = 0;
34244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
34344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        notifyCallbacks(mCurrentRadioBand);
34444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        notifyCallbacks(mCurrentStations.get(mCurrentIndex));
34544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
34644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
34744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
34844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Notifies any {@link IRadioCallback} that the mute state of the radio has changed.
34944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
35044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void notifyCallbacksMuteChanged(boolean isMuted) {
35144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        for (IRadioCallback callback : mCallbacks) {
35244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
35344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                callback.onRadioMuteChanged(isMuted);
35444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
35544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Ignore.
35644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
35744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
35844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
35944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
36044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
36144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Notifies any {@link IRadioCallback}s that the radio band has changed.
36244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
36344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void notifyCallbacks(int radioBand) {
36444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        for (IRadioCallback callback : mCallbacks) {
36544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
36644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                callback.onRadioBandChanged(radioBand);
36744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
36844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Ignore.
36944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
37044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
37144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
37244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
37344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
37444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Notifies any {@link IRadioCallback}s that the radio station has been changed to the given
37544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link RadioStation}.
37644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
37744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void notifyCallbacks(RadioStation station) {
37844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        for (IRadioCallback callback : mCallbacks) {
37944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
38044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                callback.onRadioStationChanged(station);
38144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
38244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Ignore.
38344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
38444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
38544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
38644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
38744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public static RadioDemo getInstance(Context context) {
38844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (sInstance == null) {
38944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            sInstance = new RadioDemo(context);
39044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
39144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
39244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return sInstance;
39344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
39444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer}
395