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  package sk.baka.ambient.activity.main;
19  
20  import java.io.Serializable;
21  import java.util.ArrayList;
22  import java.util.EnumMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import sk.baka.ambient.ActionsEnum;
27  import sk.baka.ambient.AmbientApplication;
28  import sk.baka.ambient.AppState;
29  import sk.baka.ambient.PlaylistPlayer;
30  import sk.baka.ambient.R;
31  import sk.baka.ambient.ZoomEnum;
32  import sk.baka.ambient.AppState.IAppStateAware;
33  import sk.baka.ambient.activity.AboutActivity;
34  import sk.baka.ambient.activity.ConfigActivity;
35  import sk.baka.ambient.activity.StatisticsActivity;
36  import sk.baka.ambient.activity.WelcomeActivity;
37  import sk.baka.ambient.activity.search.SearchActivity;
38  import sk.baka.ambient.activity.search.SearchType;
39  import sk.baka.ambient.collection.TrackMetadataBean;
40  import sk.baka.ambient.commons.Interval;
41  import sk.baka.ambient.commons.MiscUtils;
42  import sk.baka.ambient.commons.SimpleBus;
43  import sk.baka.ambient.playerservice.PlayerStateEnum;
44  import sk.baka.ambient.playlist.Random;
45  import sk.baka.ambient.playlist.Repeat;
46  import sk.baka.ambient.views.Iconify;
47  import sk.baka.ambient.views.ViewUtils;
48  import sk.baka.ambient.views.gesturelist.GesturesListView;
49  import android.app.ListActivity;
50  import android.content.Context;
51  import android.content.Intent;
52  import android.content.SharedPreferences;
53  import android.os.Bundle;
54  import android.text.Spannable;
55  import android.text.SpannableStringBuilder;
56  import android.util.Log;
57  import android.view.KeyEvent;
58  import android.view.Menu;
59  import android.view.MenuItem;
60  import android.view.MotionEvent;
61  import android.view.View;
62  
63  /***
64   * The main activity. Handles GUI operations for all GUI components.
65   * 
66   * @author Martin Vysny
67   */
68  public class MainActivity extends ListActivity implements IAppStateAware {
69  	private final AmbientApplication app = AmbientApplication.getInstance();
70  
71  	/***
72  	 * Send an intent with the activity enum name put under this key to activate
73  	 * the activity.
74  	 */
75  	public final static String INTENTDATA_ACTIVITY = "INTENTDATA_ACTIVITY";
76  
77  	/***
78  	 * Sets a new zoom level. Expects an instance of {@link EnumMap}.
79  	 */
80  	public static final String INTENTKEY_ZOOM = "INTENTKEY_ZOOM";
81  
82  	/***
83  	 * Send an intent having this extra to save a configuration.
84  	 */
85  	public final static String INTENTDATA_STORECONFIG = "INTENTDATA_STORECONFIG";
86  
87  	/***
88  	 * The list of switchable controllers. Only one of these controllers may be
89  	 * visible at the same time.
90  	 */
91  	ControllerGroup controllers;
92  
93  	/***
94  	 * The list of always visible controllers.
95  	 */
96  	private ControllerGroup alwaysVisibleControllers;
97  
98  	@Override
99  	public void onCreate(Bundle icicle) {
100 		super.onCreate(icicle);
101 		setContentView(R.layout.mainactivity);
102 		findViewById(R.id.mainPadded).setPadding(5, 2, 5, 3);
103 		findViewById(R.id.mainCaptionPadded).setPadding(5, 4, 5, 0);
104 		controllers = new ControllerGroup();
105 		alwaysVisibleControllers = new ControllerGroup();
106 		alwaysVisibleControllers.add(new WindowButtonsController(this), null);
107 		alwaysVisibleControllers.add(new TaskSwitcherController(this), null);
108 		controllers.add(new PlayerController(this), ActionsEnum.ShowPlayer);
109 		final int playlistViewId = PlaylistController.PLAYLIST_VIEW_ID;
110 		final GesturesListView playlistView = (GesturesListView) findViewById(playlistViewId);
111 		controllers.add(new CollectionController(this, playlistView),
112 				ActionsEnum.ShowCollection);
113 		controllers.add(new FileBrowserController(this, playlistView),
114 				ActionsEnum.ShowFileBrowser);
115 		final PlaylistController playlistController = new PlaylistController(
116 				this);
117 		playlistController.listView
118 				.setEmptyView(findViewById(android.R.id.empty));
119 		alwaysVisibleControllers.add(playlistController, null);
120 		controllers.add(new PlaylistManagerController(this),
121 				ActionsEnum.PlaylistManagement);
122 		final StaticPlaylistController searchResultsController = new StaticPlaylistController(
123 				R.id.searchresults, R.id.searchresultsList, this,
124 				new ArrayList<TrackMetadataBean>(), true);
125 		searchResultsController.listView.dragDropViews.clear();
126 		searchResultsController.listView.dragDropViews.add(playlistView);
127 		controllers.add(searchResultsController, null);
128 		controllers.add(new MagnatuneController(this, playlistView),
129 				ActionsEnum.ShowMagnatune);
130 		controllers.add(new ShoutcastController(this, playlistView),
131 				ActionsEnum.ShowShoutcast);
132 		controllers.add(new ContextController(this), ActionsEnum.ShowContext);
133 		controllers.add(new AmpacheController(this, playlistView),
134 				ActionsEnum.ShowAmpache);
135 		reinit(app.getStateHandler().getStartupState());
136 		handleIntent(getIntent());
137 		showWelcomeScreen();
138 	}
139 
140 	private void showWelcomeScreen() {
141 		String version = app.getStateHandler().getStartupState().welcomeVersion;
142 		if (!MiscUtils.nullEquals(version, app.getVersion())) {
143 			version = app.getVersion();
144 			app.getStateHandler().getStartupState().welcomeVersion = version;
145 			final Intent intent = new Intent(this, WelcomeActivity.class);
146 			startActivity(intent);
147 		}
148 	}
149 
150 	@Override
151 	protected void onDestroy() {
152 		controllers.destroy();
153 		alwaysVisibleControllers.destroy();
154 		super.onDestroy();
155 	}
156 
157 	@Override
158 	protected void onStop() {
159 		storeState(app.getStateHandler().getStartupState());
160 		app.getStateHandler().unregisterStateAware(this);
161 		controllers.flipVisibility(null, false);
162 		final SimpleBus bus = app.getBus();
163 		controllers.unregister(bus);
164 		alwaysVisibleControllers.unregister(bus);
165 		super.onStop();
166 	}
167 
168 	@Override
169 	protected void onStart() {
170 		super.onStart();
171 		final SimpleBus bus = app.getBus();
172 		controllers.registerAndUpdate(bus);
173 		alwaysVisibleControllers.registerAndUpdate(bus);
174 		app.getStateHandler().registerStateAware(this);
175 		final AppState state = app.getStateHandler().getStartupState();
176 		flipVisibility(controllers.get(state.visibleController), Boolean.TRUE);
177 		final EnumMap<ZoomEnum, Integer> zoom = app.getStateHandler()
178 				.getStartupState().zoom;
179 		if (zoom != null) {
180 			controllers.zoom(zoom);
181 			alwaysVisibleControllers.zoom(zoom);
182 		}
183 	}
184 
185 	private ActionsEnum getEffectiveAction(final ActionsEnum action,
186 			final boolean cycle) {
187 		if (!cycle) {
188 			return action;
189 		}
190 		final Random random = action.getRandom();
191 		if (random != null) {
192 			return ActionsEnum.getAction(MiscUtils.nextOrThis(random, true));
193 		}
194 		final Repeat repeat = action.getRepeat();
195 		if (repeat != null) {
196 			return ActionsEnum.getAction(MiscUtils.nextOrThis(repeat, true));
197 		}
198 		return action;
199 	}
200 
201 	private boolean showTip(final ActionsEnum action, final boolean cycle) {
202 		final ActionsEnum effective = getEffectiveAction(action, cycle);
203 		if (effective.description == -1) {
204 			return false;
205 		}
206 		final SharedPreferences prefs = app.getSharedPreferences("tips",
207 				Context.MODE_PRIVATE);
208 		final boolean alreadyShown = prefs.getBoolean(effective.name(), false);
209 		if (alreadyShown) {
210 			return false;
211 		}
212 		prefs.edit().putBoolean(effective.name(), true).commit();
213 		final Spannable text = new SpannableStringBuilder(
214 				getText(effective.description));
215 		Iconify.iconify(this, text);
216 		ViewUtils.showOkDialog(this, getText(effective.caption), text,
217 				effective.icon);
218 		return true;
219 	}
220 
221 	/***
222 	 * Executes given action.
223 	 * 
224 	 * @param action
225 	 *            the action to take, must not be <code>null</code>.
226 	 * @param cycle
227 	 *            if <code>true</code> then random/repeat modes are cycled
228 	 *            instead of being activated.
229 	 */
230 	public void activateAction(final ActionsEnum action, final boolean cycle) {
231 		showTip(action, cycle);
232 		final PlaylistPlayer play = app.getPlaylist();
233 		switch (action) {
234 		case PlaybackStop:
235 			play.stop();
236 			break;
237 		case PlaybackPrevious:
238 			play.previous();
239 			break;
240 		case PlaybackPause:
241 			play.pause();
242 			break;
243 		case PlaybackPlay:
244 			if (play.getPlaybackState() == PlayerStateEnum.Stopped) {
245 				play.play();
246 			} else {
247 				play.pause();
248 			}
249 			break;
250 		case PlaybackNext:
251 			play.next();
252 			break;
253 		case ShowPlayer:
254 			flipVisibility(PlayerController.class, null);
255 			break;
256 		case ShowCollection:
257 			flipVisibility(CollectionController.class, null);
258 			break;
259 		case RandomNo:
260 			play.setRandom(MiscUtils.nextOrThis(Random.NONE, cycle));
261 			break;
262 		case RandomTracks:
263 			play.setRandom(MiscUtils.nextOrThis(Random.TRACK, cycle));
264 			break;
265 		case RandomAlbums:
266 			play.setRandom(MiscUtils.nextOrThis(Random.ALBUM, cycle));
267 			break;
268 		case RandomAlbumsTracks:
269 			play.setRandom(MiscUtils.nextOrThis(Random.ALBUM_TRACK, cycle));
270 			break;
271 		case RandomAlbumsPlaylist:
272 			play.setRandom(MiscUtils.nextOrThis(Random.ALBUM_PLAYLIST, cycle));
273 			break;
274 		case RepeatNothing:
275 			play.setRepeat(MiscUtils.nextOrThis(Repeat.NO, cycle));
276 			break;
277 		case RepeatAlbum:
278 			play.setRepeat(MiscUtils.nextOrThis(Repeat.ALBUM, cycle));
279 			break;
280 		case RepeatPlaylist: {
281 			play.setRepeat(MiscUtils.nextOrThis(Repeat.PLAYLIST, cycle));
282 		}
283 			break;
284 		case RepeatTrack:
285 			play.setRepeat(MiscUtils.nextOrThis(Repeat.TRACK, cycle));
286 			break;
287 		case PlaylistShuffle:
288 			play.shuffle();
289 			break;
290 		case QueueClear:
291 			play.clearQueue();
292 			break;
293 		case QueueTracks:
294 			final Interval selected = alwaysVisibleControllers
295 					.getController(PlaylistController.class).listView
296 					.getHighlight();
297 			play.queue(selected);
298 			break;
299 		case ShowFileBrowser:
300 			flipVisibility(FileBrowserController.class, null);
301 			break;
302 		case ShowMagnatune:
303 			flipVisibility(MagnatuneController.class, null);
304 			break;
305 		case PlaylistManagement:
306 			flipVisibility(PlaylistManagerController.class, null);
307 			break;
308 		case PlaylistClear:
309 		case RepopulatePlaylist:
310 			final Interval allTracks = new Interval(0, app.getPlaylist().size());
311 			play.remove(allTracks);
312 			break;
313 		case PlaylistSortByAlbums:
314 			play.sortByAlbumOrder();
315 			break;
316 		case PlaylistStatic:
317 			play.staticPlaylist();
318 			break;
319 		case PlaylistDynamic:
320 			play.dynamicPlaylist();
321 			break;
322 		case Minimize:
323 			finish();
324 			break;
325 		case Quit:
326 			finish();
327 			app.shutdown();
328 			break;
329 		case ShowStatistics: {
330 			final Intent intent = new Intent(this, StatisticsActivity.class);
331 			startActivity(intent);
332 		}
333 			break;
334 		case Configure: {
335 			final Intent intent = new Intent(this, ConfigActivity.class);
336 			startActivity(intent);
337 		}
338 			break;
339 		case About: {
340 			final Intent intent = new Intent(this, AboutActivity.class);
341 			startActivity(intent);
342 		}
343 			break;
344 		case ShowShoutcast:
345 			flipVisibility(ShoutcastController.class, null);
346 			break;
347 		case ShowContext:
348 			flipVisibility(ContextController.class, null);
349 			break;
350 		case GoOffline:
351 			app.setOffline(true);
352 			break;
353 		case GoOnline:
354 			app.setOffline(false);
355 			break;
356 		case Back:
357 		case PlaylistSave:
358 		case ShowLyrics:
359 		case MagnatuneRescan:
360 		case ShowLicense:
361 		case ShowArtistPage:
362 		case CollectionYear:
363 		case ShoutcastNameTrackSwitch:
364 		case RefreshKaraoke:
365 		case ShowKaraoke:
366 		case ShowWiki:
367 		case AmpacheStartServer:
368 		case AmpacheStopServer:
369 		case AmpacheConfigureClient:
370 		case AmpacheSynchronize:
371 		case DeleteSelected:
372 		case BuyAlbum:
373 		case GoToRoot:
374 		case Refresh:
375 		case AmpacheDownloadSelected:
376 			Log.d(MainActivity.class.getSimpleName(), "Not a global action: "
377 					+ action);
378 			break;
379 		case ShowAmpache:
380 			flipVisibility(AmpacheController.class, null);
381 			break;
382 		case ResizeKeypad:
383 			menu.startResizing();
384 			break;
385 		case DpadSearch:
386 			final View glv = getCurrentFocus();
387 			if (glv instanceof GesturesListView) {
388 				((GesturesListView) glv).activateDpadSearch();
389 			}
390 			break;
391 		}
392 	}
393 
394 	public void reinit(AppState state) {
395 		flipVisibility(controllers.get(state.visibleController), Boolean.TRUE);
396 	}
397 
398 	private void flipVisibility(AbstractController c, Boolean visibility) {
399 		controllers.flipVisibility(c, visibility);
400 		final ActionsEnum action = (c != null) && c.isVisible() ? controllers
401 				.getActionForController(c) : null;
402 		alwaysVisibleControllers.getController(TaskSwitcherController.class)
403 				.highlightAction(action);
404 	}
405 
406 	private void flipVisibility(Class<? extends AbstractController> clazz,
407 			Boolean visibility) {
408 		flipVisibility(clazz == null ? null : controllers.getController(clazz),
409 				visibility);
410 	}
411 
412 	public void storeState(AppState state) {
413 		state.visibleController = controllers.getVisibleControllerIndex();
414 	}
415 
416 	@Override
417 	protected void onNewIntent(final Intent intent) {
418 		super.onNewIntent(intent);
419 		handleIntent(intent);
420 	}
421 
422 	@SuppressWarnings("unchecked")
423 	private void handleIntent(final Intent intent) {
424 		if (intent.hasExtra(SearchActivity.INTENTKEY_STRING)) {
425 			final String str = intent.getExtras().getString(
426 					SearchActivity.INTENTKEY_STRING);
427 			final SearchType type = SearchActivity.getType(intent.getExtras());
428 			if (type == SearchType.StoredPlaylists) {
429 				controllers.getController(PlaylistManagerController.class)
430 						.loadPlaylist(str);
431 			} else {
432 				controllers.getController(ShoutcastController.class).showGenre(
433 						str);
434 				flipVisibility(ShoutcastController.class, Boolean.TRUE);
435 				// set the visible controller in the state as well - onStart()
436 				// is handled after this handler and could make other controller
437 				// visible.
438 				app.getStateHandler().getStartupState().visibleController = controllers
439 						.getVisibleControllerIndex();
440 			}
441 			return;
442 		}
443 		if (intent.hasExtra(SearchActivity.INTENTKEY_MODEL)) {
444 			final List<TrackMetadataBean> model = SearchActivity
445 					.getModel(intent.getExtras());
446 			final String query = SearchActivity.getQuery(intent.getExtras());
447 			final SearchType type = SearchActivity.getType(intent.getExtras());
448 			controllers.getController(StaticPlaylistController.class)
449 					.setResults(model, query, type);
450 			flipVisibility(StaticPlaylistController.class, Boolean.TRUE);
451 			// set the visible controller in the state as well - onStart() is
452 			// handled after this handler and could make other controller
453 			// visible.
454 			app.getStateHandler().getStartupState().visibleController = controllers
455 					.getVisibleControllerIndex();
456 			return;
457 		}
458 		if (intent.hasExtra(INTENTDATA_STORECONFIG)) {
459 			app.getStateHandler().saveConfig();
460 			return;
461 		}
462 		if (intent.hasExtra(INTENTDATA_ACTIVITY)) {
463 			final String data = intent.getStringExtra(INTENTDATA_ACTIVITY);
464 			final ActionsEnum action = ActionsEnum.valueOf(data);
465 			activateAction(action, false);
466 			return;
467 		}
468 		if (intent.hasExtra(INTENTKEY_ZOOM)) {
469 			final Serializable hu = intent.getSerializableExtra(INTENTKEY_ZOOM);
470 			final Map<ZoomEnum, Integer> data = (Map<ZoomEnum, Integer>) hu;
471 			zoom(data);
472 			return;
473 		}
474 	}
475 
476 	@Override
477 	public boolean onPrepareOptionsMenu(Menu menu) {
478 		super.onPrepareOptionsMenu(menu);
479 		this.menu.onPrepareOptionsMenu(menu);
480 		return true;
481 	}
482 
483 	@Override
484 	public boolean onOptionsItemSelected(MenuItem item) {
485 		return menu.onOptionsItemSelected(item);
486 	}
487 
488 	private void zoom(final Map<ZoomEnum, Integer> zoom) {
489 		controllers.zoom(zoom);
490 		alwaysVisibleControllers.zoom(zoom);
491 	}
492 
493 	private final MenuHandler menu = new MenuHandler(this);
494 
495 	@Override
496 	public boolean dispatchKeyEvent(KeyEvent event) {
497 		return menu.onKey(event) ? true : super
498 				.dispatchKeyEvent(event);
499 	}
500 
501 	@Override
502 	public boolean dispatchTrackballEvent(MotionEvent ev) {
503 		return menu.dispatchTrackballEvent(ev) ? true : super
504 				.dispatchTrackballEvent(ev);
505 	}
506 }