1 /*
2  * Copyright (C) 2018 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 static com.android.car.ui.core.CarUi.requireToolbar;
20 import static com.android.car.ui.toolbar.Toolbar.State.HOME;
21 
22 import android.content.Intent;
23 import android.graphics.drawable.Drawable;
24 import android.os.Bundle;
25 import android.view.KeyEvent;
26 
27 import androidx.annotation.NonNull;
28 import androidx.fragment.app.FragmentActivity;
29 import androidx.viewpager.widget.ViewPager;
30 
31 import com.android.car.media.common.source.MediaSource;
32 import com.android.car.media.common.source.MediaTrampolineHelper;
33 import com.android.car.radio.bands.ProgramType;
34 import com.android.car.radio.service.RadioAppService;
35 import com.android.car.radio.util.Log;
36 import com.android.car.ui.baselayout.Insets;
37 import com.android.car.ui.baselayout.InsetsChangedListener;
38 import com.android.car.ui.toolbar.MenuItem;
39 import com.android.car.ui.toolbar.TabLayout;
40 import com.android.car.ui.toolbar.ToolbarController;
41 
42 import java.util.Arrays;
43 import java.util.List;
44 
45 /**
46  * The main activity for the radio app.
47  */
48 public class RadioActivity extends FragmentActivity implements InsetsChangedListener {
49     private static final String TAG = "BcRadioApp.activity";
50 
51     /**
52      * Intent action for notifying that the radio state has changed.
53      */
54     private static final String ACTION_RADIO_APP_STATE_CHANGE =
55             "android.intent.action.RADIO_APP_STATE_CHANGE";
56 
57     /**
58      * Boolean Intent extra indicating if the radio is the currently in the foreground.
59      */
60     private static final String EXTRA_RADIO_APP_FOREGROUND =
61             "android.intent.action.RADIO_APP_STATE";
62 
63     private RadioController mRadioController;
64     private BandController mBandController = new BandController();
65     private ToolbarController mToolbar;
66     private RadioPagerAdapter mRadioPagerAdapter;
67 
68     private boolean mUseSourceLogoForAppSelector;
69     private MediaTrampolineHelper mMediaTrampoline;
70 
71     @Override
onCarUiInsetsChanged(Insets insets)72     public void onCarUiInsetsChanged(Insets insets) {
73         // This InsetsChangedListener is just a marker that we will later handle
74         // insets in fragments, since the fragments aren't added immediately.
75         // Otherwise CarUi will apply the insets to the content view incorrectly.
76     }
77 
78     @Override
onCreate(Bundle savedInstanceState)79     protected void onCreate(Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81 
82         Log.d(TAG, "Radio app main activity created");
83 
84         setContentView(R.layout.radio_activity);
85 
86         mMediaTrampoline = new MediaTrampolineHelper(this);
87 
88         mRadioController = new RadioController(this);
89         mRadioController.getCurrentProgram().observe(this, info -> {
90             ProgramType programType = ProgramType.fromSelector(info.getSelector());
91             if (programType != null) {
92                 mBandController.setType(programType);
93                 updateMenuItems();
94             }
95         });
96 
97         mRadioPagerAdapter =
98                 new RadioPagerAdapter(this, getSupportFragmentManager(), mRadioController);
99         ViewPager viewPager = findViewById(R.id.viewpager);
100         viewPager.setAdapter(mRadioPagerAdapter);
101 
102         mUseSourceLogoForAppSelector =
103                 getResources().getBoolean(R.bool.use_media_source_logo_for_app_selector);
104 
105         mToolbar = requireToolbar(this);
106         mToolbar.setState(HOME);
107         mToolbar.setTitle(R.string.app_name);
108         if (!mUseSourceLogoForAppSelector) {
109             mToolbar.setLogo(R.drawable.logo_fm_radio);
110         }
111         mToolbar.registerOnTabSelectedListener(t ->
112                 viewPager.setCurrentItem(mToolbar.getTabPosition(t)));
113 
114         updateMenuItems();
115         setupTabsWithViewPager(viewPager);
116     }
117 
118     @Override
onStart()119     protected void onStart() {
120         super.onStart();
121 
122         mRadioController.start();
123 
124         Intent broadcast = new Intent(ACTION_RADIO_APP_STATE_CHANGE);
125         broadcast.putExtra(EXTRA_RADIO_APP_FOREGROUND, true);
126         sendBroadcast(broadcast);
127     }
128 
129     @Override
onNewIntent(Intent intent)130     protected void onNewIntent(Intent intent) {
131         super.onNewIntent(intent);
132         setIntent(intent); // getIntent() should always return the most recent
133 
134         if (Log.isLoggable(TAG, Log.DEBUG)) {
135             Log.d(TAG, "onNewIntent: " + intent);
136         }
137     }
138 
139     @Override
onResume()140     protected void onResume() {
141         super.onResume();
142 
143         Intent intent = getIntent();
144         if (Log.isLoggable(TAG, Log.DEBUG)) {
145             Log.d(TAG, "onResume intent: " + intent);
146         }
147 
148         if (intent != null) {
149             mMediaTrampoline.setLaunchedMediaSource(RadioAppService.getMediaSourceComp(this));
150 
151             // Mark the intent as consumed so that coming back from the media app selector doesn't
152             // set the source again.
153             setIntent(null);
154         }
155     }
156 
157     @Override
onStop()158     protected void onStop() {
159         super.onStop();
160 
161         Intent broadcast = new Intent(ACTION_RADIO_APP_STATE_CHANGE);
162         broadcast.putExtra(EXTRA_RADIO_APP_FOREGROUND, false);
163         sendBroadcast(broadcast);
164     }
165 
166     @Override
onDestroy()167     protected void onDestroy() {
168         super.onDestroy();
169 
170         mRadioController.shutdown();
171 
172         Log.d(TAG, "Radio app main activity destroyed");
173     }
174 
175     @Override
onKeyDown(int keyCode, KeyEvent event)176     public boolean onKeyDown(int keyCode, KeyEvent event) {
177         switch (keyCode) {
178             case KeyEvent.KEYCODE_MEDIA_STEP_FORWARD:
179                 mRadioController.step(true);
180                 return true;
181             case KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD:
182                 mRadioController.step(false);
183                 return true;
184             default:
185                 return super.onKeyDown(keyCode, event);
186         }
187     }
188 
189     /**
190      * Set whether background scanning is supported, to know whether to show the browse tab or not.
191      */
setProgramListSupported(boolean supported)192     public void setProgramListSupported(boolean supported) {
193         if (supported && mRadioPagerAdapter.addBrowseTab()) {
194             updateTabs();
195         }
196     }
197 
198     /**
199      * Sets supported program types.
200      */
setSupportedProgramTypes(@onNull List<ProgramType> supported)201     public void setSupportedProgramTypes(@NonNull List<ProgramType> supported) {
202         mBandController.setSupportedProgramTypes(supported);
203     }
204 
setupTabsWithViewPager(ViewPager viewPager)205     private void setupTabsWithViewPager(ViewPager viewPager) {
206         viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
207             @Override
208             public void onPageSelected(int position) {
209                 mToolbar.selectTab(position);
210             }
211         });
212         updateTabs();
213     }
214 
updateMenuItems()215     private void updateMenuItems() {
216         ProgramType currentBand = mBandController.getCurrentBand();
217         MenuItem bandSelectorMenuItem = MenuItem.builder(this)
218                 .setIcon(currentBand != null ? currentBand.getResourceId() : 0)
219                 .setOnClickListener(i -> {
220                     ProgramType programType = mBandController.switchToNext();
221                     mRadioController.switchBand(programType);
222                 })
223                 .build();
224 
225         Intent appSelectorIntent = MediaSource.getSourceSelectorIntent(this, false);
226         MenuItem appSelectorMenuItem = MenuItem.builder(this)
227                 .setIcon(mUseSourceLogoForAppSelector
228                         ? R.drawable.logo_fm_radio : R.drawable.ic_app_switch)
229                 .setTinted(!mUseSourceLogoForAppSelector)
230                 .setOnClickListener(m -> startActivity(appSelectorIntent))
231                 .build();
232 
233         mToolbar.setMenuItems(Arrays.asList(bandSelectorMenuItem, appSelectorMenuItem));
234     }
235 
updateTabs()236     private void updateTabs() {
237         mToolbar.clearAllTabs();
238         for (int i = 0; i < mRadioPagerAdapter.getCount(); i++) {
239             Drawable icon = mRadioPagerAdapter.getPageIcon(i);
240             CharSequence title = mRadioPagerAdapter.getPageTitle(i);
241             mToolbar.addTab(new TabLayout.Tab(icon, title));
242         }
243     }
244 }
245