RadioService.java revision 1cc2cf31cdcdcd0996b58239e3c685fe92e80760
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.content.Context;
2144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.content.Intent;
22fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczykimport android.hardware.radio.ProgramList;
2333ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczykimport android.hardware.radio.ProgramSelector;
2444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioManager;
25dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczykimport android.hardware.radio.RadioManager.ProgramInfo;
2644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioTuner;
2744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.media.AudioAttributes;
2844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.media.AudioManager;
293423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.os.Bundle;
3044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.Handler;
3144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.IBinder;
3244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.RemoteException;
333423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.support.v4.media.MediaBrowserCompat.MediaItem;
343423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.support.v4.media.MediaBrowserServiceCompat;
354467ff0c4f0b80cc35be17f1af7c95c2a591bdd2Tomasz Wasilczykimport android.util.Log;
36f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang
373423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport com.android.car.radio.media.BrowseTree;
384467ff0c4f0b80cc35be17f1af7c95c2a591bdd2Tomasz Wasilczykimport com.android.car.radio.media.Program;
393423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport com.android.car.radio.media.TunerSession;
4089dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczykimport com.android.car.radio.platform.ImageMemoryCache;
41e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczykimport com.android.car.radio.platform.RadioManagerExt;
424467ff0c4f0b80cc35be17f1af7c95c2a591bdd2Tomasz Wasilczykimport com.android.car.radio.service.IRadioCallback;
434467ff0c4f0b80cc35be17f1af7c95c2a591bdd2Tomasz Wasilczykimport com.android.car.radio.service.IRadioManager;
449de3e263e20b844365b10bc9c342c136cd01ba0bTomasz Wasilczykimport com.android.car.radio.storage.RadioStorage;
4544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
4644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.ArrayList;
47b1c7c3ce9d0f647e2203f646928dd84edbb75c1cTomasz Wasilczykimport java.util.HashSet;
4844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.List;
4925ed47e7c2126213a3ed1ba33eb0b797b0f0cbb2Tomasz Wasilczykimport java.util.Objects;
5044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
5144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer/**
5244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * A persistent {@link Service} that is responsible for opening and closing a {@link RadioTuner}.
5344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * All radio operations should be delegated to this class. To be notified of any changes in radio
5444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * metadata, register as a {@link android.hardware.radio.RadioTuner.Callback} on this Service.
5544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer *
5644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * <p>Utilize the {@link RadioBinder} to perform radio operations.
5744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer */
583423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykpublic class RadioService extends MediaBrowserServiceCompat
593423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        implements AudioManager.OnAudioFocusChangeListener {
603423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
613423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private static String TAG = "BcRadioApp.uisrv";
623423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
633423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public static String ACTION_UI_SERVICE = "com.android.car.radio.ACTION_UI_SERVICE";
6444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
6544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
6644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * The amount of time to wait before re-trying to open the {@link #mRadioTuner}.
6744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
6844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private static final int RADIO_TUNER_REOPEN_DELAY_MS = 5000;
6944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7019c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private final Object mLock = new Object();
7119c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
7244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int mReOpenRadioTunerCount = 0;
7344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private final Handler mHandler = new Handler();
7444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7519c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private RadioStorage mRadioStorage;
7619c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private final RadioStorage.PresetsChangeListener mPresetsListener = this::onPresetsChanged;
7719c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
7844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioTuner mRadioTuner;
7944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
8044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mRadioSuccessfullyInitialized;
8144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
82dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk    private ProgramInfo mCurrentProgram;
83dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk    private int mCurrentRadioBand = RadioManager.BAND_FM;
8444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
85e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk    private RadioManagerExt mRadioManager;
8689dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk    private ImageMemoryCache mImageCache;
8744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
88f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang    private AudioManager mAudioManager;
8944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private AudioAttributes mRadioAudioAttributes;
9044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
913423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private BrowseTree mBrowseTree;
923423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private TunerSession mMediaSession;
93fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk    private ProgramList mProgramList;
943423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
9544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
9644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Whether or not this {@link RadioService} currently has audio focus, meaning it is the
9744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * primary driver of media. Usually, interaction with the radio will be prefaced with an
9844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * explicit request for audio focus. However, this is not ideal when muting the radio, so this
9944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * state needs to be tracked.
10044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
10144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mHasAudioFocus;
10244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
10344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
10444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * An internal {@link android.hardware.radio.RadioTuner.Callback} that will listen for
10544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * changes in radio metadata and pass these method calls through to
10644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link #mRadioTunerCallbacks}.
10744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
10844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioTuner.Callback mInternalRadioTunerCallback = new InternalRadioCallback();
10944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private List<IRadioCallback> mRadioTunerCallbacks = new ArrayList<>();
11044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
11244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public IBinder onBind(Intent intent) {
1133423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        if (ACTION_UI_SERVICE.equals(intent.getAction())) {
1143423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk            return mBinder;
11544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
1163423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        return super.onBind(intent);
11744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
11844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
12044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onCreate() {
12144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        super.onCreate();
12244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
12444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onCreate()");
12544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
12644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12719c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        mRadioStorage = RadioStorage.getInstance(this);
12819c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
129f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
130f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang        mRadioAudioAttributes = new AudioAttributes.Builder()
131f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang                .setUsage(AudioAttributes.USAGE_MEDIA)
132f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
133f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang                .build();
13444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
13589dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mRadioManager = new RadioManagerExt(this);
13689dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mImageCache = new ImageMemoryCache(mRadioManager, 1000);
137f3548bc1b56e17934a355a8c1847ac7efbc0d995Tomasz Wasilczyk        mBrowseTree = new BrowseTree(this, mImageCache);
13889dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mMediaSession = new TunerSession(this, mBrowseTree, mBinder, mImageCache);
1393423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        setSessionToken(mMediaSession.getSessionToken());
1403423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
141d768ae7c08f39a365a2be1bbe21e2248b41f60d0Tomasz Wasilczyk        mBrowseTree.setAmFmRegionConfig(mRadioManager.getAmFmRegionConfig());
142d768ae7c08f39a365a2be1bbe21e2248b41f60d0Tomasz Wasilczyk        openRadioBandInternal(mCurrentRadioBand);
14319c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
14419c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        mRadioStorage.addPresetsChangeListener(mPresetsListener);
14519c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        onPresetsChanged();
14644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
14744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mRadioSuccessfullyInitialized = true;
14844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
14944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
15144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onDestroy() {
15244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
15344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onDestroy()");
15444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
15544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15619c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        mRadioStorage.removePresetsChangeListener(mPresetsListener);
1573423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        mMediaSession.release();
15880706bfa3dbf8e5d93012cc001c3d95e7bd66746Tomasz Wasilczyk        mRadioManager.getRadioTunerExt().close();
15944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        close();
16044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        super.onDestroy();
16244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
16344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16419c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private void onPresetsChanged() {
16519c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        synchronized (mLock) {
16619c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk            mBrowseTree.setFavorites(new HashSet<>(mRadioStorage.getPresets()));
1676407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mMediaSession.notifyFavoritesChanged();
16819c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        }
16919c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    }
17019c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
17144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
17244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Opens the current radio band. Currently, this only supports FM and AM bands.
17344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *
17444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
17544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *                  {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
17644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @return {@link RadioManager#STATUS_OK} if successful; otherwise,
17744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link RadioManager#STATUS_ERROR}.
17844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
17944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int openRadioBandInternal(int radioBand) {
18044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
18144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "openRadioBandInternal() audio focus request fail");
18244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return RadioManager.STATUS_ERROR;
18344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
18444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
18544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mCurrentRadioBand = radioBand;
186e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk        if (mRadioTuner == null) {
187e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk            mRadioTuner = mRadioManager.openSession(mInternalRadioTunerCallback, null);
188fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList = mRadioTuner.getDynamicProgramList(null);
189fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mBrowseTree.setProgramList(mProgramList);
19044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
19144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
19244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
19344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "openRadioBandInternal() STATUS_OK");
19444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
19544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
19644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // Reset the counter for exponential backoff each time the radio tuner has been successfully
19744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // opened.
19844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mReOpenRadioTunerCount = 0;
19944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
20044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return RadioManager.STATUS_OK;
20144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
20244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
2031cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk    private void notifyMuteChanged(boolean muted) {
2041cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk        synchronized (mLock) {
2051cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk            mMediaSession.notifyMuteChanged(muted);
2061cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk            for (IRadioCallback callback : mRadioTunerCallbacks) {
2071cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                try {
2081cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                    callback.onRadioMuteChanged(muted);
2091cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                } catch (RemoteException e) {
2101cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                    Log.e(TAG, "Mute state change callback failed", e);
2111cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                }
2121cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk            }
2131cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk        }
2141cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk    }
2151cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk
21644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int requestAudioFocus() {
217f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang        int status = mAudioManager.requestAudioFocus(this, mRadioAudioAttributes,
21844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    AudioManager.AUDIOFOCUS_GAIN, 0);
21944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
22044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
22144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "requestAudioFocus status: " + status);
22244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
22344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
22444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
22544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mHasAudioFocus = true;
22644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
2276c62f1af9fcbf3e7ca00a701266a6185d9ed6d9fTomasz Wasilczyk            mRadioManager.getRadioTunerExt().setMuted(false);
2281cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk            notifyMuteChanged(false);
22944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
23044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
23144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return status;
23244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
23344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
23444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void abandonAudioFocus() {
23544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
23644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "abandonAudioFocus()");
23744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
23844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
239f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang        mAudioManager.abandonAudioFocus(this, mRadioAudioAttributes);
24044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mHasAudioFocus = false;
2411cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk        notifyMuteChanged(true);
24244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
24344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
24444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
24544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Closes any active {@link RadioTuner}s and releases audio focus.
24644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
24744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void close() {
24844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
24944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "close()");
25044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
25144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
25244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        abandonAudioFocus();
25344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
254fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk        if (mProgramList != null) {
255fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList.close();
256fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList = null;
257fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk        }
25844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mRadioTuner != null) {
25944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.close();
26044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner = null;
26144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
26244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
26344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
26444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
26544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onAudioFocusChange(int focusChange) {
26644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
26744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "focus change: " + focusChange);
26844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
26944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        switch (focusChange) {
27144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_GAIN:
27244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHasAudioFocus = true;
27344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                openRadioBandInternal(mCurrentRadioBand);
27444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
27544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // For a transient loss, just allow the focus to be released. The radio will stop
27744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // itself automatically. There is no need for an explicit abandon audio focus call
27844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // because this removes the AudioFocusChangeListener.
27944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
28044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
28144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHasAudioFocus = false;
28244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
28344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
28444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS:
28544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                close();
28644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
28744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
28844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            default:
28944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Do nothing for all other cases.
29044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
29144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
29244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
29344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private IRadioManager.Stub mBinder = new IRadioManager.Stub() {
29444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
29544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Tunes the radio to the given frequency. To be notified of a successful tune, register
29644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * as a {@link android.hardware.radio.RadioTuner.Callback}.
29744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
29844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
29933ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczyk        public void tune(ProgramSelector sel) {
30033ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczyk            if (mRadioManager == null || sel == null
30144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
30244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
30344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
30444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
30533ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczyk            mRadioTuner.tune(sel);
30644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
30744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
30844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
30944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Seeks the radio forward. To be notified of a successful tune, register as a
31044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link android.hardware.radio.RadioTuner.Callback}.
31144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
31244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
31344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void seekForward() {
31444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null
31544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
31644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
31744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
31844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
31944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
32044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                int radioStatus = openRadioBandInternal(mCurrentRadioBand);
32144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
32244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
32344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
32444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
32544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
32644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.scan(RadioTuner.DIRECTION_UP, true);
32744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
32844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
32944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
33044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Seeks the radio backwards. To be notified of a successful tune, register as a
33144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link android.hardware.radio.RadioTuner.Callback}.
33244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
33344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
33444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void seekBackward() {
33544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null
33644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
33744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
33844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
33944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
34044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
34144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                int radioStatus = openRadioBandInternal(mCurrentRadioBand);
34244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
34344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
34444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
34544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
34644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
34744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
34844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
34944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3506baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        private boolean setMuted(boolean mute) {
35180706bfa3dbf8e5d93012cc001c3d95e7bd66746Tomasz Wasilczyk            if (!mRadioManager.getRadioTunerExt().setMuted(mute)) return false;
3521cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk            notifyMuteChanged(mute);
3536baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk            return true;
3546baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        }
35544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3566baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        /**
3576baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         * Mutes the radio.
3586baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         *
3596baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         * @return {@code true} if the mute was successful.
3606baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         */
3616baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        @Override
3626baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        public boolean mute() {
3636baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk            return setMuted(true);
36444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
36544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
36644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
36744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Un-mutes the radio and causes audio to play.
36844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *
36944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @return {@code true} if the un-mute was successful.
37044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
37144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
37244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean unMute() {
3736baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk            if (!setMuted(false)) return false;
37444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
37544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return requestAudioFocus() == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
37644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
37744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
37844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
37944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio is currently muted.
38044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
38144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
38244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean isMuted() {
38380706bfa3dbf8e5d93012cc001c3d95e7bd66746Tomasz Wasilczyk            return mRadioManager.getRadioTunerExt().isMuted();
38444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
38544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3866407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        @Override
3876407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        public void addFavorite(Program program) {
3886407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mRadioStorage.storePreset(program);
3896407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        }
3906407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk
3916407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        @Override
3926407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        public void removeFavorite(ProgramSelector sel) {
3936407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mRadioStorage.removePreset(sel);
3946407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        }
3956407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk
39644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
39744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Opens the radio for the given band.
39844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *
39944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
40044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *                  {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
40144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @return {@link RadioManager#STATUS_OK} if successful; otherwise,
40244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link RadioManager#STATUS_ERROR}.
40344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
40444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
40544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public int openRadioBand(int radioBand) {
40644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
40744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "openRadioBand() for band: " + radioBand);
40844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
40944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
41044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null) {
41144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return RadioManager.STATUS_ERROR;
41244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
41344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
41444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return openRadioBandInternal(radioBand);
41544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
41644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
41744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
41844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Adds the given {@link android.hardware.radio.RadioTuner.Callback} to be notified
41944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * of any radio metadata changes.
42044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
42144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
42244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void addRadioTunerCallback(IRadioCallback callback) {
42344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (callback == null) {
42444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
42544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
42644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
42744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTunerCallbacks.add(callback);
42844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
42944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
43044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
43144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Removes the given {@link android.hardware.radio.RadioTuner.Callback} from receiving
43244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * any radio metadata chagnes.
43344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
43444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
43544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void removeRadioTunerCallback(IRadioCallback callback) {
43644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (callback == null) {
43744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
43844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
43944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
44044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTunerCallbacks.remove(callback);
44144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
44244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
44344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
444dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk        public ProgramInfo getCurrentProgramInfo() {
445dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            return mCurrentProgram;
44644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
44744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
44844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
44944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio was able to successfully initialize. A value of
45044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@code false} here could mean that the {@code RadioService} was not able to connect to
45144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * the {@link RadioManager} or there were no radio modules on the current device.
45244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
45344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
45444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean isInitialized() {
45544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mRadioSuccessfullyInitialized;
45644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
45744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
45844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
45944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio currently has focus and is therefore the application
46044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * that is supplying music.
46144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
46244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
46344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean hasFocus() {
46444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mHasAudioFocus;
46544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
46644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    };
46744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
46844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
46944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * A extension of {@link android.hardware.radio.RadioTuner.Callback} that delegates to a
47044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * callback registered on this service.
47144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
47244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private class InternalRadioCallback extends RadioTuner.Callback {
47344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
474dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk        public void onProgramInfoChanged(ProgramInfo info) {
47544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
476dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                Log.d(TAG, "Program info changed: " + info);
47744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
47844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
47925ed47e7c2126213a3ed1ba33eb0b797b0f0cbb2Tomasz Wasilczyk            mCurrentProgram = Objects.requireNonNull(info);
480dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            mMediaSession.notifyProgramInfoChanged(info);
48144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
482dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            for (IRadioCallback callback : mRadioTunerCallbacks) {
483dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                try {
484dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                    callback.onCurrentProgramInfoChanged(info);
485dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                } catch (RemoteException e) {
486dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                    Log.e(TAG, "Failed to notify about changed radio station", e);
48744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
48844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
48944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
49044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
49144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
49244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onConfigurationChanged(RadioManager.BandConfig config) {
49344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
49444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "onConfigurationChanged(): config: " + config);
49544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
49644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
49744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (config != null) {
49844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentRadioBand = config.getType();
49944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
50044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
50144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "onConfigurationChanged(): config type: " + mCurrentRadioBand);
50244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
50344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
50444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
50544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
50644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
50744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
50844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onRadioBandChanged(mCurrentRadioBand);
50944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
51044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
51144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onConfigurationChanged(); "
51244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        + "Failed to notify IRadioCallbacks: " + e.getMessage());
51344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
51444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
51544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
51644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
51744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onError(int status) {
51844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "onError(); status: " + status);
51944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
52044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If there is a hardware failure or the radio service died, then this requires a
52144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // re-opening of the radio tuner.
52244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (status == RadioTuner.ERROR_HARDWARE_FAILURE
52344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || status == RadioTuner.ERROR_SERVER_DIED) {
524fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk                close();
52544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
52644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Attempt to re-open the RadioTuner. Each time the radio tuner fails to open, the
52744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // mReOpenRadioTunerCount will be incremented.
52844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHandler.removeCallbacks(mOpenRadioTunerRunnable);
52944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHandler.postDelayed(mOpenRadioTunerRunnable,
53044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        mReOpenRadioTunerCount * RADIO_TUNER_REOPEN_DELAY_MS);
53144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
53244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mReOpenRadioTunerCount++;
53344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
53444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
53544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
53644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
53744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onError(status);
53844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
53944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
54044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onError(); Failed to notify IRadioCallbacks: " + e.getMessage());
54144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
54244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
54344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
54444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
54544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onControlChanged(boolean control) {
54644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If the radio loses control of the RadioTuner, then close it and allow it to be
54744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // re-opened when control has been gained.
54844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (!control) {
54944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                close();
55044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
55144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
55244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
55344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
55444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                openRadioBandInternal(mCurrentRadioBand);
55544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
55644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
55744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
55844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
55944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private final Runnable mOpenRadioTunerRunnable = () -> openRadioBandInternal(mCurrentRadioBand);
5603423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
5613423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    @Override
5623423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
56378605bcf875f767a6a68f66cbedbd73eef8aafb2Tomasz Wasilczyk        // TODO(b/75970985): check permissions, if necessary
56478605bcf875f767a6a68f66cbedbd73eef8aafb2Tomasz Wasilczyk        return mBrowseTree.getRoot();
5653423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    }
5663423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
5673423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    @Override
5683423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
5693423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        mBrowseTree.loadChildren(parentMediaId, result);
5703423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    }
57144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer}
572