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.Intent;
21fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczykimport android.hardware.radio.ProgramList;
2233ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczykimport android.hardware.radio.ProgramSelector;
2344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioManager;
24dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczykimport android.hardware.radio.RadioManager.ProgramInfo;
2544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioTuner;
263423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.os.Bundle;
2744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.Handler;
2844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.IBinder;
2944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.RemoteException;
303423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.support.v4.media.MediaBrowserCompat.MediaItem;
313423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.support.v4.media.MediaBrowserServiceCompat;
329e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczykimport android.support.v4.media.session.PlaybackStateCompat;
334467ff0c4f0b80cc35be17f1af7c95c2a591bdd2Tomasz Wasilczykimport android.util.Log;
34f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang
35274d116b2b8c66d17dfc00478d16f6e74891867cTomasz Wasilczykimport com.android.car.broadcastradio.support.Program;
36274d116b2b8c66d17dfc00478d16f6e74891867cTomasz Wasilczykimport com.android.car.broadcastradio.support.media.BrowseTree;
37317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczykimport com.android.car.broadcastradio.support.platform.ProgramSelectorExt;
389e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczykimport com.android.car.radio.audio.AudioStreamController;
399e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczykimport com.android.car.radio.audio.IPlaybackStateListener;
403423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport com.android.car.radio.media.TunerSession;
4189dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczykimport com.android.car.radio.platform.ImageMemoryCache;
42e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczykimport com.android.car.radio.platform.RadioManagerExt;
434467ff0c4f0b80cc35be17f1af7c95c2a591bdd2Tomasz Wasilczykimport com.android.car.radio.service.IRadioCallback;
444467ff0c4f0b80cc35be17f1af7c95c2a591bdd2Tomasz Wasilczykimport com.android.car.radio.service.IRadioManager;
459de3e263e20b844365b10bc9c342c136cd01ba0bTomasz Wasilczykimport com.android.car.radio.storage.RadioStorage;
4644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
4744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.ArrayList;
48b1c7c3ce9d0f647e2203f646928dd84edbb75c1cTomasz Wasilczykimport java.util.HashSet;
4944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.List;
5025ed47e7c2126213a3ed1ba33eb0b797b0f0cbb2Tomasz Wasilczykimport java.util.Objects;
518a5cca4137c99dd777fc6f1e2e114dcd59404ccbTomasz Wasilczykimport java.util.Optional;
5244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
5344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer/**
5444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * A persistent {@link Service} that is responsible for opening and closing a {@link RadioTuner}.
5544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * All radio operations should be delegated to this class. To be notified of any changes in radio
5644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * metadata, register as a {@link android.hardware.radio.RadioTuner.Callback} on this Service.
5744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer *
5844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * <p>Utilize the {@link RadioBinder} to perform radio operations.
5944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer */
609e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczykpublic class RadioService extends MediaBrowserServiceCompat implements IPlaybackStateListener {
613423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
623423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private static String TAG = "BcRadioApp.uisrv";
633423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
643423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public static String ACTION_UI_SERVICE = "com.android.car.radio.ACTION_UI_SERVICE";
6544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
6644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
6744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * The amount of time to wait before re-trying to open the {@link #mRadioTuner}.
6844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
6944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private static final int RADIO_TUNER_REOPEN_DELAY_MS = 5000;
7044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7119c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private final Object mLock = new Object();
7219c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
7344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int mReOpenRadioTunerCount = 0;
7444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private final Handler mHandler = new Handler();
7544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7619c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private RadioStorage mRadioStorage;
7719c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private final RadioStorage.PresetsChangeListener mPresetsListener = this::onPresetsChanged;
7819c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
7944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioTuner mRadioTuner;
8044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
8144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mRadioSuccessfullyInitialized;
8244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
83dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk    private ProgramInfo mCurrentProgram;
8444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
85e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk    private RadioManagerExt mRadioManager;
8689dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk    private ImageMemoryCache mImageCache;
8744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
889e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk    private AudioStreamController mAudioStreamController;
8944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
903423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private BrowseTree mBrowseTree;
913423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private TunerSession mMediaSession;
92fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk    private ProgramList mProgramList;
933423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
9444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
9544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Whether or not this {@link RadioService} currently has audio focus, meaning it is the
9644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * primary driver of media. Usually, interaction with the radio will be prefaced with an
9744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * explicit request for audio focus. However, this is not ideal when muting the radio, so this
9844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * state needs to be tracked.
9944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
10044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mHasAudioFocus;
10144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
10244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
10344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * An internal {@link android.hardware.radio.RadioTuner.Callback} that will listen for
10444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * changes in radio metadata and pass these method calls through to
10544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link #mRadioTunerCallbacks}.
10644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
10744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioTuner.Callback mInternalRadioTunerCallback = new InternalRadioCallback();
10844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private List<IRadioCallback> mRadioTunerCallbacks = new ArrayList<>();
10944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
11144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public IBinder onBind(Intent intent) {
1123423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        if (ACTION_UI_SERVICE.equals(intent.getAction())) {
1133423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk            return mBinder;
11444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
1153423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        return super.onBind(intent);
11644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
11744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
11944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onCreate() {
12044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        super.onCreate();
12144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
12344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onCreate()");
12444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
12544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12689dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mRadioManager = new RadioManagerExt(this);
1279e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk        mAudioStreamController = new AudioStreamController(this, mRadioManager);
1289e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk        mRadioStorage = RadioStorage.getInstance(this);
12989dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mImageCache = new ImageMemoryCache(mRadioManager, 1000);
1309e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk
131f3548bc1b56e17934a355a8c1847ac7efbc0d995Tomasz Wasilczyk        mBrowseTree = new BrowseTree(this, mImageCache);
13289dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mMediaSession = new TunerSession(this, mBrowseTree, mBinder, mImageCache);
1333423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        setSessionToken(mMediaSession.getSessionToken());
1349e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk        mAudioStreamController.addPlaybackStateListener(mMediaSession);
135d768ae7c08f39a365a2be1bbe21e2248b41f60d0Tomasz Wasilczyk        mBrowseTree.setAmFmRegionConfig(mRadioManager.getAmFmRegionConfig());
13619c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
13719c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        mRadioStorage.addPresetsChangeListener(mPresetsListener);
13819c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        onPresetsChanged();
13944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
1409e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk        mAudioStreamController.addPlaybackStateListener(this);
1419e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk
142317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk        openRadioBandInternal(mRadioStorage.getStoredRadioBand());
1439e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk
14444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mRadioSuccessfullyInitialized = true;
14544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
14644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
14744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
14844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onDestroy() {
14944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
15044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onDestroy()");
15144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
15244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15319c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        mRadioStorage.removePresetsChangeListener(mPresetsListener);
1543423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        mMediaSession.release();
15580706bfa3dbf8e5d93012cc001c3d95e7bd66746Tomasz Wasilczyk        mRadioManager.getRadioTunerExt().close();
15644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        close();
15744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        super.onDestroy();
15944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
16044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16119c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private void onPresetsChanged() {
16219c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        synchronized (mLock) {
16319c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk            mBrowseTree.setFavorites(new HashSet<>(mRadioStorage.getPresets()));
1646407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mMediaSession.notifyFavoritesChanged();
16519c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        }
16619c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    }
16719c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
16844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
16944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Opens the current radio band. Currently, this only supports FM and AM bands.
17044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *
17144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
17244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *                  {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
17344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @return {@link RadioManager#STATUS_OK} if successful; otherwise,
17444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link RadioManager#STATUS_ERROR}.
17544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
17644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int openRadioBandInternal(int radioBand) {
1779e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk        if (!mAudioStreamController.requestMuted(false)) return RadioManager.STATUS_ERROR;
17844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
179e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk        if (mRadioTuner == null) {
180e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk            mRadioTuner = mRadioManager.openSession(mInternalRadioTunerCallback, null);
181fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList = mRadioTuner.getDynamicProgramList(null);
182fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mBrowseTree.setProgramList(mProgramList);
18344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
18444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
18544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
18644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "openRadioBandInternal() STATUS_OK");
18744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
18844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
18944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // Reset the counter for exponential backoff each time the radio tuner has been successfully
19044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // opened.
19144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mReOpenRadioTunerCount = 0;
19244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
193317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk        tuneToDefault(radioBand);
194317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk
19544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return RadioManager.STATUS_OK;
19644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
19744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
198317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk    private void tuneToDefault(int band) {
199317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk        if (!mAudioStreamController.preparePlayback(Optional.empty())) return;
200317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk
201317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk        long storedChannel = mRadioStorage.getStoredRadioChannel(band);
202317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk        if (storedChannel != RadioStorage.INVALID_RADIO_CHANNEL) {
203317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            Log.i(TAG, "Restoring stored program: " + storedChannel);
204317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            mRadioTuner.tune(ProgramSelectorExt.createAmFmSelector(storedChannel));
205317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk        } else {
206317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            Log.i(TAG, "No stored program, seeking forward to not play static");
207317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk
208317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            // TODO(b/80500464): don't hardcode, pull from tuner config
209317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            long lastChannel;
210317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            if (band == RadioManager.BAND_AM) lastChannel = 1620;
211317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            else lastChannel = 108000;
212317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            mRadioTuner.tune(ProgramSelectorExt.createAmFmSelector(lastChannel));
213317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk
214317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            mRadioTuner.scan(RadioTuner.DIRECTION_UP, true);
215317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk        }
216317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk    }
217317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk
2189e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk    /* TODO(b/73950974): remove onRadioMuteChanged from IRadioCallback,
2199e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk     * use IPlaybackStateListener directly.
2209e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk     */
2219e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk    @Override
2229e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk    public void onPlaybackStateChanged(@PlaybackStateCompat.State int state) {
2239e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk        boolean muted = state != PlaybackStateCompat.STATE_PLAYING;
2241cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk        synchronized (mLock) {
2251cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk            for (IRadioCallback callback : mRadioTunerCallbacks) {
2261cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                try {
2271cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                    callback.onRadioMuteChanged(muted);
2281cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                } catch (RemoteException e) {
2291cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                    Log.e(TAG, "Mute state change callback failed", e);
2301cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk                }
2311cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk            }
2321cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk        }
2331cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk    }
2341cc2cf31cdcdcd0996b58239e3c685fe92e80760Tomasz Wasilczyk
23544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
23644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Closes any active {@link RadioTuner}s and releases audio focus.
23744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
23844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void close() {
23944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
24044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "close()");
24144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
24244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
2439e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk        mAudioStreamController.requestMuted(true);
24444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
245fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk        if (mProgramList != null) {
246fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList.close();
247fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList = null;
248fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk        }
24944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mRadioTuner != null) {
25044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.close();
25144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner = null;
25244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
25344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
25444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
25544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private IRadioManager.Stub mBinder = new IRadioManager.Stub() {
25644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
25744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Tunes the radio to the given frequency. To be notified of a successful tune, register
25844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * as a {@link android.hardware.radio.RadioTuner.Callback}.
25944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
26044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
26133ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczyk        public void tune(ProgramSelector sel) {
2628a5cca4137c99dd777fc6f1e2e114dcd59404ccbTomasz Wasilczyk            if (!mAudioStreamController.preparePlayback(Optional.empty())) return;
26333ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczyk            mRadioTuner.tune(sel);
26444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
26544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
266a77ec639e6563d1d6bb49a837ef6f4bb9320f1d9Simon Dai        @Override
267a77ec639e6563d1d6bb49a837ef6f4bb9320f1d9Simon Dai        public List<ProgramInfo> getProgramList() {
268a77ec639e6563d1d6bb49a837ef6f4bb9320f1d9Simon Dai            return mRadioTuner.getDynamicProgramList(null).toList();
269a77ec639e6563d1d6bb49a837ef6f4bb9320f1d9Simon Dai        }
270a77ec639e6563d1d6bb49a837ef6f4bb9320f1d9Simon Dai
27144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
27244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Seeks the radio forward. To be notified of a successful tune, register as a
27344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link android.hardware.radio.RadioTuner.Callback}.
27444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
27544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
27644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void seekForward() {
2778a5cca4137c99dd777fc6f1e2e114dcd59404ccbTomasz Wasilczyk            if (!mAudioStreamController.preparePlayback(Optional.of(true))) return;
27844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
280317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk                int radioStatus = openRadioBandInternal(mRadioStorage.getStoredRadioBand());
28144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
28244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
28344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
28444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
28544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
28644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.scan(RadioTuner.DIRECTION_UP, true);
28744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
28844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
28944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
29044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Seeks the radio backwards. To be notified of a successful tune, register as a
29144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link android.hardware.radio.RadioTuner.Callback}.
29244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
29344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
29444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void seekBackward() {
2958a5cca4137c99dd777fc6f1e2e114dcd59404ccbTomasz Wasilczyk            if (!mAudioStreamController.preparePlayback(Optional.of(false))) return;
29644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
29744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
298317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk                int radioStatus = openRadioBandInternal(mRadioStorage.getStoredRadioBand());
29944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
30044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
30144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
30244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
30344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
30444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
30544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
30644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3076baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        /**
3086baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         * Mutes the radio.
3096baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         *
3106baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         * @return {@code true} if the mute was successful.
3116baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         */
3126baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        @Override
3136baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        public boolean mute() {
3149e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk            return mAudioStreamController.requestMuted(true);
31544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
31644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
31744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
31844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Un-mutes the radio and causes audio to play.
31944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *
32044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @return {@code true} if the un-mute was successful.
32144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
32244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
32344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean unMute() {
3249e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk            return mAudioStreamController.requestMuted(false);
32544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
32644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
32744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
32844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio is currently muted.
32944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
33044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
33144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean isMuted() {
3329e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk            return mAudioStreamController.isMuted();
33344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
33444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3356407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        @Override
3366407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        public void addFavorite(Program program) {
3376407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mRadioStorage.storePreset(program);
3386407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        }
3396407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk
3406407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        @Override
3416407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        public void removeFavorite(ProgramSelector sel) {
3426407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mRadioStorage.removePreset(sel);
3436407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        }
3446407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk
34544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
346317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk        public void switchBand(int radioBand) {
347317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            tuneToDefault(radioBand);
34844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
34944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
35044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
35144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Adds the given {@link android.hardware.radio.RadioTuner.Callback} to be notified
35244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * of any radio metadata changes.
35344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
35444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
35544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void addRadioTunerCallback(IRadioCallback callback) {
35644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (callback == null) {
35744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
35844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
35944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
36044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTunerCallbacks.add(callback);
36144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
36244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
36344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
36444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Removes the given {@link android.hardware.radio.RadioTuner.Callback} from receiving
36544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * any radio metadata chagnes.
36644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
36744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
36844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void removeRadioTunerCallback(IRadioCallback callback) {
36944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (callback == null) {
37044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
37144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
37244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
37344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTunerCallbacks.remove(callback);
37444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
37544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
37644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
377dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk        public ProgramInfo getCurrentProgramInfo() {
378dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            return mCurrentProgram;
37944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
38044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
38144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
38244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio was able to successfully initialize. A value of
38344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@code false} here could mean that the {@code RadioService} was not able to connect to
38444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * the {@link RadioManager} or there were no radio modules on the current device.
38544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
38644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
38744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean isInitialized() {
38844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mRadioSuccessfullyInitialized;
38944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
39044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
39144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
39244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio currently has focus and is therefore the application
39344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * that is supplying music.
39444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
39544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
39644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean hasFocus() {
39744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mHasAudioFocus;
39844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
39944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    };
40044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
40144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
40244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * A extension of {@link android.hardware.radio.RadioTuner.Callback} that delegates to a
40344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * callback registered on this service.
40444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
40544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private class InternalRadioCallback extends RadioTuner.Callback {
40644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
407dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk        public void onProgramInfoChanged(ProgramInfo info) {
40844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
409dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                Log.d(TAG, "Program info changed: " + info);
41044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
41144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
41225ed47e7c2126213a3ed1ba33eb0b797b0f0cbb2Tomasz Wasilczyk            mCurrentProgram = Objects.requireNonNull(info);
413dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            mMediaSession.notifyProgramInfoChanged(info);
4148a5cca4137c99dd777fc6f1e2e114dcd59404ccbTomasz Wasilczyk            mAudioStreamController.notifyProgramInfoChanged();
415317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            mRadioStorage.storeRadioChannel(info.getSelector());
41644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
417dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            for (IRadioCallback callback : mRadioTunerCallbacks) {
418dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                try {
419dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                    callback.onCurrentProgramInfoChanged(info);
420dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                } catch (RemoteException e) {
421dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                    Log.e(TAG, "Failed to notify about changed radio station", e);
42244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
42344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
42444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
42544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
42644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
42744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onError(int status) {
42844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "onError(); status: " + status);
42944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
43044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If there is a hardware failure or the radio service died, then this requires a
43144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // re-opening of the radio tuner.
43244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (status == RadioTuner.ERROR_HARDWARE_FAILURE
43344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || status == RadioTuner.ERROR_SERVER_DIED) {
434fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk                close();
43544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
43644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Attempt to re-open the RadioTuner. Each time the radio tuner fails to open, the
43744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // mReOpenRadioTunerCount will be incremented.
43844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHandler.removeCallbacks(mOpenRadioTunerRunnable);
43944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHandler.postDelayed(mOpenRadioTunerRunnable,
44044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        mReOpenRadioTunerCount * RADIO_TUNER_REOPEN_DELAY_MS);
44144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
44244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mReOpenRadioTunerCount++;
44344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
44444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
44544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
44644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
44744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onError(status);
44844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
44944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
45044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onError(); Failed to notify IRadioCallbacks: " + e.getMessage());
45144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
45244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
45344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
45444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
45544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onControlChanged(boolean control) {
45644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If the radio loses control of the RadioTuner, then close it and allow it to be
45744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // re-opened when control has been gained.
45844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (!control) {
45944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                close();
46044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
46144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
46244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
46344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
464317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk                openRadioBandInternal(mRadioStorage.getStoredRadioBand());
46544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
46644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
46744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
46844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
469317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk    private final Runnable mOpenRadioTunerRunnable =
470317f803138b6f9757a3961771b7a07ce54f803f6Tomasz Wasilczyk            () -> openRadioBandInternal(mRadioStorage.getStoredRadioBand());
4713423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
4723423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    @Override
4733423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
474b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk        /* Radio application may restrict who can read its MediaBrowser tree.
475b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk         * Our implementation doesn't.
476b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk         */
47778605bcf875f767a6a68f66cbedbd73eef8aafb2Tomasz Wasilczyk        return mBrowseTree.getRoot();
4783423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    }
4793423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
4803423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    @Override
4813423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
4823423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        mBrowseTree.loadChildren(parentMediaId, result);
4833423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    }
4849e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk
4859e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk    @Override
486b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk    public int onStartCommand(Intent intent, int flags, int startId) {
487b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk        if (BrowseTree.ACTION_PLAY_BROADCASTRADIO.equals(intent.getAction())) {
488b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk            Log.i(TAG, "Executing general play radio intent");
489b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk            mMediaSession.getController().getTransportControls().playFromMediaId(
490b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk                    mBrowseTree.getRoot().getRootId(), null);
491b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk            return START_NOT_STICKY;
492b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk        }
493b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk
494b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk        return super.onStartCommand(intent, flags, startId);
495b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk    }
496b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk
497b415fe8a6968f0b84be98ddd192bfeb50390c652Tomasz Wasilczyk    @Override
4989e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk    public IBinder asBinder() {
4999e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk        throw new UnsupportedOperationException("Not a binder");
5009e86a0013e768553d66407afd300366c9d651570Tomasz Wasilczyk    }
50144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer}
502