1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.radio; 18 19 import android.hardware.radio.ProgramSelector; 20 import android.hardware.radio.RadioManager.ProgramInfo; 21 import android.hardware.radio.RadioMetadata; 22 import android.media.session.PlaybackState; 23 import android.view.View; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 import androidx.lifecycle.LiveData; 28 29 import com.android.car.broadcastradio.support.Program; 30 import com.android.car.broadcastradio.support.platform.ProgramInfoExt; 31 import com.android.car.radio.bands.ProgramType; 32 import com.android.car.radio.bands.RegionConfig; 33 import com.android.car.radio.service.RadioAppService; 34 import com.android.car.radio.service.RadioAppServiceWrapper; 35 import com.android.car.radio.service.RadioAppServiceWrapper.ConnectionState; 36 import com.android.car.radio.storage.RadioStorage; 37 import com.android.car.radio.util.Log; 38 39 import java.util.List; 40 import java.util.Objects; 41 42 /** 43 * The main controller of the radio app. 44 */ 45 public class RadioController { 46 private static final String TAG = "BcRadioApp.controller"; 47 48 private final Object mLock = new Object(); 49 private final RadioActivity mActivity; 50 51 private final RadioAppServiceWrapper mAppService = new RadioAppServiceWrapper(); 52 private final DisplayController mDisplayController; 53 private final RadioStorage mRadioStorage; 54 55 @Nullable private ProgramInfo mCurrentProgram; 56 RadioController(@onNull RadioActivity activity)57 public RadioController(@NonNull RadioActivity activity) { 58 mActivity = Objects.requireNonNull(activity); 59 60 mDisplayController = new DisplayController(activity, this); 61 62 mRadioStorage = RadioStorage.getInstance(activity); 63 mRadioStorage.getFavorites().observe(activity, this::onFavoritesChanged); 64 65 mAppService.getCurrentProgram().observe(activity, this::onCurrentProgramChanged); 66 mAppService.getConnectionState().observe(activity, this::onConnectionStateChanged); 67 68 mDisplayController.setBackwardSeekButtonListener(this::onBackwardSeekClick); 69 mDisplayController.setForwardSeekButtonListener(this::onForwardSeekClick); 70 mDisplayController.setPlayButtonCallback(this::onSwitchToPlayState); 71 mDisplayController.setFavoriteToggleListener(this::onFavoriteToggled); 72 } 73 onConnectionStateChanged(@onnectionState int state)74 private void onConnectionStateChanged(@ConnectionState int state) { 75 mDisplayController.setState(state); 76 if (state == RadioAppServiceWrapper.STATE_CONNECTED) { 77 mActivity.setProgramListSupported(mAppService.isProgramListSupported()); 78 mActivity.setSupportedProgramTypes(getRegionConfig().getSupportedProgramTypes()); 79 } 80 } 81 82 /** 83 * Starts the controller and establishes connection with {@link RadioAppService}. 84 */ start()85 public void start() { 86 mAppService.bind(mActivity); 87 } 88 89 /** 90 * Closes {@link RadioAppService} connection and cleans up the resources. 91 */ shutdown()92 public void shutdown() { 93 mAppService.unbind(); 94 } 95 96 /** 97 * See {@link RadioAppServiceWrapper#getPlaybackState}. 98 */ 99 @NonNull getPlaybackState()100 public LiveData<Integer> getPlaybackState() { 101 return mAppService.getPlaybackState(); 102 } 103 104 /** 105 * See {@link RadioAppServiceWrapper#getCurrentProgram}. 106 */ 107 @NonNull getCurrentProgram()108 public LiveData<ProgramInfo> getCurrentProgram() { 109 return mAppService.getCurrentProgram(); 110 } 111 112 /** 113 * See {@link RadioAppServiceWrapper#getProgramList}. 114 */ 115 @NonNull getProgramList()116 public LiveData<List<ProgramInfo>> getProgramList() { 117 return mAppService.getProgramList(); 118 } 119 120 /** 121 * Tunes the radio to the given channel. 122 */ tune(ProgramSelector sel)123 public void tune(ProgramSelector sel) { 124 mAppService.tune(sel); 125 } 126 127 /** 128 * Steps the radio tuner in the given direction, see {@link RadioAppServiceWrapper#step}. 129 */ step(boolean forward)130 public void step(boolean forward) { 131 mAppService.step(forward); 132 } 133 134 /** 135 * Switch radio band. Currently, this only supports FM and AM bands. 136 * 137 * @param pt {@link ProgramType} to switch to. 138 */ switchBand(@onNull ProgramType pt)139 public void switchBand(@NonNull ProgramType pt) { 140 mAppService.switchBand(pt); 141 } 142 143 @NonNull getRegionConfig()144 public RegionConfig getRegionConfig() { 145 return mAppService.getRegionConfig(); 146 } 147 148 /** 149 * Sets the service's {@link SkipMode}. 150 */ setSkipMode(@onNull SkipMode mode)151 public void setSkipMode(@NonNull SkipMode mode) { 152 mAppService.setSkipMode(mode); 153 } 154 onFavoritesChanged(List<Program> favorites)155 private void onFavoritesChanged(List<Program> favorites) { 156 synchronized (mLock) { 157 if (mCurrentProgram == null) return; 158 boolean isFav = RadioStorage.isFavorite(favorites, mCurrentProgram.getSelector()); 159 mDisplayController.setCurrentIsFavorite(isFav); 160 } 161 } 162 onCurrentProgramChanged(@onNull ProgramInfo info)163 private void onCurrentProgramChanged(@NonNull ProgramInfo info) { 164 synchronized (mLock) { 165 mCurrentProgram = Objects.requireNonNull(info); 166 ProgramSelector sel = info.getSelector(); 167 RadioMetadata meta = ProgramInfoExt.getMetadata(info); 168 169 mDisplayController.setChannel(sel); 170 171 mDisplayController.setStationName( 172 ProgramInfoExt.getProgramName(info, ProgramInfoExt.NAME_NO_CHANNEL_FALLBACK)); 173 174 if (meta.containsKey(RadioMetadata.METADATA_KEY_TITLE) 175 || meta.containsKey(RadioMetadata.METADATA_KEY_ARTIST)) { 176 mDisplayController.setDetails( 177 meta.getString(RadioMetadata.METADATA_KEY_TITLE), 178 meta.getString(RadioMetadata.METADATA_KEY_ARTIST)); 179 } else { 180 mDisplayController.setDetails(meta.getString(RadioMetadata.METADATA_KEY_RDS_RT)); 181 } 182 183 mDisplayController.setCurrentIsFavorite(mRadioStorage.isFavorite(sel)); 184 } 185 } 186 onBackwardSeekClick(View v)187 private void onBackwardSeekClick(View v) { 188 mDisplayController.startSeekAnimation(false); 189 mAppService.skip(false); 190 } 191 onForwardSeekClick(View v)192 private void onForwardSeekClick(View v) { 193 mDisplayController.startSeekAnimation(true); 194 mAppService.skip(true); 195 } 196 onSwitchToPlayState(@laybackState.State int newPlayState)197 private void onSwitchToPlayState(@PlaybackState.State int newPlayState) { 198 switch (newPlayState) { 199 case PlaybackState.STATE_PLAYING: 200 mAppService.setMuted(false); 201 break; 202 case PlaybackState.STATE_PAUSED: 203 case PlaybackState.STATE_STOPPED: 204 mAppService.setMuted(true); 205 break; 206 default: 207 Log.e(TAG, "Invalid request to switch to play state " + newPlayState); 208 } 209 } 210 onFavoriteToggled(boolean addFavorite)211 private void onFavoriteToggled(boolean addFavorite) { 212 synchronized (mLock) { 213 if (mCurrentProgram == null) return; 214 215 if (addFavorite) { 216 mRadioStorage.addFavorite(Program.fromProgramInfo(mCurrentProgram)); 217 } else { 218 mRadioStorage.removeFavorite(mCurrentProgram.getSelector()); 219 } 220 } 221 } 222 } 223