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;
19  
20  import java.io.InputStream;
21  import java.lang.ref.Reference;
22  import java.lang.ref.SoftReference;
23  import java.util.concurrent.ConcurrentHashMap;
24  import java.util.concurrent.ConcurrentMap;
25  
26  import sk.baka.ambient.activity.main.MainActivity;
27  import sk.baka.ambient.collection.ICollection;
28  import sk.baka.ambient.collection.TrackOriginEnum;
29  import sk.baka.ambient.commons.IOUtils;
30  import sk.baka.ambient.commons.MiscUtils;
31  import sk.baka.ambient.commons.ObjectStorage;
32  import sk.baka.ambient.commons.SimpleBus;
33  import sk.baka.ambient.library.Library;
34  import sk.baka.ambient.lrc.LRCStorage;
35  import android.app.Application;
36  import android.os.Handler;
37  import android.util.Log;
38  
39  /***
40   * Serves mainly for a primary access point to a singleton data structures and
41   * services, such as {@link Library}, {@link PlaylistPlayer} etc.
42   * 
43   * @author Martin Vysny
44   */
45  public final class AmbientApplication extends Application {
46  
47  	/***
48  	 * The cover cache manager.
49  	 */
50  	private CoverCache covers;
51  
52  	/***
53  	 * The karaoke lyrics cache manager.
54  	 */
55  	private LRCStorage karaoke;
56  
57  	/***
58  	 * Stored playlists.
59  	 */
60  	private ObjectStorage storedPlaylists;
61  
62  	/***
63  	 * The music library.
64  	 */
65  	private Library library;
66  
67  	/***
68  	 * The playlist player.
69  	 */
70  	private PlaylistPlayer playlist;
71  
72  	/***
73  	 * The message bus.
74  	 */
75  	private SimpleBus bus;
76  
77  	/***
78  	 * The main message handler.
79  	 */
80  	private static Handler handler;
81  
82  	/***
83  	 * Returns the main message loop handler.
84  	 * 
85  	 * @return the handler.
86  	 */
87  	public static Handler getHandler() {
88  		if (handler == null) {
89  			throw new IllegalStateException("null handler");
90  		}
91  		return handler;
92  	}
93  
94  	private BackgroundOpExecutor backgroundTasks;
95  
96  	private PlaybackNotificator playbackNotificator;
97  
98  	private NetworkHandler networkHandler;
99  
100 	/***
101 	 * Returns the playback notificator instance.
102 	 * 
103 	 * @return the instance, never <code>null</code>.
104 	 */
105 	public PlaybackNotificator getNotificator() {
106 		return playbackNotificator;
107 	}
108 
109 	@Override
110 	public void onCreate() {
111 		super.onCreate();
112 		if (instance != null) {
113 			throw new IllegalStateException("Not a singleton");
114 		}
115 		instance = this;
116 		handler = new Handler();
117 		errorHandler = new ErrorHandler(this);
118 		try {
119 			stateHandler = new AppStateHandler(this);
120 			final ConfigurationBean config = stateHandler.getConfig();
121 			final AppState state = stateHandler.getStartupState();
122 			bus = new SimpleBus();
123 			backgroundTasks = new BackgroundOpExecutor();
124 			library = new Library(this);
125 			covers = new CoverCache();
126 			covers.setMaxStorageSize(config.coverCache * 1024);
127 			covers.setCacheOverflowBehavior(config.dontDownloadOnCacheFull);
128 			bus.addHandler(covers);
129 			karaoke = new LRCStorage();
130 			storedPlaylists = new ObjectStorage(".playlist");
131 			playlist = new PlaylistPlayer(this);
132 			// start with an empty static playlist
133 			playlist.staticPlaylist();
134 			stateHandler.registerStateAware(playlist);
135 			new PhoneStateHandler(this);
136 			playbackNotificator = new PlaybackNotificator(this);
137 			networkHandler = new NetworkHandler(this);
138 			networkHandler.setForcedOffline(!state.online);
139 		} catch (Exception e) {
140 			instance = null;
141 			Log.e(AmbientApplication.class.getSimpleName(),
142 					"Failed to initialize", e);
143 			throw new RuntimeException(e);
144 		}
145 	}
146 
147 	/***
148 	 * Sets the online/offline state.
149 	 * 
150 	 * @param offline
151 	 *            if <code>true</code> then the application switches into
152 	 *            offline mode.
153 	 */
154 	public void setOffline(final boolean offline) {
155 		networkHandler.setForcedOffline(offline);
156 	}
157 
158 	/***
159 	 * Retrieves current offline state.
160 	 * 
161 	 * @return if <code>true</code> then the application is in the offline mode.
162 	 */
163 	public boolean isOffline() {
164 		return networkHandler.isOffline();
165 	}
166 
167 	/***
168 	 * The error handler.
169 	 */
170 	private ErrorHandler errorHandler;
171 
172 	/***
173 	 * Returns the error handler instance.
174 	 * 
175 	 * @return the error handler instance, never <code>null</code>.
176 	 */
177 	public ErrorHandler getErrorHandler() {
178 		return errorHandler;
179 	}
180 
181 	/***
182 	 * The application state and configuration handler instance.
183 	 */
184 	private AppStateHandler stateHandler;
185 
186 	/***
187 	 * Returns the application state and configuration handler instance.
188 	 * 
189 	 * @return the application state and configuration handler instance, never
190 	 *         <code>null</code>.
191 	 */
192 	public AppStateHandler getStateHandler() {
193 		return stateHandler;
194 	}
195 
196 	/***
197 	 * Checks if {@link MainActivity} is visible.
198 	 * 
199 	 * @return <code>true</code> if the activity is visible, <code>false</code>
200 	 *         otherwise.
201 	 */
202 	public boolean isMainActivityVisible() {
203 		return stateHandler.containsStateAware(MainActivity.class);
204 	}
205 
206 	/***
207 	 * Returns the cover cache manager instance.
208 	 * 
209 	 * @return the cover cache manager.
210 	 */
211 	public CoverCache getCovers() {
212 		return covers;
213 	}
214 
215 	/***
216 	 * Returns the karaoke lyrics manager instance.
217 	 * 
218 	 * @return the karaoke lyrics manager.
219 	 */
220 	public LRCStorage getKaraoke() {
221 		return karaoke;
222 	}
223 
224 	/***
225 	 * Returns the cover cache manager instance.
226 	 * 
227 	 * @return the cover cache manager.
228 	 */
229 	public ObjectStorage getPlaylistStorage() {
230 		return storedPlaylists;
231 	}
232 
233 	private static AmbientApplication instance;
234 
235 	/***
236 	 * Returns application instance.
237 	 * 
238 	 * @return the application instance.
239 	 */
240 	public static AmbientApplication getInstance() {
241 		if (instance == null) {
242 			throw new IllegalStateException("Not yet initialized");
243 		}
244 		return instance;
245 	}
246 
247 	/***
248 	 * Returns singleton instance of the library which stores Magnatune tracks.
249 	 * 
250 	 * @return never <code>null</code>
251 	 */
252 	public Library getLibrary() {
253 		return library;
254 	}
255 
256 	/***
257 	 * Returns the background tasks executor.
258 	 * 
259 	 * @return non-<code>null</code> instance of executor.
260 	 */
261 	public BackgroundOpExecutor getBackgroundTasks() {
262 		return backgroundTasks;
263 	}
264 
265 	/***
266 	 * Returns singleton instance of the playlist.
267 	 * 
268 	 * @return never <code>null</code>.
269 	 */
270 	public PlaylistPlayer getPlaylist() {
271 		return playlist;
272 	}
273 
274 	/***
275 	 * Returns the message bus.
276 	 * 
277 	 * @return the message bus.
278 	 */
279 	public SimpleBus getBus() {
280 		return bus;
281 	}
282 
283 	@Override
284 	public void onTerminate() {
285 		try {
286 			try {
287 				bus.clear();
288 				// terminate tasks after the bus is clear, to prevent events
289 				// from popping up.
290 				networkHandler.destroy();
291 				playbackNotificator.destroy();
292 				backgroundTasks.terminate();
293 				playlist.close();
294 				library.close();
295 			} catch (final Exception ex) {
296 				Log.e(AmbientApplication.class.getSimpleName(),
297 						"Errors while shutting down: " + ex.getMessage(), ex);
298 			}
299 		} finally {
300 			super.onTerminate();
301 		}
302 	}
303 
304 	/***
305 	 * An error occurred. A notification will be shown to the user.
306 	 * 
307 	 * @param sender
308 	 *            the sender.
309 	 * @param error
310 	 *            error if <code>true</code>, warning if <code>false</code>.
311 	 * @param message
312 	 *            the message to show. The message will be followed by a new
313 	 *            line, the <code>Cause: </code> string and the
314 	 *            {@link Throwable#getMessage()} if the cause is not
315 	 *            <code>null</code>.
316 	 * @param cause
317 	 *            optional cause.
318 	 */
319 	public void error(final Class<?> sender, boolean error, String message,
320 			Throwable cause) {
321 		errorHandler.error(sender, error, message, cause);
322 	}
323 
324 	private Reference<Object> clipboard = null;
325 
326 	/***
327 	 * Sets clipboard contents.
328 	 * 
329 	 * @param obj
330 	 *            the clipboard object to set.
331 	 */
332 	public void setClipboard(final Object obj) {
333 		clipboard = new SoftReference<Object>(obj);
334 		bus.getInvocator(IApplicationListener.class, true).clipboardChanged();
335 	}
336 
337 	/***
338 	 * Retrieves a clipboard object.
339 	 * 
340 	 * @return the clipboard object, may be <code>null</code> if the clipboard
341 	 *         was cleared, either by client or by the garbage collector.
342 	 */
343 	public Object getClipboard() {
344 		if (clipboard == null)
345 			return null;
346 		return clipboard.get();
347 	}
348 
349 	/***
350 	 * Checks if the application is shutting down.
351 	 * 
352 	 * @return <code>true</code> if we are shutting down, <code>false</code>
353 	 *         otherwise.
354 	 */
355 	public boolean isShutdown() {
356 		return isShutdown;
357 	}
358 
359 	private boolean isShutdown = false;
360 
361 	/***
362 	 * Shuts down the application.
363 	 */
364 	public void shutdown() {
365 		isShutdown = true;
366 		onTerminate();
367 	}
368 
369 	private final ConcurrentMap<TrackOriginEnum, ICollection> providers = new ConcurrentHashMap<TrackOriginEnum, ICollection>();
370 
371 	/***
372 	 * Registers new provider for given origin. The provider will be used only
373 	 * for location fixing and must support
374 	 * {@link ICollection#fixLocations(java.util.Collection)}.
375 	 * 
376 	 * @param origin
377 	 *            the origin
378 	 * @param provider
379 	 *            the provider instance.
380 	 */
381 	public void registerProvider(final TrackOriginEnum origin,
382 			final ICollection provider) {
383 		if (!provider.supportsLocationFix()) {
384 			throw new IllegalArgumentException("Does not support location fix");
385 		}
386 		providers.put(origin, provider);
387 	}
388 
389 	/***
390 	 * Returns the provider for given origin. May return <code>null</code> if no
391 	 * such provider was registered.
392 	 * 
393 	 * @param origin
394 	 *            the origin
395 	 * @return the provider instance.
396 	 */
397 	public ICollection getProvider(final TrackOriginEnum origin) {
398 		return providers.get(origin);
399 	}
400 
401 	/***
402 	 * The Ambient version.
403 	 */
404 	private String version;
405 
406 	/***
407 	 * Returns the Ambient version.
408 	 * 
409 	 * @return the version string or "unknown" if the version is not available.
410 	 */
411 	public String getVersion() {
412 		if (version != null) {
413 			return version;
414 		}
415 		final InputStream in = getClassLoader().getResourceAsStream("version");
416 		if (in != null) {
417 			try {
418 				version = IOUtils.readLine(in);
419 			} catch (Exception ex) {
420 				Log.e(getClass().getSimpleName(), "Failed to get version", ex);
421 				version = "unknown";
422 			} finally {
423 				MiscUtils.closeQuietly(in);
424 			}
425 		}
426 		return version;
427 	}
428 }