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.broadcastradio.support.platform.ProgramSelectorExt; 32 import com.android.car.radio.bands.ProgramType; 33 import com.android.car.radio.bands.RegionConfig; 34 import com.android.car.radio.media.TunerSession; 35 import com.android.car.radio.service.RadioAppService; 36 import com.android.car.radio.service.RadioAppServiceWrapper; 37 import com.android.car.radio.service.RadioAppServiceWrapper.ConnectionState; 38 import com.android.car.radio.storage.RadioStorage; 39 import com.android.car.radio.util.Log; 40 41 import java.util.List; 42 import java.util.Objects; 43 44 /** 45 * The main controller of the radio app. 46 */ 47 public class RadioController { 48 private static final String TAG = "BcRadioApp.controller"; 49 50 private final Object mLock = new Object(); 51 private final RadioActivity mActivity; 52 53 private final RadioAppServiceWrapper mAppService = new RadioAppServiceWrapper(); 54 private final DisplayController mDisplayController; 55 private final RadioStorage mRadioStorage; 56 57 @Nullable private ProgramInfo mCurrentProgram; 58 RadioController(@onNull RadioActivity activity)59 public RadioController(@NonNull RadioActivity activity) { 60 mActivity = Objects.requireNonNull(activity); 61 62 mDisplayController = new DisplayController(activity, this); 63 64 mRadioStorage = RadioStorage.getInstance(activity); 65 mRadioStorage.getFavorites().observe(activity, this::onFavoritesChanged); 66 67 mAppService.getCurrentProgram().observe(activity, this::onCurrentProgramChanged); 68 mAppService.getConnectionState().observe(activity, this::onConnectionStateChanged); 69 70 mDisplayController.setBackwardSeekButtonListener(this::onBackwardSeekClick); 71 mDisplayController.setForwardSeekButtonListener(this::onForwardSeekClick); 72 mDisplayController.setPlayButtonCallback(this::onSwitchToPlayState); 73 mDisplayController.setFavoriteToggleListener(this::onFavoriteToggled); 74 } 75 onConnectionStateChanged(@onnectionState int state)76 private void onConnectionStateChanged(@ConnectionState int state) { 77 mDisplayController.setState(state); 78 if (state == RadioAppServiceWrapper.STATE_CONNECTED) { 79 mActivity.setProgramListSupported(mAppService.isProgramListSupported()); 80 mActivity.setSupportedProgramTypes(getRegionConfig().getSupportedProgramTypes()); 81 } 82 } 83 84 /** 85 * Starts the controller and establishes connection with {@link RadioAppService}. 86 */ start()87 public void start() { 88 mAppService.bind(mActivity); 89 } 90 91 /** 92 * Closes {@link RadioAppService} connection and cleans up the resources. 93 */ shutdown()94 public void shutdown() { 95 mAppService.unbind(); 96 } 97 98 /** 99 * See {@link RadioAppServiceWrapper#getPlaybackState}. 100 */ 101 @NonNull getPlaybackState()102 public LiveData<Integer> getPlaybackState() { 103 return mAppService.getPlaybackState(); 104 } 105 106 /** 107 * See {@link RadioAppServiceWrapper#getCurrentProgram}. 108 */ 109 @NonNull getCurrentProgram()110 public LiveData<ProgramInfo> getCurrentProgram() { 111 return mAppService.getCurrentProgram(); 112 } 113 114 /** 115 * See {@link RadioAppServiceWrapper#getProgramList}. 116 */ 117 @NonNull getProgramList()118 public LiveData<List<ProgramInfo>> getProgramList() { 119 return mAppService.getProgramList(); 120 } 121 122 /** 123 * Tunes the radio to the given channel. 124 */ tune(ProgramSelector sel)125 public void tune(ProgramSelector sel) { 126 mAppService.tune(sel); 127 } 128 129 /** 130 * Steps the radio tuner in the given direction, see {@link RadioAppServiceWrapper#step}. 131 */ step(boolean forward)132 public void step(boolean forward) { 133 mAppService.step(forward); 134 } 135 136 /** 137 * Switch radio band. Currently, this only supports FM and AM bands. 138 * 139 * @param pt {@link ProgramType} to switch to. 140 */ switchBand(@onNull ProgramType pt)141 public void switchBand(@NonNull ProgramType pt) { 142 mAppService.switchBand(pt); 143 } 144 145 @NonNull getRegionConfig()146 public RegionConfig getRegionConfig() { 147 return mAppService.getRegionConfig(); 148 } 149 150 /** 151 * Sets the service's {@link SkipMode}. 152 */ setSkipMode(@onNull SkipMode mode)153 public void setSkipMode(@NonNull SkipMode mode) { 154 mAppService.setSkipMode(mode); 155 } 156 onFavoritesChanged(List<Program> favorites)157 private void onFavoritesChanged(List<Program> favorites) { 158 synchronized (mLock) { 159 if (mCurrentProgram == null) return; 160 boolean isFav = RadioStorage.isFavorite(favorites, mCurrentProgram.getSelector()); 161 mDisplayController.setCurrentIsFavorite(isFav); 162 } 163 } 164 onCurrentProgramChanged(@onNull ProgramInfo info)165 private void onCurrentProgramChanged(@NonNull ProgramInfo info) { 166 synchronized (mLock) { 167 mCurrentProgram = Objects.requireNonNull(info); 168 ProgramSelector sel = info.getSelector(); 169 RadioMetadata meta = ProgramInfoExt.getMetadata(info); 170 171 mDisplayController.setChannel(sel); 172 173 mDisplayController.setStationName( 174 ProgramSelectorExt.getDisplayName(sel, info.getChannel())); 175 176 if (meta.containsKey(RadioMetadata.METADATA_KEY_TITLE) 177 || meta.containsKey(RadioMetadata.METADATA_KEY_ARTIST)) { 178 mDisplayController.setDetails( 179 meta.getString(RadioMetadata.METADATA_KEY_TITLE), 180 meta.getString(RadioMetadata.METADATA_KEY_ARTIST)); 181 } else { 182 mDisplayController.setDetails(ProgramInfoExt.getProgramName(info, /* flags= */ 0, 183 TunerSession.PROGRAM_NAME_ORDER)); 184 } 185 186 mDisplayController.setCurrentIsFavorite(mRadioStorage.isFavorite(sel)); 187 } 188 } 189 onBackwardSeekClick(View v)190 private void onBackwardSeekClick(View v) { 191 mDisplayController.startSeekAnimation(false); 192 mAppService.skip(false); 193 } 194 onForwardSeekClick(View v)195 private void onForwardSeekClick(View v) { 196 mDisplayController.startSeekAnimation(true); 197 mAppService.skip(true); 198 } 199 onSwitchToPlayState(@laybackState.State int newPlayState)200 private void onSwitchToPlayState(@PlaybackState.State int newPlayState) { 201 switch (newPlayState) { 202 case PlaybackState.STATE_PLAYING: 203 mAppService.setMuted(false); 204 break; 205 case PlaybackState.STATE_PAUSED: 206 case PlaybackState.STATE_STOPPED: 207 mAppService.setMuted(true); 208 break; 209 default: 210 Log.e(TAG, "Invalid request to switch to play state " + newPlayState); 211 } 212 } 213 onFavoriteToggled(boolean addFavorite)214 private void onFavoriteToggled(boolean addFavorite) { 215 synchronized (mLock) { 216 if (mCurrentProgram == null) return; 217 218 if (addFavorite) { 219 mRadioStorage.addFavorite(Program.fromProgramInfo(mCurrentProgram)); 220 } else { 221 mRadioStorage.removeFavorite(mCurrentProgram.getSelector()); 222 } 223 } 224 } 225 } 226