View Javadoc

1   /***
2    *     Ambient - A music player for the Android platform
3    Copyright (C) 2007 Martin Vysny
4    
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9    
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14  
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package sk.baka.ambient.activity.main;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.Comparator;
25  import java.util.List;
26  import java.util.concurrent.CopyOnWriteArrayList;
27  
28  import sk.baka.ambient.ActionsEnum;
29  import sk.baka.ambient.AmbientApplication;
30  import sk.baka.ambient.R;
31  import sk.baka.ambient.collection.TrackMetadataBean;
32  import sk.baka.ambient.commons.Interval;
33  import sk.baka.ambient.stream.shoutcast.Radio;
34  import sk.baka.ambient.stream.shoutcast.ShoutcastUtils;
35  import sk.baka.ambient.views.gesturelist.GesturesListView;
36  import android.view.View;
37  import android.widget.TextView;
38  
39  /***
40   * Controls the shoutcast server manager.
41   * 
42   * @author Martin Vysny
43   */
44  public final class ShoutcastController extends AbstractListController {
45  
46  	/***
47  	 * Creates the shoutcast controller instance.
48  	 * 
49  	 * @param activity
50  	 *            the activity instance.
51  	 * @param playlistView
52  	 *            the playlist view
53  	 */
54  	public ShoutcastController(final MainActivity activity,
55  			final GesturesListView playlistView) {
56  		super(R.id.shoutcastbrowser, R.id.shoutcastList, activity);
57  		initButtonBar(R.id.shoutcastButtons, actions);
58  		// register the drag'n'drop target.
59  		listView.dragDropViews.clear();
60  		listView.dragDropViews.add(playlistView);
61  	}
62  
63  	@Override
64  	public void destroy() {
65  		listView.dragDropViews.clear();
66  		super.destroy();
67  	}
68  
69  	@Override
70  	protected void onAction(ActionsEnum action) {
71  		if (action == ActionsEnum.ShoutcastNameTrackSwitch) {
72  			isRadioNames = !isRadioNames;
73  			if (radioList != null) {
74  				sortRadioList();
75  				update(null);
76  			}
77  			return;
78  		}
79  		if (action == ActionsEnum.Back) {
80  			goBack();
81  			return;
82  		}
83  		super.onAction(action);
84  	}
85  
86  	/***
87  	 * The actions to display on the Task switcher.
88  	 */
89  	public static final List<ActionsEnum> actions = Collections
90  			.unmodifiableList(Arrays.asList(ActionsEnum.Back,
91  					ActionsEnum.ShoutcastNameTrackSwitch));
92  
93  	/***
94  	 * If <code>true</code> then a genre list is final being shown. If
95  	 * <code>false</code> then a radio list is being shown.
96  	 */
97  	private volatile boolean isShowingGenre = true;
98  
99  	/***
100 	 * Used only when the radio list is shown. If <code>true</code> then the
101 	 * radio name is shown. If <code>false</code> then the currently played
102 	 * track name is shown.
103 	 */
104 	private boolean isRadioNames = true;
105 
106 	/***
107 	 * Cached genres list.
108 	 */
109 	private volatile List<String> genres = null;
110 
111 	/***
112 	 * Returns list of genres. If the genre list is not yet loaded then the
113 	 * download starts in the background and an empty list is returned.
114 	 * 
115 	 * @return the genre list.
116 	 */
117 	private List<String> getGenres() {
118 		if (genres != null) {
119 			return genres;
120 		}
121 		app.getBackgroundTasks().schedule(new GenreDownloader(),
122 				GenreDownloader.class, true,
123 				app.getString(R.string.shoutcastDownloadGenres));
124 		return Collections.emptyList();
125 	}
126 
127 	private final class GenreDownloader implements Runnable {
128 		public void run() {
129 			try {
130 				final List<String> genres = ShoutcastUtils.parseGenres();
131 				ShoutcastController.this.genres = new CopyOnWriteArrayList<String>(
132 						genres);
133 				updateAsync();
134 			} catch (final Exception e) {
135 				throw new RuntimeException(e);
136 			}
137 		}
138 	}
139 
140 	private void updateAsync() {
141 		AmbientApplication.getHandler().post(new Runnable() {
142 			public void run() {
143 				update(null);
144 			}
145 		});
146 	}
147 
148 	@Override
149 	protected void visibilityChanged(boolean visible) {
150 		if (visible) {
151 			update(null);
152 		}
153 	}
154 
155 	@Override
156 	protected void recomputeListItems() {
157 		// handle offline mode
158 		final boolean offline = !app.getStateHandler().getStartupState().online;
159 		mainView.findViewById(R.id.shoutcastbrowserControls).setVisibility(
160 				offline ? View.GONE : View.VISIBLE);
161 		mainView.findViewById(R.id.shoutcastOffline).setVisibility(
162 				offline ? View.VISIBLE : View.GONE);
163 		if (offline) {
164 			return;
165 		}
166 		listView.getModel().getModel().clear();
167 		if (!isVisible()) {
168 			return;
169 		}
170 		if (isShowingGenre) {
171 			listView.getModel().getModel().addAll(getGenres());
172 		} else {
173 			if (radioList != null) {
174 				listView.getModel().getModel().addAll(radioList);
175 			}
176 		}
177 	}
178 
179 	@Override
180 	public String getHint(Interval highlight) {
181 		return highlight.length + " radio(s)";
182 	}
183 
184 	@Override
185 	public boolean isComputeTracksLong(Interval interval) {
186 		return !interval.isEmpty();
187 	}
188 
189 	@Override
190 	public boolean isComputeTracksOnlineOp(Interval interval) {
191 		return !interval.isEmpty();
192 	}
193 
194 	@Override
195 	public boolean canComputeItems() {
196 		return !isShowingGenre;
197 	}
198 
199 	@Override
200 	public boolean canHighlight() {
201 		return !isShowingGenre;
202 	}
203 
204 	public void itemActivated(int index, Object model) {
205 		if (isShowingGenre) {
206 			showGenre((String) model);
207 		} else {
208 			final Radio r = (Radio) model;
209 			app.getBackgroundTasks().schedule(new PlaylistDownloader(r),
210 					PlaylistDownloader.class, true,
211 					app.getString(R.string.shoutcastDownloadURLs));
212 		}
213 	}
214 
215 	private final class PlaylistDownloader implements Runnable {
216 		private final Radio r;
217 		private volatile boolean gettingPlaylist = true;
218 		private volatile List<TrackMetadataBean> playlist = null;
219 
220 		/***
221 		 * Creates new instance.
222 		 * 
223 		 * @param r
224 		 *            the radio.
225 		 */
226 		public PlaylistDownloader(Radio r) {
227 			this.r = r;
228 		}
229 
230 		public void run() {
231 			if (gettingPlaylist) {
232 				playlist = getRadioStreams(r);
233 				playlist = Collections.synchronizedList(playlist);
234 				gettingPlaylist = false;
235 				AmbientApplication.getHandler().post(this);
236 			} else {
237 				app.getPlaylist().add(app.getPlaylist().size(), playlist);
238 			}
239 		}
240 	}
241 
242 	/***
243 	 * Loads the radio list.
244 	 * 
245 	 * @author Martin Vysny
246 	 */
247 	private class RadioDownloader implements Runnable {
248 		private final String genre;
249 
250 		/***
251 		 * Creates new instance.
252 		 * 
253 		 * @param genre
254 		 *            the genre.
255 		 */
256 		public RadioDownloader(final String genre) {
257 			this.genre = genre;
258 		}
259 
260 		public void run() {
261 			List<Radio> list;
262 			try {
263 				list = ShoutcastUtils.getRadioList(genre);
264 			} catch (final Exception e) {
265 				throw new RuntimeException(e);
266 			}
267 			radioList = Collections.synchronizedList(list);
268 			sortRadioList();
269 			isShowingGenre = false;
270 			updateAsync();
271 		}
272 	}
273 
274 	/***
275 	 * The radio list.
276 	 */
277 	private volatile List<Radio> radioList = null;
278 
279 	@Override
280 	public void removeItems(Interval remove) {
281 		goBack();
282 	}
283 
284 	private void goBack() {
285 		if (isShowingGenre) {
286 			// nowhere to go.
287 			return;
288 		}
289 		isShowingGenre = true;
290 		radioList = null;
291 		update(null);
292 	}
293 
294 	public void update(GesturesListView listView, View itemView, int index,
295 			Object model) {
296 		final TextView view = (TextView) itemView;
297 		if (listView.getHighlight().contains(index)) {
298 			view.setBackgroundColor(highlightColor);
299 		} else {
300 			view.setBackgroundColor(0);
301 		}
302 		if (model instanceof String) {
303 			view.setText((String) model);
304 		} else {
305 			final Radio r = (Radio) model;
306 			if (isRadioNames) {
307 				view.setText(r.name + " (" + r.genre + ")");
308 			} else {
309 				view.setText(r.currentTrack + " (" + r.genre + ")");
310 			}
311 		}
312 	}
313 
314 	private List<TrackMetadataBean> getRadioStreams(final Radio radio) {
315 		try {
316 			return radio.getRadioURLs();
317 		} catch (final Exception ex) {
318 			throw new RuntimeException(ex);
319 		}
320 	}
321 
322 	@Override
323 	public List<TrackMetadataBean> computeTracks(Interval highlight) {
324 		if (isShowingGenre)
325 			throw new IllegalStateException();
326 		final List<TrackMetadataBean> result = new ArrayList<TrackMetadataBean>();
327 		for (int i = highlight.start; i <= highlight.end; i++) {
328 			final Radio r = radioList.get(i);
329 			result.addAll(getRadioStreams(r));
330 		}
331 		return new CopyOnWriteArrayList<TrackMetadataBean>(result);
332 	}
333 
334 	private void sortRadioList() {
335 		if (isRadioNames) {
336 			ShoutcastUtils.sortByName(radioList);
337 		} else {
338 			Collections.sort(radioList, new Comparator<Radio>() {
339 				public int compare(Radio object1, Radio object2) {
340 					return object1.currentTrack
341 							.compareToIgnoreCase(object2.currentTrack);
342 				}
343 			});
344 		}
345 	}
346 
347 	/***
348 	 * Browse given genre.
349 	 * 
350 	 * @param genre
351 	 *            the genre to browse.
352 	 */
353 	public void showGenre(final String genre) {
354 		// read the radio list
355 		app.getBackgroundTasks().schedule(new RadioDownloader(genre),
356 				RadioDownloader.class, true,
357 				app.getString(R.string.shoutcastDownloadStations));
358 	}
359 
360 	@Override
361 	protected void performZoom(boolean zoom) {
362 		super.performZoom(zoom);
363 		initButtonBar(R.id.shoutcastButtons, actions);
364 	}
365 }