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