RadioService.java revision 80706bfa3dbf8e5d93012cc001c3d95e7bd66746
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.RadioMetadata;
2744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.hardware.radio.RadioTuner;
2844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.media.AudioAttributes;
2944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.media.AudioManager;
303423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.os.Bundle;
3144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.Handler;
3244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.IBinder;
3344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.RemoteException;
3444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.os.SystemProperties;
3544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.support.annotation.Nullable;
3644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.text.TextUtils;
3744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport android.util.Log;
383423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.support.v4.media.MediaBrowserCompat.MediaItem;
393423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport android.support.v4.media.MediaBrowserServiceCompat;
40f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang
41b1c7c3ce9d0f647e2203f646928dd84edbb75c1cTomasz Wasilczykimport com.android.car.radio.media.Program;
423423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport com.android.car.radio.media.BrowseTree;
433423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykimport com.android.car.radio.media.TunerSession;
4444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.IRadioCallback;
4544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport com.android.car.radio.service.IRadioManager;
4689dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczykimport com.android.car.radio.platform.ImageMemoryCache;
47dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczykimport com.android.car.radio.platform.ProgramInfoExt;
48b1c7c3ce9d0f647e2203f646928dd84edbb75c1cTomasz Wasilczykimport com.android.car.radio.platform.ProgramSelectorExt;
49e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczykimport com.android.car.radio.platform.RadioManagerExt;
506c62f1af9fcbf3e7ca00a701266a6185d9ed6d9fTomasz Wasilczykimport com.android.car.radio.platform.RadioTunerExt;
5144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
5244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.ArrayList;
53b1c7c3ce9d0f647e2203f646928dd84edbb75c1cTomasz Wasilczykimport java.util.HashSet;
5444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyerimport java.util.List;
5544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
5644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer/**
5744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * A persistent {@link Service} that is responsible for opening and closing a {@link RadioTuner}.
5844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * All radio operations should be delegated to this class. To be notified of any changes in radio
5944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * metadata, register as a {@link android.hardware.radio.RadioTuner.Callback} on this Service.
6044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer *
6144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer * <p>Utilize the {@link RadioBinder} to perform radio operations.
6244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer */
633423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczykpublic class RadioService extends MediaBrowserServiceCompat
643423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        implements AudioManager.OnAudioFocusChangeListener {
653423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
663423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private static String TAG = "BcRadioApp.uisrv";
673423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
683423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public static String ACTION_UI_SERVICE = "com.android.car.radio.ACTION_UI_SERVICE";
6944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
7144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * The amount of time to wait before re-trying to open the {@link #mRadioTuner}.
7244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
7344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private static final int RADIO_TUNER_REOPEN_DELAY_MS = 5000;
7444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
7519c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private final Object mLock = new Object();
7619c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
7744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int mReOpenRadioTunerCount = 0;
7844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private final Handler mHandler = new Handler();
7944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
8019c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private RadioStorage mRadioStorage;
8119c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private final RadioStorage.PresetsChangeListener mPresetsListener = this::onPresetsChanged;
8219c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
8344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioTuner mRadioTuner;
8444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
8544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mRadioSuccessfullyInitialized;
8644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
87dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk    private ProgramInfo mCurrentProgram;
88dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk    private int mCurrentRadioBand = RadioManager.BAND_FM;
8944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
90e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk    private RadioManagerExt mRadioManager;
9189dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk    private ImageMemoryCache mImageCache;
9244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
93f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang    private AudioManager mAudioManager;
9444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private AudioAttributes mRadioAudioAttributes;
9544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
963423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private BrowseTree mBrowseTree;
973423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    private TunerSession mMediaSession;
98fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk    private ProgramList mProgramList;
993423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
10044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
10144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Whether or not this {@link RadioService} currently has audio focus, meaning it is the
10244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * primary driver of media. Usually, interaction with the radio will be prefaced with an
10344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * explicit request for audio focus. However, this is not ideal when muting the radio, so this
10444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * state needs to be tracked.
10544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
10644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private boolean mHasAudioFocus;
10744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
10844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
10944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * An internal {@link android.hardware.radio.RadioTuner.Callback} that will listen for
11044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * changes in radio metadata and pass these method calls through to
11144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link #mRadioTunerCallbacks}.
11244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
11344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private RadioTuner.Callback mInternalRadioTunerCallback = new InternalRadioCallback();
11444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private List<IRadioCallback> mRadioTunerCallbacks = new ArrayList<>();
11544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
11644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
11744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public IBinder onBind(Intent intent) {
1183423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        if (ACTION_UI_SERVICE.equals(intent.getAction())) {
1193423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk            return mBinder;
12044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
1213423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        return super.onBind(intent);
12244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
12344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
12544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onCreate() {
12644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        super.onCreate();
12744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
12844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
12944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onCreate()");
13044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
13144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
13219c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        mRadioStorage = RadioStorage.getInstance(this);
13319c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
134f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
135f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang        mRadioAudioAttributes = new AudioAttributes.Builder()
136f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang                .setUsage(AudioAttributes.USAGE_MEDIA)
137f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
138f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang                .build();
13944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
14089dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mRadioManager = new RadioManagerExt(this);
14189dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mImageCache = new ImageMemoryCache(mRadioManager, 1000);
142f3548bc1b56e17934a355a8c1847ac7efbc0d995Tomasz Wasilczyk        mBrowseTree = new BrowseTree(this, mImageCache);
14389dbd03c73e643523d9ed8b17554f0ab292c1213Tomasz Wasilczyk        mMediaSession = new TunerSession(this, mBrowseTree, mBinder, mImageCache);
1443423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        setSessionToken(mMediaSession.getSessionToken());
1453423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
146d768ae7c08f39a365a2be1bbe21e2248b41f60d0Tomasz Wasilczyk        mBrowseTree.setAmFmRegionConfig(mRadioManager.getAmFmRegionConfig());
147d768ae7c08f39a365a2be1bbe21e2248b41f60d0Tomasz Wasilczyk        openRadioBandInternal(mCurrentRadioBand);
14819c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
14919c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        mRadioStorage.addPresetsChangeListener(mPresetsListener);
15019c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        onPresetsChanged();
15144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mRadioSuccessfullyInitialized = true;
15344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
15444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
15544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
15644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onDestroy() {
15744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
15844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "onDestroy()");
15944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
16044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16119c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        mRadioStorage.removePresetsChangeListener(mPresetsListener);
1623423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        mMediaSession.release();
16380706bfa3dbf8e5d93012cc001c3d95e7bd66746Tomasz Wasilczyk        mRadioManager.getRadioTunerExt().close();
16444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        close();
16544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        super.onDestroy();
16744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
16844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
16919c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    private void onPresetsChanged() {
17019c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        synchronized (mLock) {
17119c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk            mBrowseTree.setFavorites(new HashSet<>(mRadioStorage.getPresets()));
1726407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mMediaSession.notifyFavoritesChanged();
17319c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk        }
17419c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk    }
17519c3b08d2481a5c9bf540130632334d4eed73babTomasz Wasilczyk
17644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
17744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Opens the current radio band. Currently, this only supports FM and AM bands.
17844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *
17944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
18044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     *                  {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
18144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * @return {@link RadioManager#STATUS_OK} if successful; otherwise,
18244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * {@link RadioManager#STATUS_ERROR}.
18344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
18444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int openRadioBandInternal(int radioBand) {
18544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
18644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "openRadioBandInternal() audio focus request fail");
18744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return RadioManager.STATUS_ERROR;
18844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
18944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
19044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mCurrentRadioBand = radioBand;
191e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk        if (mRadioTuner == null) {
192e185ab731e91dbb8a1f16b36a58ace2c5593b00dTomasz Wasilczyk            mRadioTuner = mRadioManager.openSession(mInternalRadioTunerCallback, null);
193fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList = mRadioTuner.getDynamicProgramList(null);
194fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mBrowseTree.setProgramList(mProgramList);
19544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
19644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
19744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
19844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "openRadioBandInternal() STATUS_OK");
19944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
20044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
20144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // Reset the counter for exponential backoff each time the radio tuner has been successfully
20244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        // opened.
20344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        mReOpenRadioTunerCount = 0;
20444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
20544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        return RadioManager.STATUS_OK;
20644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
20744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
20844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private int requestAudioFocus() {
209f334cefbfabd28cb294a378778c00f1975a00deeHongwei Wang        int status = mAudioManager.requestAudioFocus(this, mRadioAudioAttributes,
21044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    AudioManager.AUDIOFOCUS_GAIN, 0);
21144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
21244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
21344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "requestAudioFocus status: " + status);
21444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
21544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
21644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (status == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
21744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mHasAudioFocus = true;
21844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
2196c62f1af9fcbf3e7ca00a701266a6185d9ed6d9fTomasz Wasilczyk            mRadioManager.getRadioTunerExt().setMuted(false);
2206c62f1af9fcbf3e7ca00a701266a6185d9ed6d9fTomasz Wasilczyk
22144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            for (IRadioCallback callback : mRadioTunerCallbacks) {
22244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                try {
22344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onRadioMuteChanged(false);
22444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                } catch (RemoteException e) {
22544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.e(TAG, "requestAudioFocus(); onRadioMuteChanged() notify failed: "
22644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                            + e.getMessage());
22744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
22844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
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;
24144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
24244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        for (IRadioCallback callback : mRadioTunerCallbacks) {
24344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
24444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                callback.onRadioMuteChanged(true);
24544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
24644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "abandonAudioFocus(); onRadioMutechanged() notify failed: "
24744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        + e.getMessage());
24844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
24944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
25044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
25144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
25244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
25344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * Closes any active {@link RadioTuner}s and releases audio focus.
25444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
25544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private void close() {
25644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
25744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "close()");
25844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
25944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
26044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        abandonAudioFocus();
26144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
262fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk        if (mProgramList != null) {
263fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList.close();
264fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk            mProgramList = null;
265fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk        }
26644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (mRadioTuner != null) {
26744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.close();
26844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner = null;
26944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
27044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
27144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    @Override
27344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    public void onAudioFocusChange(int focusChange) {
27444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        if (Log.isLoggable(TAG, Log.DEBUG)) {
27544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.d(TAG, "focus change: " + focusChange);
27644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
27744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
27844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        switch (focusChange) {
27944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_GAIN:
28044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHasAudioFocus = true;
28144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                openRadioBandInternal(mCurrentRadioBand);
28244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
28344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
28444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // For a transient loss, just allow the focus to be released. The radio will stop
28544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // itself automatically. There is no need for an explicit abandon audio focus call
28644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // because this removes the AudioFocusChangeListener.
28744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
28844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
28944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHasAudioFocus = false;
29044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
29144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
29244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            case AudioManager.AUDIOFOCUS_LOSS:
29344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                close();
29444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                break;
29544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
29644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            default:
29744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Do nothing for all other cases.
29844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
29944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
30044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
30144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private IRadioManager.Stub mBinder = new IRadioManager.Stub() {
30244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
30344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Tunes the radio to the given frequency. To be notified of a successful tune, register
30444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * as a {@link android.hardware.radio.RadioTuner.Callback}.
30544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
30644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
30733ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczyk        public void tune(ProgramSelector sel) {
30833ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczyk            if (mRadioManager == null || sel == null
30944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
31044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
31144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
31244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
31333ee142f84a813e09a1cdeb72c136c7f89602450Tomasz Wasilczyk            mRadioTuner.tune(sel);
31444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
31544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
31644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
31744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Seeks the radio forward. To be notified of a successful tune, register as a
31844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link android.hardware.radio.RadioTuner.Callback}.
31944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
32044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
32144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void seekForward() {
32244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null
32344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
32444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
32544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
32644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
32744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
32844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                int radioStatus = openRadioBandInternal(mCurrentRadioBand);
32944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
33044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
33144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
33244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
33344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
33444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.scan(RadioTuner.DIRECTION_UP, true);
33544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
33644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
33744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
33844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Seeks the radio backwards. To be notified of a successful tune, register as a
33944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link android.hardware.radio.RadioTuner.Callback}.
34044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
34144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
34244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void seekBackward() {
34344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null
34444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || requestAudioFocus() != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
34544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
34644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
34744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
34844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
34944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                int radioStatus = openRadioBandInternal(mCurrentRadioBand);
35044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (radioStatus == RadioManager.STATUS_ERROR) {
35144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    return;
35244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
35344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
35444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
35544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
35644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
35744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3586baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        private boolean setMuted(boolean mute) {
35980706bfa3dbf8e5d93012cc001c3d95e7bd66746Tomasz Wasilczyk            if (!mRadioManager.getRadioTunerExt().setMuted(mute)) return false;
36044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3616baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk            for (IRadioCallback callback : mRadioTunerCallbacks) {
3626baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk                try {
3636baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk                    callback.onRadioMuteChanged(mute);
3646baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk                } catch (RemoteException e) {
3656c62f1af9fcbf3e7ca00a701266a6185d9ed6d9fTomasz Wasilczyk                    Log.e(TAG, "onRadioMuteChanged callback failed", e);
36644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
36744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
36844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3696baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk            return true;
3706baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        }
37144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
3726baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        /**
3736baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         * Mutes the radio.
3746baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         *
3756baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         * @return {@code true} if the mute was successful.
3766baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk         */
3776baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        @Override
3786baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk        public boolean mute() {
3796baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk            return setMuted(true);
38044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
38144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
38244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
38344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Un-mutes the radio and causes audio to play.
38444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *
38544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @return {@code true} if the un-mute was successful.
38644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
38744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
38844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean unMute() {
3896baa9b3004a4a68c41206db235639dc3806be282Tomasz Wasilczyk            if (!setMuted(false)) return false;
39044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
39144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return requestAudioFocus() == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
39244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
39344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
39444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
39544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio is currently muted.
39644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
39744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
39844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean isMuted() {
39980706bfa3dbf8e5d93012cc001c3d95e7bd66746Tomasz Wasilczyk            return mRadioManager.getRadioTunerExt().isMuted();
40044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
40144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
4026407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        @Override
4036407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        public void addFavorite(Program program) {
4046407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mRadioStorage.storePreset(program);
4056407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        }
4066407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk
4076407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        @Override
4086407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        public void removeFavorite(ProgramSelector sel) {
4096407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk            mRadioStorage.removePreset(sel);
4106407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk        }
4116407b74e0ac4968f84463b2b9b9762d7fda68daeTomasz Wasilczyk
41244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
41344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Opens the radio for the given band.
41444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *
41544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
41644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         *                  {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
41744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * @return {@link RadioManager#STATUS_OK} if successful; otherwise,
41844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@link RadioManager#STATUS_ERROR}.
41944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
42044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
42144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public int openRadioBand(int radioBand) {
42244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
42344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "openRadioBand() for band: " + radioBand);
42444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
42544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
42644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioManager == null) {
42744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return RadioManager.STATUS_ERROR;
42844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
42944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
43044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return openRadioBandInternal(radioBand);
43144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
43244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
43344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
43444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Adds the given {@link android.hardware.radio.RadioTuner.Callback} to be notified
43544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * of any radio metadata changes.
43644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
43744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
43844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void addRadioTunerCallback(IRadioCallback callback) {
43944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (callback == null) {
44044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
44144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
44244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
44344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTunerCallbacks.add(callback);
44444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
44544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
44644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
44744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Removes the given {@link android.hardware.radio.RadioTuner.Callback} from receiving
44844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * any radio metadata chagnes.
44944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
45044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
45144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void removeRadioTunerCallback(IRadioCallback callback) {
45244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (callback == null) {
45344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
45444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
45544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
45644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            mRadioTunerCallbacks.remove(callback);
45744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
45844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
45944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
460dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk        public ProgramInfo getCurrentProgramInfo() {
461dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            return mCurrentProgram;
46244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
46344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
46444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
46544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio was able to successfully initialize. A value of
46644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * {@code false} here could mean that the {@code RadioService} was not able to connect to
46744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * the {@link RadioManager} or there were no radio modules on the current device.
46844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
46944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
47044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean isInitialized() {
47144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mRadioSuccessfullyInitialized;
47244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
47344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
47444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        /**
47544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * Returns {@code true} if the radio currently has focus and is therefore the application
47644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         * that is supplying music.
47744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer         */
47844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
47944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public boolean hasFocus() {
48044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            return mHasAudioFocus;
48144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
48244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    };
48344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
48444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    /**
48544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * A extension of {@link android.hardware.radio.RadioTuner.Callback} that delegates to a
48644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     * callback registered on this service.
48744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer     */
48844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private class InternalRadioCallback extends RadioTuner.Callback {
48944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
490dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk        public void onProgramInfoChanged(ProgramInfo info) {
49144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
492dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                Log.d(TAG, "Program info changed: " + info);
49344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
49444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
495dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            mCurrentProgram = info;
496dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            mMediaSession.notifyProgramInfoChanged(info);
49744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
498dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk            for (IRadioCallback callback : mRadioTunerCallbacks) {
499dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                try {
500dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                    callback.onCurrentProgramInfoChanged(info);
501dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                } catch (RemoteException e) {
502dc70a5cad0f5b3586a3ff2e94e902f7f6c45b5e8Tomasz Wasilczyk                    Log.e(TAG, "Failed to notify about changed radio station", e);
50344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
50444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
50544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
50644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
50744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
50844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onConfigurationChanged(RadioManager.BandConfig config) {
50944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (Log.isLoggable(TAG, Log.DEBUG)) {
51044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.d(TAG, "onConfigurationChanged(): config: " + config);
51144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
51244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
51344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (config != null) {
51444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mCurrentRadioBand = config.getType();
51544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
51644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                if (Log.isLoggable(TAG, Log.DEBUG)) {
51744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    Log.d(TAG, "onConfigurationChanged(): config type: " + mCurrentRadioBand);
51844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
51944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
52044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
52144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
52244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
52344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
52444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onRadioBandChanged(mCurrentRadioBand);
52544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
52644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
52744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onConfigurationChanged(); "
52844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        + "Failed to notify IRadioCallbacks: " + e.getMessage());
52944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
53044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
53144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
53244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
53344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onError(int status) {
53444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            Log.e(TAG, "onError(); status: " + status);
53544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
53644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If there is a hardware failure or the radio service died, then this requires a
53744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // re-opening of the radio tuner.
53844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (status == RadioTuner.ERROR_HARDWARE_FAILURE
53944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    || status == RadioTuner.ERROR_SERVER_DIED) {
540fa786fd40b477e8175298740451fb15e2623d929Tomasz Wasilczyk                close();
54144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
54244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // Attempt to re-open the RadioTuner. Each time the radio tuner fails to open, the
54344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                // mReOpenRadioTunerCount will be incremented.
54444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHandler.removeCallbacks(mOpenRadioTunerRunnable);
54544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mHandler.postDelayed(mOpenRadioTunerRunnable,
54644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                        mReOpenRadioTunerCount * RADIO_TUNER_REOPEN_DELAY_MS);
54744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
54844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                mReOpenRadioTunerCount++;
54944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
55044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
55144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            try {
55244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                for (IRadioCallback callback : mRadioTunerCallbacks) {
55344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                    callback.onError(status);
55444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                }
55544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            } catch (RemoteException e) {
55644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                Log.e(TAG, "onError(); Failed to notify IRadioCallbacks: " + e.getMessage());
55744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
55844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
55944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
56044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        @Override
56144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        public void onControlChanged(boolean control) {
56244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // If the radio loses control of the RadioTuner, then close it and allow it to be
56344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            // re-opened when control has been gained.
56444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (!control) {
56544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                close();
56644f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                return;
56744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
56844f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
56944f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            if (mRadioTuner == null) {
57044f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer                openRadioBandInternal(mCurrentRadioBand);
57144f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer            }
57244f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer        }
57344f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    }
57444f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer
57544f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer    private final Runnable mOpenRadioTunerRunnable = () -> openRadioBandInternal(mCurrentRadioBand);
5763423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
5773423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    @Override
5783423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
57978605bcf875f767a6a68f66cbedbd73eef8aafb2Tomasz Wasilczyk        // TODO(b/75970985): check permissions, if necessary
58078605bcf875f767a6a68f66cbedbd73eef8aafb2Tomasz Wasilczyk        return mBrowseTree.getRoot();
5813423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    }
5823423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk
5833423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    @Override
5843423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
5853423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk        mBrowseTree.loadChildren(parentMediaId, result);
5863423d7204260b1b5707d103956b6776bf46c996aTomasz Wasilczyk    }
58744f17dab6698b8c5d87672f5df71c471bd4b91a3Rakesh Iyer}
588