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
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
288
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 }