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 Iyer
1744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerpackage com.android.car.radio;
1844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
1944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.app.Service;
2044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.car.hardware.radio.CarRadioManager;
2144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.content.Context;
2244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.content.Intent;
2344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.content.pm.PackageManager;
2444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioManager;
2544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioMetadata;
2644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioTuner;
2744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.media.AudioAttributes;
2844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.media.AudioManager;
2944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.Handler;
3044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.IBinder;
3144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.RemoteException;
3244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.SystemProperties;
3344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.support.annotation.Nullable;
3444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.support.car.Car;
3544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.support.car.CarNotConnectedException;
369dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrupimport android.support.car.CarConnectionCallback;
3744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.support.car.media.CarAudioManager;
3844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.text.TextUtils;
3944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.util.Log;
4044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.demo.RadioDemo;
4144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.IRadioCallback;
4244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.IRadioManager;
4344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.RadioRds;
4444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.RadioStation;
4544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
4644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.ArrayList;
4744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.List;
4844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
4944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer/**
5044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * A persistent {@link Service} that is responsible for opening and closing a {@link RadioTuner}.
5144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * All radio operations should be delegated to this class. To be notified of any changes in radio
5244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * metadata, register as a {@link android.hardware.radio.RadioTuner.Callback} on this Service.
5344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer *
5444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * <p>Utilize the {@link RadioBinder} to perform radio operations.
5544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer */
5644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerpublic class RadioService extends Service implements AudioManager.OnAudioFocusChangeListener {
5744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private static String TAG = "Em.RadioService";
5844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
5944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
6044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * The amount of time to wait before re-trying to open the {@link #mRadioTuner}.
6144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
6244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private static final int RADIO_TUNER_REOPEN_DELAY_MS = 5000;
6344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
6444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int mReOpenRadioTunerCount = 0;
6544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private final Handler mHandler = new Handler();
6644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
6744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private Car mCarApi;
6844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioTuner mRadioTuner;
6944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mRadioSuccessfullyInitialized;
7144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int mCurrentRadioBand = RadioManager.BAND_FM;
7244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int mCurrentRadioChannel = RadioStorage.INVALID_RADIO_CHANNEL;
7344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private String mCurrentChannelInfo;
7544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private String mCurrentArtist;
7644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private String mCurrentSongTitle;
7744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioManager mRadioManager;
7944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioBackgroundScanner mBackgroundScanner;
8044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioManager.FmBandDescriptor mFmDescriptor;
8144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioManager.AmBandDescriptor mAmDescriptor;
8244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
8344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioManager.FmBandConfig mFmConfig;
8444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioManager.AmBandConfig mAmConfig;
8544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
8644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
8744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
8844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private CarAudioManager mCarAudioManager;
8944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private AudioAttributes mRadioAudioAttributes;
9044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
9144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
9244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Whether or not this {@link RadioService} currently has audio focus, meaning it is the
9344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * primary driver of media. Usually, interaction with the radio will be prefaced with an
9444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * explicit request for audio focus. However, this is not ideal when muting the radio, so this
9544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * state needs to be tracked.
9644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
9744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mHasAudioFocus;
9844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
9944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
10044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * An internal {@link android.hardware.radio.RadioTuner.Callback} that will listen for
10144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * changes in radio metadata and pass these method calls through to
10244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link #mRadioTunerCallbacks}.
10344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
10444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioTuner.Callback mInternalRadioTunerCallback = new InternalRadioCallback();
10544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private List<IRadioCallback> mRadioTunerCallbacks = new ArrayList<>();
10644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
10744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
10844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public IBinder onBind(Intent intent) {
10944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
11044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onBind(); Intent: " + intent);
11144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
11244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return mBinder;
11344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
11444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
11644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onCreate() {
11744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        super.onCreate();
11844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
12044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onCreate()");
12144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
12244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // Connection to car services does not work for non-automotive yet, so this call needs to
12444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // be guarded.
12544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
1269dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup            mCarApi = Car.createCar(this /* context */, mCarConnectionCallback);
12744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mCarApi.connect();
12844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
12944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
13044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (SystemProperties.getBoolean(RadioDemo.DEMO_MODE_PROPERTY, false)) {
13144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            initializeDemo();
13244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        } else {
13344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            initialze();
13444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
13544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
13644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
13744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
13844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Initializes this service to use a demo {@link IRadioManager}.
13944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *
14035dbc15d818fc64e8af86e40e38d4119fea1dd15Anthony Chen     * @see RadioDemo
14144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
14244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void initializeDemo() {
14344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
14444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "initializeDemo()");
14544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
14644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
14744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mBinder = RadioDemo.getInstance(this /* context */).createDemoManager();
14844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
14944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
15144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Connects to the {@link RadioManager}.
15244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
15344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void initialze() {
15444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mRadioManager = (RadioManager) getSystemService(Context.RADIO_SERVICE);
15544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
15744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "initialze(); mRadioManager: " + mRadioManager);
15844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
15944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mRadioManager == null) {
16144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.w(TAG, "RadioManager could not be loaded.");
16244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return;
16344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
16444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        int status = mRadioManager.listModules(mModules);
16644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (status != RadioManager.STATUS_OK) {
16744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.w(TAG, "Load modules failed with status: " + status);
16844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return;
16944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
17044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
17144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
17244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "initialze(); listModules complete: " + mModules);
17344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
17444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
17544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mModules.size() == 0) {
17644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.w(TAG, "No radio modules on device.");
17744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return;
17844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
17944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
18044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        boolean isDebugLoggable = Log.isLoggable(TAG, Log.DEBUG);
18144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
18244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // Load the possible radio bands. For now, just accept FM and AM bands.
18344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        for (RadioManager.BandDescriptor band : mModules.get(0).getBands()) {
18444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (isDebugLoggable) {
18544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "loading band: " + band.toString());
18644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
18744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
1888414397997221ece54d8e3a61ba770c17c6ca688Tomasz Wasilczyk            if (mFmDescriptor == null && band.isFmBand()) {
18944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mFmDescriptor = (RadioManager.FmBandDescriptor) band;
19044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
19144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
1928414397997221ece54d8e3a61ba770c17c6ca688Tomasz Wasilczyk            if (mAmDescriptor == null && band.isAmBand()) {
19344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mAmDescriptor = (RadioManager.AmBandDescriptor) band;
19444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
19544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
19644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
19744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mFmDescriptor == null && mAmDescriptor == null) {
19844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.w(TAG, "No AM and FM radio bands could be loaded.");
19944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return;
20044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
20144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
20244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // TODO: Make stereo configurable depending on device.
20344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mFmConfig = new RadioManager.FmBandConfig.Builder(mFmDescriptor)
20444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                .setStereo(true)
20544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                .build();
20644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mAmConfig = new RadioManager.AmBandConfig.Builder(mAmDescriptor)
20744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                .setStereo(true)
20844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                .build();
20944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
21044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // If there is a second tuner on the device, then set it up as the background scanner.
2118414397997221ece54d8e3a61ba770c17c6ca688Tomasz Wasilczyk        // TODO(b/63101896): we don't know if the second tuner is for the same medium, so we don't
2128414397997221ece54d8e3a61ba770c17c6ca688Tomasz Wasilczyk        // set background scanner for now.
21344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
21444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mRadioSuccessfullyInitialized = true;
21544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
21644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
21744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
21844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onDestroy() {
21944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
22044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onDestroy()");
22144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
22244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
22344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        close();
22444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
22544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mCarApi != null) {
22644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mCarApi.disconnect();
22744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
22844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
22944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        super.onDestroy();
23044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
23144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
23244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
23344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Opens the current radio band. Currently, this only supports FM and AM bands.
23444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *
23544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
23644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *                  {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
23744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @return {@link RadioManager#STATUS_OK} if successful; otherwise,
23844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link RadioManager#STATUS_ERROR}.
23944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
24044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int openRadioBandInternal(int radioBand) {
24144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
24244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "openRadioBandInternal() audio focus request fail");
24344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return RadioManager.STATUS_ERROR;
24444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
24544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
24644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mCurrentRadioBand = radioBand;
24744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        RadioManager.BandConfig config = getRadioConfig(radioBand);
24844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
24944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (config == null) {
25044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.w(TAG, "Cannot create config for radio band: " + radioBand);
25144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return RadioManager.STATUS_ERROR;
25244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
25344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
25444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mRadioTuner != null) {
25544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.setConfiguration(config);
25644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        } else {
25744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner = mRadioManager.openTuner(mModules.get(0).getId(), config, true,
25844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    mInternalRadioTunerCallback, null /* handler */);
25944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
26044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
26144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
26244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "openRadioBandInternal() STATUS_OK");
26344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
26444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
26544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mBackgroundScanner != null) {
26644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mBackgroundScanner.onRadioBandChanged(radioBand);
26744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
26844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
26944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // Reset the counter for exponential backoff each time the radio tuner has been successfully
27044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // opened.
27144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mReOpenRadioTunerCount = 0;
27244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return RadioManager.STATUS_OK;
27444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
27544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
27744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Returns a {@link RadioRds} object that holds all the current radio metadata. If all the
27844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * metadata is empty, then {@code null} is returned.
27944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
28044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Nullable
28144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioRds createCurrentRadioRds() {
28244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (TextUtils.isEmpty(mCurrentChannelInfo) && TextUtils.isEmpty(mCurrentArtist)
28344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                && TextUtils.isEmpty(mCurrentSongTitle)) {
28444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return null;
28544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
28644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
28744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return new RadioRds(mCurrentChannelInfo, mCurrentArtist, mCurrentSongTitle);
28844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
28944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
29044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
29144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Creates a {@link RadioStation} that encapsulates all the information about the current
29244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * radio station.
29344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
29444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioStation createCurrentRadioStation() {
29544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // mCurrentRadioChannel can possibly be invalid if this class never receives a callback
29644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // for onProgramInfoChanged(). As a result, manually retrieve the information for the
29744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // current station from RadioTuner if this is the case.
29844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mCurrentRadioChannel == RadioStorage.INVALID_RADIO_CHANNEL && mRadioTuner != null) {
29944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
30044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "createCurrentRadioStation(); invalid current radio channel. "
30144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        + "Calling getProgramInformation for valid station");
30244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
30344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
30444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // getProgramInformation() expects an array of size 1.
30544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            RadioManager.ProgramInfo[] info = new RadioManager.ProgramInfo[1];
30644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            int status = mRadioTuner.getProgramInformation(info);
30744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
30844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
30944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "getProgramInformation() status: " + status + "; info: " + info[0]);
31044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
31144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
31244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (status == RadioManager.STATUS_OK && info[0] != null) {
31344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentRadioChannel = info[0].getChannel();
31444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
31544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
31644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "program info channel: " + mCurrentRadioChannel);
31744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
31844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
31944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
32044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
32144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return new RadioStation(mCurrentRadioChannel, 0 /* subChannelNumber */,
32244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentRadioBand, createCurrentRadioRds());
32344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
32444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
32544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
32644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Returns the proper {@link android.hardware.radio.RadioManager.BandConfig} for the given
32744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * radio band. {@code null} is returned if the band is not suppored.
32844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
32944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Nullable
33044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioManager.BandConfig getRadioConfig(int selectedRadioBand) {
33144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        switch (selectedRadioBand) {
33244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case RadioManager.BAND_AM:
3338414397997221ece54d8e3a61ba770c17c6ca688Tomasz Wasilczyk            case RadioManager.BAND_AM_HD:
33444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return mAmConfig;
33544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case RadioManager.BAND_FM:
3368414397997221ece54d8e3a61ba770c17c6ca688Tomasz Wasilczyk            case RadioManager.BAND_FM_HD:
33744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return mFmConfig;
33844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
33944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            default:
34044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return null;
34144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
34244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
34344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
34444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int requestAudioFocus() {
34544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        int status = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
34644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        try {
34744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            status = mCarAudioManager.requestAudioFocus(this, mRadioAudioAttributes,
34844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    AudioManager.AUDIOFOCUS_GAIN, 0);
34944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        } catch (CarNotConnectedException e) {
35044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "requestAudioFocus() failed", e);
35144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
35244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
35344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
35444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "requestAudioFocus status: " + status);
35544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
35644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
35744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
35844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mHasAudioFocus = true;
35944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
36044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // Receiving audio focus means that the radio is un-muted.
36144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            for (IRadioCallback callback : mRadioTunerCallbacks) {
36244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                try {
36344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onRadioMuteChanged(false);
36444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                } catch (RemoteException e) {
36544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.e(TAG, "requestAudioFocus(); onRadioMuteChanged() notify failed: "
36644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            + e.getMessage());
36744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
36844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
36944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
37044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
37144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return status;
37244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
37344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
37444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void abandonAudioFocus() {
37544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
37644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "abandonAudioFocus()");
37744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
37844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
37944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mCarAudioManager == null) {
38044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return;
38144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
38244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
38344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mCarAudioManager.abandonAudioFocus(this, mRadioAudioAttributes);
38444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mHasAudioFocus = false;
38544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
38644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        for (IRadioCallback callback : mRadioTunerCallbacks) {
38744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
38844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                callback.onRadioMuteChanged(true);
38944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
39044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "abandonAudioFocus(); onRadioMutechanged() notify failed: "
39144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        + e.getMessage());
39244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
39344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
39444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
39544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
39644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
39744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Closes any active {@link RadioTuner}s and releases audio focus.
39844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
39944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void close() {
40044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
40144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "close()");
40244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
40344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
40444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        abandonAudioFocus();
40544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
40644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mRadioTuner != null) {
40744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.close();
40844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner = null;
40944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
41044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
41144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
41244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
41344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onAudioFocusChange(int focusChange) {
41444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
41544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "focus change: " + focusChange);
41644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
41744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
41844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        switch (focusChange) {
41944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_GAIN:
42044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHasAudioFocus = true;
42144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                openRadioBandInternal(mCurrentRadioBand);
42244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
42344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
42444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // For a transient loss, just allow the focus to be released. The radio will stop
42544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // itself automatically. There is no need for an explicit abandon audio focus call
42644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // because this removes the AudioFocusChangeListener.
42744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
42844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
42944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHasAudioFocus = false;
43044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
43144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
43244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS:
43344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                close();
43444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
43544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
43644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            default:
43744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Do nothing for all other cases.
43844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
43944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
44044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
44144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
4429dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup     * {@link CarConnectionCallback} that retrieves the {@link CarRadioManager}.
44344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
4449dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup    private final CarConnectionCallback mCarConnectionCallback =
4459dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup            new CarConnectionCallback() {
44644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                @Override
4479dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup                public void onConnected(Car car) {
44844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    if (Log.isLoggable(TAG, Log.DEBUG)) {
44944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        Log.d(TAG, "Car service connected.");
45044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
45144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    try {
45244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        // The CarAudioManager only needs to be retrieved once.
45344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        if (mCarAudioManager == null) {
45444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            mCarAudioManager = (CarAudioManager) mCarApi.getCarManager(
45544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                                    android.car.Car.AUDIO_SERVICE);
45644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
45744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            mRadioAudioAttributes = mCarAudioManager.getAudioAttributesForCarUsage(
45844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                                    CarAudioManager.CAR_AUDIO_USAGE_RADIO);
45944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        }
46044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    } catch (CarNotConnectedException e) {
46144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        //TODO finish
46244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        Log.e(TAG, "Car not connected");
46344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
46444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
46544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
46644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                @Override
4679dc138b7446e68228e233839d75bd2cfb5736807Jason Tholstrup                public void onDisconnected(Car car) {
46844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    if (Log.isLoggable(TAG, Log.DEBUG)) {
46944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        Log.d(TAG, "Car service disconnected.");
47044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
47144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
47244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            };
47344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
47444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private IRadioManager.Stub mBinder = new IRadioManager.Stub() {
47544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
47644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Tunes the radio to the given frequency. To be notified of a successful tune, register
47744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * as a {@link android.hardware.radio.RadioTuner.Callback}.
47844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
47944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
48044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void tune(RadioStation radioStation) {
48144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null || radioStation == null
48244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
48344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
48444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
48544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
48644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null || radioStation.getRadioBand() != mCurrentRadioBand) {
48744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                int radioStatus = openRadioBandInternal(radioStation.getRadioBand());
48844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
48944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
49044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
49144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
49244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
49344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            int status = mRadioTuner.tune(radioStation.getChannelNumber(), 0 /* subChannel */);
49444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
49544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
49644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "Tuning to station: " + radioStation + "\n\tstatus: " + status);
49744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
49844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
49944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
50044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
50144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Seeks the radio forward. To be notified of a successful tune, register as a
50244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link android.hardware.radio.RadioTuner.Callback}.
50344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
50444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
50544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void seekForward() {
50644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null
50744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
50844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
50944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
51044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
51144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
51244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                int radioStatus = openRadioBandInternal(mCurrentRadioBand);
51344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
51444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
51544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
51644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
51744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
51844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.scan(RadioTuner.DIRECTION_UP, true);
51944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
52044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
52144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
52244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Seeks the radio backwards. To be notified of a successful tune, register as a
52344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link android.hardware.radio.RadioTuner.Callback}.
52444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
52544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
52644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void seekBackward() {
52744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null
52844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
52944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
53044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
53144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
53244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
53344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                int radioStatus = openRadioBandInternal(mCurrentRadioBand);
53444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
53544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
53644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
53744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
53844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
53944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
54044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
54144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
54244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
54344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Mutes the radio.
54444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *
54544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @return {@code true} if the mute was successful.
54644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
54744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
54844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean mute() {
54944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null) {
55044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return false;
55144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
55244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
55344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mCarAudioManager == null) {
55444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
55544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "mute() called, but not connected to CarAudioManager");
55644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
55744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return false;
55844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
55944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
56044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If the radio does not currently have focus, then no need to do anything because the
56144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // radio won't be playing any sound.
56244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (!mHasAudioFocus) {
56344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
56444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "mute() called, but radio does not currently have audio focus; "
56544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            + "ignoring.");
56644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
56744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return false;
56844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
56944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
57044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            boolean muteSuccessful = false;
57144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
57244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
57344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                muteSuccessful = mCarAudioManager.setMediaMute(true);
57444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
57544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
57644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "setMediaMute(true) status: " + muteSuccessful);
57744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
57844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (CarNotConnectedException e) {
57944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "mute() failed: " + e.getMessage());
58044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                e.printStackTrace();
58144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
58244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
58344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (muteSuccessful && mRadioTunerCallbacks.size() > 0) {
58444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
58544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    try {
58644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        callback.onRadioMuteChanged(true);
58744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    } catch (RemoteException e) {
58844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        Log.e(TAG, "mute() notify failed: " + e.getMessage());
58944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    }
59044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
59144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
59244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
59344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return muteSuccessful;
59444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
59544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
59644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
59744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Un-mutes the radio and causes audio to play.
59844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *
59944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @return {@code true} if the un-mute was successful.
60044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
60144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
60244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean unMute() {
60344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null) {
60444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return false;
60544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
60644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
60744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mCarAudioManager == null) {
60844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
60944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "toggleMute() called, but not connected to CarAudioManager");
61044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
61144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return false;
61244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
61344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
61444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // Requesting audio focus will automatically un-mute the radio if it had been muted.
61544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return requestAudioFocus() == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
61644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
61744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
61844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
61944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio is currently muted.
62044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
62144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
62244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean isMuted() {
62344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (!mHasAudioFocus) {
62444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return true;
62544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
62644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
62744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null) {
62844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return true;
62944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
63044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
63144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mCarAudioManager == null) {
63244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
63344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "isMuted() called, but not connected to CarAudioManager");
63444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
63544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return true;
63644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
63744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
63844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            boolean isMuted = false;
63944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
64044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
64144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                isMuted = mCarAudioManager.isMediaMuted();
64244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (CarNotConnectedException e) {
64344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "isMuted() failed: " + e.getMessage());
64444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                e.printStackTrace();
64544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
64644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
64744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return isMuted;
64844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
64944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
65044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
65144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Opens the radio for the given band.
65244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *
65344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
65444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *                  {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
65544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @return {@link RadioManager#STATUS_OK} if successful; otherwise,
65644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link RadioManager#STATUS_ERROR}.
65744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
65844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
65944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public int openRadioBand(int radioBand) {
66044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
66144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "openRadioBand() for band: " + radioBand);
66244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
66344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
66444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null) {
66544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return RadioManager.STATUS_ERROR;
66644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
66744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
66844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return openRadioBandInternal(radioBand);
66944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
67044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
67144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
67244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Adds the given {@link android.hardware.radio.RadioTuner.Callback} to be notified
67344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * of any radio metadata changes.
67444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
67544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
67644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void addRadioTunerCallback(IRadioCallback callback) {
67744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (callback == null) {
67844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
67944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
68044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
68144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTunerCallbacks.add(callback);
68244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
68344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
68444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
68544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Removes the given {@link android.hardware.radio.RadioTuner.Callback} from receiving
68644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * any radio metadata chagnes.
68744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
68844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
68944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void removeRadioTunerCallback(IRadioCallback callback) {
69044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (callback == null) {
69144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
69244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
69344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
69444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTunerCallbacks.remove(callback);
69544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
69644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
69744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
69844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns a {@link RadioStation} that encapsulates the information about the current
69944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * station the radio is tuned to.
70044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
70144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
70244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public RadioStation getCurrentRadioStation() {
70344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return createCurrentRadioStation();
70444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
70544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
70644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
70744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio was able to successfully initialize. A value of
70844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@code false} here could mean that the {@code RadioService} was not able to connect to
70944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * the {@link RadioManager} or there were no radio modules on the current device.
71044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
71144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
71244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean isInitialized() {
71344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mRadioSuccessfullyInitialized;
71444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
71544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
71644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
71744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio currently has focus and is therefore the application
71844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * that is supplying music.
71944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
72044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
72144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean hasFocus() {
72244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mHasAudioFocus;
72344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
72444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
72544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
72644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the current radio module has dual tuners, meaning that a tuner
72744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * is available to scan for stations in the background.
72844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
72944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
73044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean hasDualTuners() {
73144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mModules.size() >= 2;
73244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
73344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    };
73444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
73544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
73644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * A extension of {@link android.hardware.radio.RadioTuner.Callback} that delegates to a
73744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * callback registered on this service.
73844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
73944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private class InternalRadioCallback extends RadioTuner.Callback {
74044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
74144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onProgramInfoChanged(RadioManager.ProgramInfo info) {
74244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
74344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "onProgramInfoChanged(); info: " + info);
74444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
74544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
74644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            clearMetadata();
74744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
74844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (info != null) {
74944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentRadioChannel = info.getChannel();
75044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
75144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
75244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "onProgramInfoChanged(); info channel: " + mCurrentRadioChannel);
75344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
75444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
75544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
75644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            RadioStation station = createCurrentRadioStation();
75744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
75844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
75944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
76044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onRadioStationChanged(station);
76144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
76244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
76344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onProgramInfoChanged(); "
76444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        + "Failed to notify IRadioCallbacks: " + e.getMessage());
76544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
76644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
76744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
76844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
76944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onMetadataChanged(RadioMetadata metadata) {
77044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
77144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "onMetadataChanged(); metadata: " + metadata);
77244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
77344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
77444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            clearMetadata();
77544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            updateMetadata(metadata);
77644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
77744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            RadioRds radioRds = createCurrentRadioRds();
77844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
77944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
78044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
78144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onRadioMetadataChanged(radioRds);
78244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
78344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
78444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onMetadataChanged(); "
78544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        + "Failed to notify IRadioCallbacks: " + e.getMessage());
78644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
78744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
78844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
78944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
79044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onConfigurationChanged(RadioManager.BandConfig config) {
79144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
79244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "onConfigurationChanged(): config: " + config);
79344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
79444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
79544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            clearMetadata();
79644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
79744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (config != null) {
79844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentRadioBand = config.getType();
79944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
80044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
80144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "onConfigurationChanged(): config type: " + mCurrentRadioBand);
80244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
80344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
80444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
80544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
80644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
80744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
80844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onRadioBandChanged(mCurrentRadioBand);
80944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
81044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
81144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onConfigurationChanged(); "
81244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        + "Failed to notify IRadioCallbacks: " + e.getMessage());
81344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
81444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
81544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
81644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
81744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onError(int status) {
81844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "onError(); status: " + status);
81944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
82044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If there is a hardware failure or the radio service died, then this requires a
82144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // re-opening of the radio tuner.
82244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (status == RadioTuner.ERROR_HARDWARE_FAILURE
82344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || status == RadioTuner.ERROR_SERVER_DIED) {
82444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (mRadioTuner != null) {
82544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    mRadioTuner.close();
82644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    mRadioTuner = null;
82744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
82844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
82944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Attempt to re-open the RadioTuner. Each time the radio tuner fails to open, the
83044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // mReOpenRadioTunerCount will be incremented.
83144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHandler.removeCallbacks(mOpenRadioTunerRunnable);
83244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHandler.postDelayed(mOpenRadioTunerRunnable,
83344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        mReOpenRadioTunerCount * RADIO_TUNER_REOPEN_DELAY_MS);
83444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
83544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mReOpenRadioTunerCount++;
83644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
83744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
83844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
83944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
84044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onError(status);
84144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
84244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
84344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onError(); Failed to notify IRadioCallbacks: " + e.getMessage());
84444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
84544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
84644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
84744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
84844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onControlChanged(boolean control) {
84944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If the radio loses control of the RadioTuner, then close it and allow it to be
85044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // re-opened when control has been gained.
85144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (!control) {
85244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                close();
85344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
85444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
85544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
85644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
85744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                openRadioBandInternal(mCurrentRadioBand);
85844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
85944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
86044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
86144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
86244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Sets all metadata fields to {@code null}.
86344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
86444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        private void clearMetadata() {
86544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mCurrentChannelInfo = null;
86644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mCurrentArtist = null;
86744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mCurrentSongTitle = null;
86844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
86944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
87044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
87144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Retrieves the relevant information off the given {@link RadioMetadata} object and
87244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * sets them correspondingly on {@link #mCurrentChannelInfo}, {@link #mCurrentArtist}
87344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * and {@link #mCurrentSongTitle}.
87444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
87544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        private void updateMetadata(RadioMetadata metadata) {
87644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (metadata != null) {
87744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentChannelInfo = metadata.getString(RadioMetadata.METADATA_KEY_RDS_PS);
87844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentArtist = metadata.getString(RadioMetadata.METADATA_KEY_ARTIST);
87944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentSongTitle = metadata.getString(RadioMetadata.METADATA_KEY_TITLE);
88044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
88144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
88244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, String.format("updateMetadata(): [channel info: %s, artist: %s, "
88344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            + "song title: %s]", mCurrentChannelInfo, mCurrentArtist,
88444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            mCurrentSongTitle));
88544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
88644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
88744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
88844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
88944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
89044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private final Runnable mOpenRadioTunerRunnable = () -> openRadioBandInternal(mCurrentRadioBand);
89144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer}
892