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