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.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.concurrent.Callable;
29  import java.util.concurrent.CopyOnWriteArrayList;
30  
31  import org.xml.sax.SAXException;
32  
33  import sk.baka.ambient.ActionsEnum;
34  import sk.baka.ambient.AmbientApplication;
35  import sk.baka.ambient.R;
36  import sk.baka.ambient.ZoomEnum;
37  import sk.baka.ambient.collection.TrackMetadataBean;
38  import sk.baka.ambient.commons.Interval;
39  import sk.baka.ambient.commons.MiscUtils;
40  import sk.baka.ambient.stream.shoutcast.Radio;
41  import sk.baka.ambient.stream.shoutcast.ShoutcastUtils;
42  import sk.baka.ambient.views.gesturelist.GesturesListView;
43  import android.view.View;
44  import android.widget.TextView;
45  
46  /***
47   * Controls the shoutcast server manager.
48   * 
49   * @author Martin Vysny
50   */
51  public final class ShoutcastController extends AbstractListController {
52  
53  	/***
54  	 * Creates the shoutcast controller instance.
55  	 * 
56  	 * @param activity
57  	 *            the activity instance.
58  	 * @param playlistView
59  	 *            the playlist view
60  	 */
61  	public ShoutcastController(final MainActivity activity,
62  			final GesturesListView playlistView) {
63  		super(R.id.shoutcastbrowser, R.id.shoutcastList, activity);
64  		initButtonBar(R.id.shoutcastButtons, actions);
65  		// register the drag'n'drop target.
66  		listView.dragDropViews.clear();
67  		listView.dragDropViews.add(playlistView);
68  	}
69  
70  	@Override
71  	public void destroy() {
72  		listView.dragDropViews.clear();
73  		super.destroy();
74  	}
75  
76  	@Override
77  	protected void onAction(ActionsEnum action) {
78  		if (action == ActionsEnum.ShoutcastNameTrackSwitch) {
79  			isRadioNames = !isRadioNames;
80  			if (radioList != null) {
81  				sortRadioList();
82  				update(null);
83  			}
84  		}
85  		if (action == ActionsEnum.Back) {
86  			goBack();
87  		}
88  		super.onAction(action);
89  	}
90  
91  	/***
92  	 * The actions to display on the Task switcher.
93  	 */
94  	public static final List<ActionsEnum> actions = Collections
95  			.unmodifiableList(Arrays.asList(ActionsEnum.Back,
96  					ActionsEnum.ShoutcastNameTrackSwitch));
97  
98  	/***
99  	 * If <code>true</code> then a genre list is final being shown. If
100 	 * <code>false</code> then a radio list is being shown.
101 	 */
102 	private volatile boolean isShowingGenre = true;
103 
104 	/***
105 	 * Used only when the radio list is shown. If <code>true</code> then the
106 	 * radio name is shown. If <code>false</code> then the currently played
107 	 * track name is shown.
108 	 */
109 	private boolean isRadioNames = true;
110 
111 	/***
112 	 * Cached genres list.
113 	 */
114 	private volatile List<String> genres = null;
115 
116 	/***
117 	 * Returns list of genres. If the genre list is not yet loaded then the
118 	 * download starts in the background and an empty list is returned.
119 	 * 
120 	 * @return the genre list.
121 	 */
122 	private List<String> getGenres() {
123 		if (genres != null) {
124 			return genres;
125 		}
126 		app.getBackgroundTasks().schedule(new GenreDownloader(),
127 				GenreDownloader.class, true,
128 				app.getString(R.string.shoutcastDownloadGenres));
129 		return Collections.emptyList();
130 	}
131 
132 	private final class GenreDownloader implements Callable<Void> {
133 		public Void call() throws SAXException, IOException {
134 			final List<String> genres = ShoutcastUtils.parseGenres();
135 			ShoutcastController.this.genres = new CopyOnWriteArrayList<String>(
136 					genres);
137 			updateAsync();
138 			return null;
139 		}
140 	}
141 
142 	private void updateAsync() {
143 		AmbientApplication.getHandler().post(new Runnable() {
144 			public void run() {
145 				update(null);
146 			}
147 		});
148 	}
149 
150 	@Override
151 	protected void visibilityChanged(boolean visible) {
152 		if (visible) {
153 			update(null);
154 		}
155 	}
156 
157 	@Override
158 	protected void recomputeListItems() {
159 		// handle offline mode
160 		final boolean offline = app.isOffline();
161 		mainView.findViewById(R.id.shoutcastbrowserControls).setVisibility(
162 				offline ? View.GONE : View.VISIBLE);
163 		mainView.findViewById(R.id.shoutcastOffline).setVisibility(
164 				offline ? View.VISIBLE : View.GONE);
165 		if (offline) {
166 			return;
167 		}
168 		listView.getModel().getModel().clear();
169 		if (!isVisible()) {
170 			return;
171 		}
172 		if (isShowingGenre) {
173 			listView.getModel().getModel().addAll(getGenres());
174 		} else {
175 			if (radioList != null) {
176 				listView.getModel().getModel().addAll(radioList);
177 			}
178 		}
179 	}
180 
181 	@Override
182 	public String getHint(Interval highlight) {
183 		return highlight.length + " radio(s)";
184 	}
185 
186 	@Override
187 	public boolean isComputeTracksLong(Interval interval) {
188 		return !interval.isEmpty();
189 	}
190 
191 	@Override
192 	public boolean isComputeTracksOnlineOp(Interval interval) {
193 		return !interval.isEmpty();
194 	}
195 
196 	@Override
197 	public boolean canComputeItems() {
198 		return !isShowingGenre;
199 	}
200 
201 	@Override
202 	public boolean canHighlight() {
203 		return !isShowingGenre;
204 	}
205 
206 	public void itemActivated(int index, Object model) {
207 		if (isShowingGenre) {
208 			showGenre((String) model);
209 		} else {
210 			final Radio r = (Radio) model;
211 			app.getBackgroundTasks().schedule(new PlaylistDownloader(r),
212 					PlaylistDownloader.class, true,
213 					app.getString(R.string.shoutcastDownloadURLs));
214 		}
215 	}
216 
217 	private final class PlaylistDownloader implements Callable<Void> {
218 		private final Radio r;
219 		private volatile boolean gettingPlaylist = true;
220 		private volatile List<TrackMetadataBean> playlist = null;
221 
222 		/***
223 		 * Creates new instance.
224 		 * 
225 		 * @param r
226 		 *            the radio.
227 		 */
228 		public PlaylistDownloader(Radio r) {
229 			this.r = r;
230 		}
231 
232 		public Void call() {
233 			if (gettingPlaylist) {
234 				playlist = getRadioStreams(r);
235 				playlist = Collections.synchronizedList(playlist);
236 				gettingPlaylist = false;
237 				AmbientApplication.getHandler()
238 						.post(MiscUtils.toRunnable(this));
239 			} else {
240 				app.getPlaylist().add(app.getPlaylist().size(), playlist);
241 			}
242 			return null;
243 		}
244 	}
245 
246 	/***
247 	 * Loads the radio list.
248 	 * 
249 	 * @author Martin Vysny
250 	 */
251 	private class RadioDownloader implements Callable<Void> {
252 		private final String genre;
253 
254 		/***
255 		 * Creates new instance.
256 		 * 
257 		 * @param genre
258 		 *            the genre.
259 		 */
260 		public RadioDownloader(final String genre) {
261 			this.genre = genre;
262 		}
263 
264 		public Void call() throws SAXException, IOException {
265 			final List<Radio> list = ShoutcastUtils.getRadioList(genre);
266 			radioList = Collections.synchronizedList(list);
267 			sortRadioList();
268 			isShowingGenre = false;
269 			updateAsync();
270 			return null;
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 			view.setText(radioToString(r));
307 		}
308 	}
309 
310 	private String radioToString(final Radio r) {
311 		if (isRadioNames) {
312 			return r.name + " (" + r.genre + ")";
313 		} else {
314 			return r.currentTrack + " (" + r.genre + ")";
315 		}
316 	}
317 
318 	private List<TrackMetadataBean> getRadioStreams(final Radio radio) {
319 		try {
320 			return radio.getRadioURLs();
321 		} catch (final Exception ex) {
322 			throw new RuntimeException(ex);
323 		}
324 	}
325 
326 	@Override
327 	public List<TrackMetadataBean> computeTracks(Interval highlight) {
328 		if (isShowingGenre)
329 			throw new IllegalStateException();
330 		final List<TrackMetadataBean> result = new ArrayList<TrackMetadataBean>();
331 		for (int i = highlight.start; i <= highlight.end; i++) {
332 			final Radio r = radioList.get(i);
333 			result.addAll(getRadioStreams(r));
334 		}
335 		return new CopyOnWriteArrayList<TrackMetadataBean>(result);
336 	}
337 
338 	private void sortRadioList() {
339 		if (isRadioNames) {
340 			ShoutcastUtils.sortByName(radioList);
341 		} else {
342 			Collections.sort(radioList, new Comparator<Radio>() {
343 				public int compare(Radio object1, Radio object2) {
344 					return object1.currentTrack
345 							.compareToIgnoreCase(object2.currentTrack);
346 				}
347 			});
348 		}
349 	}
350 
351 	/***
352 	 * Browse given genre.
353 	 * 
354 	 * @param genre
355 	 *            the genre to browse.
356 	 */
357 	public void showGenre(final String genre) {
358 		// read the radio list
359 		app.getBackgroundTasks().schedule(new RadioDownloader(genre),
360 				RadioDownloader.class, true,
361 				app.getString(R.string.shoutcastDownloadStations));
362 	}
363 
364 	@Override
365 	protected void performZoom(final Map<ZoomEnum, Integer> zoom) {
366 		super.performZoom(zoom);
367 		initButtonBar(R.id.shoutcastButtons, actions);
368 	}
369 
370 	public String toString(Object model) {
371 		return radioToString((Radio) model);
372 	}
373 }