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 text.setSpan(new StyleSpan(Typeface.ITALIC), text.length() - 2,
100 text.length(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
101 text.append(track.getArtist());
102 }
103 }
104 final Notification notification = new Notification(R.drawable.icon,
105 null, 0);
106 final PendingIntent contentIntent = PendingIntent.getActivity(app, 0,
107 new Intent(app, MainActivity.class), 0);
108 final StringBuilder trackName = new StringBuilder(track.getDisplayableName());
109 if (track.getLength() > 0) {
110 trackName.append(" (");
111 track.appendDisplayableLength(trackName, false);
112 trackName.append(')');
113 }
114 notification.setLatestEventInfo(app, trackName, text,
115 contentIntent);
116 notification.flags = Notification.FLAG_NO_CLEAR
117 | Notification.FLAG_ONGOING_EVENT;
118 nm.notify(NOTIFICATION_ID, notification);
119 }
120
121 /***
122 * Cancels all notifications.
123 */
124 public void cancelNotifications() {
125 final NotificationManager nm = (NotificationManager) app
126 .getSystemService(Context.NOTIFICATION_SERVICE);
127 nm.cancel(NOTIFICATION_ID);
128 nm.cancel(ERROR_NOTIFICATION_ID);
129 }
130
131 /***
132 * Cancels the playback notification.
133 */
134 public void cancelPlaybackNotification() {
135 final NotificationManager nm = (NotificationManager) app
136 .getSystemService(Context.NOTIFICATION_SERVICE);
137 nm.cancel(NOTIFICATION_ID);
138 }
139
140 private final class Listener implements IPlaylistPlayerListener,
141 IPlayerListener {
142
143 public void playbackStateChanged(PlayerStateEnum state) {
144
145 if (state != PlayerStateEnum.Playing) {
146 cancelPlaybackNotification();
147 } else {
148 final TrackMetadataBean track = AmbientApplication
149 .getInstance().getPlaylist().getCurrentlyPlayingTrack();
150 if (track != null) {
151 createNotification(track);
152 }
153 }
154 }
155
156 public void playlistChanged(Interval target) {
157
158 }
159
160 public void randomChanged(Random random) {
161
162 }
163
164 public void repeatChanged(Repeat repeat) {
165
166 }
167
168 public void trackChanged(PlaylistItem track, boolean play,
169 int positionMillis) {
170
171 final boolean isPlaying = AmbientApplication.getInstance()
172 .getPlaylist().getPlaybackState() == PlayerStateEnum.Playing;
173 if ((track != null) && isPlaying) {
174 createNotification(track.getTrack());
175 }
176
177 if (track == null) {
178 return;
179 }
180 if (!play) {
181 return;
182 }
183 if (app.getStateHandler().getConfig().notifyWhenMinimizedOnly
184 && app.isMainActivityVisible()) {
185 return;
186 }
187 notifyOnNextTrack(track.getTrack());
188 }
189
190 public void trackPositionChanged(int position, boolean playing) {
191
192 }
193
194 public void buffered(byte percent) {
195
196 }
197
198 public void radioNewTrack(String name) {
199
200 final TrackMetadataBean.Builder b = new TrackMetadataBean.Builder();
201 createNotification(b.setOrigin(TrackOriginEnum.Shoutcast)
202 .setLocation("Shoutcast radio").setTitle(name).build(-1));
203
204 final TrackMetadataBean bean = TrackMetadataBean.newBuilder()
205 .setLocation(name).setTitle(name).setOrigin(
206 TrackOriginEnum.Shoutcast).build(-1);
207 notifyOnNextTrack(bean);
208
209 }
210
211 public void started(String file, int duration, int currentPosition) {
212
213
214
215 }
216
217 public void stopped(String error, boolean errorMissing,
218 TrackOriginEnum origin) {
219
220
221
222 }
223 }
224
225 /***
226 * Shows a notification that
227 *
228 * @param bean
229 * the bean to format
230 */
231 private synchronized void notifyOnNextTrack(final TrackMetadataBean bean) {
232 notifyOnNextTrack(bean, app.getStateHandler().getConfig());
233 }
234
235 /***
236 * Forces the application to show a notification based on given
237 * configuration.
238 *
239 * @param bean
240 * the bean to format
241 * @param config
242 * the configuration to use
243 */
244 public synchronized void notifyOnNextTrack(final TrackMetadataBean bean,
245 final ConfigurationBean config) {
246 if (config.notificationType == ConfigurationBean.TrackChangeNotifEnum.None) {
247 return;
248 }
249
250 final boolean shortNotif = config.notificationType == ConfigurationBean.TrackChangeNotifEnum.Short;
251 final String format = shortNotif ? config.shortNotificationFormat
252 : config.longNotificationFormat;
253 try {
254 if ((notificationFormatter == null)
255 || !notificationFormatter.formatString.equals(format)) {
256 notificationFormatter = new TagFormatter(format);
257 }
258 } catch (ParseException e) {
259 throw new RuntimeException(e);
260 }
261 final String formatted = notificationFormatter.format(bean);
262
263 final Toast toast = Toast.makeText(app, formatted,
264 config.notificationType.viewDuration);
265 toast.setGravity(config.notificationLocation.gravity, 0, 0);
266 toast.show();
267 }
268
269 /***
270 * Cached notification formatter instance.
271 */
272 private TagFormatter notificationFormatter;
273
274 /***
275 * Destroys the notificator, removing it from the bus.
276 */
277 void destroy() {
278 app.getBus().removeHandler(listener);
279 }
280
281 /***
282 * Updates the error notification.
283 *
284 * @param errorCount
285 * the error/warning count. If zero the notification is hidden.
286 * @param last
287 * last error/warning or <code>null</code> if no errors are
288 * present.
289 */
290 void updateErrorNotification(final int errorCount,
291 final ErrorHandler.ErrorInfo last) {
292 final NotificationManager nm = (NotificationManager) app
293 .getSystemService(Context.NOTIFICATION_SERVICE);
294 if (errorCount <= 0) {
295 nm.cancel(ERROR_NOTIFICATION_ID);
296 return;
297 }
298 final Notification notification = new Notification(R.drawable.quit, null,
299 last.timestamp);
300 final PendingIntent contentIntent = PendingIntent.getActivity(app, 0,
301 new Intent(app, ErrorActivity.class), 0);
302 notification.setLatestEventInfo(app, app
303 .getString(R.string.ambientError), last.message, contentIntent);
304 notification.number = errorCount;
305 nm.notify(ERROR_NOTIFICATION_ID, notification);
306 }
307 }