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
19 package sk.baka.ambient;
20
21 import java.text.ParseException;
22
23 import sk.baka.ambient.activity.ErrorActivity;
24 import sk.baka.ambient.activity.main.MainActivity;
25 import sk.baka.ambient.collection.TrackMetadataBean;
26 import sk.baka.ambient.collection.TrackOriginEnum;
27 import sk.baka.ambient.commons.Interval;
28 import sk.baka.ambient.commons.MiscUtils;
29 import sk.baka.ambient.commons.TagFormatter;
30 import sk.baka.ambient.playerservice.IPlayerListener;
31 import sk.baka.ambient.playerservice.PlayerStateEnum;
32 import sk.baka.ambient.playlist.PlaylistItem;
33 import sk.baka.ambient.playlist.Random;
34 import sk.baka.ambient.playlist.Repeat;
35 import android.app.Notification;
36 import android.app.NotificationManager;
37 import android.app.PendingIntent;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.graphics.Typeface;
41 import android.text.SpannableStringBuilder;
42 import android.text.Spanned;
43 import android.text.style.StyleSpan;
44 import android.widget.Toast;
45
46 /***
47 * Controls the notification shown when a track is being played.
48 *
49 * @author Martin Vysny
50 */
51 public final class PlaybackNotificator {
52 /***
53 * The ID of the notification.
54 */
55 public static final int NOTIFICATION_ID = 23847;
56
57 /***
58 * The ID of the error notification.
59 */
60 public static final int ERROR_NOTIFICATION_ID = 23848;
61
62 private final AmbientApplication app;
63
64 /***
65 * Creates new notification controller.
66 *
67 * @param app
68 * the application instance.
69 */
70 PlaybackNotificator(final AmbientApplication app) {
71 super();
72 this.app = app;
73 app.getBus().addHandler(listener);
74 final boolean isPlaying = AmbientApplication.getInstance()
75 .getPlaylist().getPlaybackState() == PlayerStateEnum.Playing;
76 final TrackMetadataBean track = AmbientApplication.getInstance()
77 .getPlaylist().getCurrentlyPlayingTrack();
78 if ((track != null) && isPlaying) {
79 createNotification(track);
80 }
81 }
82
83 private final Listener listener = new Listener();
84
85 private void createNotification(final TrackMetadataBean track) {
86 if (app.isShutdown()) {
87 cancelNotifications();
88 return;
89 }
90 final NotificationManager nm = (NotificationManager) app
91 .getSystemService(Context.NOTIFICATION_SERVICE);
92 final SpannableStringBuilder text = new SpannableStringBuilder();
93 if (!MiscUtils.isEmptyOrWhitespace(track.getAlbum())) {
94 text.append(track.getAlbum());
95 }
96 if (!MiscUtils.isEmptyOrWhitespace(track.getArtist())) {
97 if (text.length() != 0) {
98 text.append(" ");
99 final String by = app.getString(R.string.trackBy);
100 text.append(by);
101 text.setSpan(new StyleSpan(Typeface.ITALIC), text.length()
102 - by.length(), text.length(),
103 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
104 text.append(" ");
105 text.append(track.getArtist());
106 }
107 }
108 final Notification notification = new Notification(R.drawable.icon,
109 null, System.currentTimeMillis());
110 final PendingIntent contentIntent = PendingIntent.getActivity(app, 0,
111 new Intent(app, MainActivity.class), 0);
112 notification.setLatestEventInfo(app, track.getDisplayableName(), text,
113 contentIntent);
114 notification.flags = Notification.FLAG_NO_CLEAR
115 | Notification.FLAG_ONGOING_EVENT;
116 nm.notify(NOTIFICATION_ID, notification);
117 }
118
119 /***
120 * Cancels all notifications.
121 */
122 public void cancelNotifications() {
123 final NotificationManager nm = (NotificationManager) app
124 .getSystemService(Context.NOTIFICATION_SERVICE);
125 nm.cancel(NOTIFICATION_ID);
126 nm.cancel(ERROR_NOTIFICATION_ID);
127 }
128
129 /***
130 * Cancels the playback notification.
131 */
132 public void cancelPlaybackNotification() {
133 final NotificationManager nm = (NotificationManager) app
134 .getSystemService(Context.NOTIFICATION_SERVICE);
135 nm.cancel(NOTIFICATION_ID);
136 }
137
138 private final class Listener implements IPlaylistPlayerListener,
139 IPlayerListener {
140
141 public void playbackStateChanged(PlayerStateEnum state) {
142
143 if (state != PlayerStateEnum.Playing) {
144 cancelPlaybackNotification();
145 } else {
146 final TrackMetadataBean track = AmbientApplication
147 .getInstance().getPlaylist().getCurrentlyPlayingTrack();
148 if (track != null) {
149 createNotification(track);
150 }
151 }
152 }
153
154 public void playlistChanged(Interval target) {
155
156 }
157
158 public void randomChanged(Random random) {
159
160 }
161
162 public void repeatChanged(Repeat repeat) {
163
164 }
165
166 public void trackChanged(PlaylistItem track, boolean play,
167 int positionMillis) {
168
169 final boolean isPlaying = AmbientApplication.getInstance()
170 .getPlaylist().getPlaybackState() == PlayerStateEnum.Playing;
171 if ((track != null) && isPlaying) {
172 createNotification(track.getTrack());
173 }
174
175 if (track == null) {
176 return;
177 }
178 if (!play) {
179 return;
180 }
181 if (app.getStateHandler().getConfig().notifyWhenMinimizedOnly
182 && app.isMainActivityVisible()) {
183 return;
184 }
185 notifyOnNextTrack(track.getTrack());
186 }
187
188 public void trackPositionChanged(int position, boolean playing) {
189
190 }
191
192 public void buffered(byte percent) {
193
194 }
195
196 public void radioNewTrack(String name) {
197
198 final TrackMetadataBean.Builder b = new TrackMetadataBean.Builder();
199 createNotification(b.setOrigin(TrackOriginEnum.Shoutcast)
200 .setLocation("Shoutcast radio").setTitle(name).build(-1));
201
202 final TrackMetadataBean bean = TrackMetadataBean.newBuilder()
203 .setLocation(name).setTitle(name).setOrigin(
204 TrackOriginEnum.Shoutcast).build(-1);
205 notifyOnNextTrack(bean);
206
207 }
208
209 public void started(String file, int duration, int currentPosition) {
210
211
212
213 }
214
215 public void stopped(String error, boolean errorMissing,
216 TrackOriginEnum origin) {
217
218
219
220 }
221 }
222
223 /***
224 * Shows a notification that
225 *
226 * @param bean
227 * the bean to format
228 */
229 private synchronized void notifyOnNextTrack(final TrackMetadataBean bean) {
230 notifyOnNextTrack(bean, app.getStateHandler().getConfig());
231 }
232
233 /***
234 * Forces the application to show a notification based on given
235 * configuration.
236 *
237 * @param bean
238 * the bean to format
239 * @param config
240 * the configuration to use
241 */
242 public synchronized void notifyOnNextTrack(final TrackMetadataBean bean,
243 final ConfigurationBean config) {
244 if (config.notificationType == ConfigurationBean.TrackChangeNotifEnum.None) {
245 return;
246 }
247
248 final boolean shortNotif = config.notificationType == ConfigurationBean.TrackChangeNotifEnum.Short;
249 final String format = shortNotif ? config.shortNotificationFormat
250 : config.longNotificationFormat;
251 try {
252 if ((notificationFormatter == null)
253 || !notificationFormatter.formatString.equals(format)) {
254 notificationFormatter = new TagFormatter(format);
255 }
256 } catch (ParseException e) {
257 throw new RuntimeException(e);
258 }
259 final String formatted = notificationFormatter.format(bean);
260
261 final Toast toast = Toast.makeText(app, formatted,
262 config.notificationType.viewDuration);
263 toast.setGravity(config.notificationLocation.gravity, 0, 0);
264 toast.show();
265 }
266
267 /***
268 * Cached notification formatter instance.
269 */
270 private TagFormatter notificationFormatter;
271
272 /***
273 * Destroys the notificator, removing it from the bus.
274 */
275 void destroy() {
276 app.getBus().removeHandler(listener);
277 }
278
279 /***
280 * Updates the error notification.
281 *
282 * @param errorCount
283 * the error/warning count. If zero the notification is hidden.
284 * @param last
285 * last error/warning or <code>null</code> if no errors are
286 * present.
287 */
288 void updateErrorNotification(final int errorCount,
289 final ErrorHandler.ErrorInfo last) {
290 final NotificationManager nm = (NotificationManager) app
291 .getSystemService(Context.NOTIFICATION_SERVICE);
292 if (errorCount <= 0) {
293 nm.cancel(ERROR_NOTIFICATION_ID);
294 return;
295 }
296 final Notification notification = new Notification(R.drawable.quit, null,
297 last.timestamp);
298 final PendingIntent contentIntent = PendingIntent.getActivity(app, 0,
299 new Intent(app, ErrorActivity.class), 0);
300 notification.setLatestEventInfo(app, app
301 .getString(R.string.ambientError), last.message, contentIntent);
302 notification.number = errorCount;
303 nm.notify(ERROR_NOTIFICATION_ID, notification);
304 }
305 }