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