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