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
436
437
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
452
453
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 }