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